Asignatura: Programación IV
Unidades Integradas: III (Manipulación del DOM y Eventos) y IV (JavaScript Asíncrono y APIs)
Objetivo: Integrar la manipulación eficiente del DOM con el consumo de APIs REST mediante fetch y async/await, construyendo un simulador básico de predicción de texto (modelo N-Grama) para comprender los fundamentos del procesamiento del lenguaje natural en IA.
Cuando una aplicación web consume datos de una API, la operación es asíncrona. JavaScript no detiene la ejecución del resto del código (evitando el bloqueo de renderización), sino que utiliza el Event Loop para manejar la respuesta cuando está lista.
async/await: Sintaxis moderna que hace que el código asíncrono se lea como si fuera síncrono, mejorando la legibilidad frente a los callbacks o .then().
Manejo de Errores: Es obligatorio usar bloques try...catch al consumir APIs para evitar que la aplicación falle silenciosamente si la red o el servidor responden con un error.
En el procesamiento del lenguaje natural (NLP), un N-Grama es una secuencia contigua de N elementos (generalmente palabras) extraída de un texto.
Bigrama (N=2): Analiza pares de palabras. Si el texto es "el gato come", los bigramas son: {"el": ["gato"], "gato": ["come"]}.
Simulación de IA: Al escribir una palabra, el modelo busca en su "memoria" (el diccionario de bigramas) cuáles son las palabras que estadísticamente le siguen, ofreciendo predicciones. Es la base simplificada de los teclados predictivos y modelos de lenguaje.
Instrucciones: Crear una carpeta de proyecto con index.html, styles.css y script.js. Seguir los pasos a continuación.
Crear la interfaz con un campo para obtener el texto base, un área de visualización y el simulador de escritura.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simulador N-Grama con Fetch API</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="contenedor">
<h1>Simulador de Predicción de Texto (Bigramas)</h1>
<!-- Sección de obtención de datos -->
<section class="panel-control">
<button id="btnCargarTexto" class="btn-primario">Cargar Texto Base desde API</button>
<p id="estadoCarga" class="texto-estado">Esperando acción...</p>
</section>
<!-- Visualización del texto base -->
<section class="panel-datos">
<h3>Texto Base Procesado:</h3>
<div id="contenedorTexto" class="caja-texto">El texto aparecerá aquí...</div>
</section>
<!-- Simulador de escritura -->
<section class="panel-simulacion">
<h3>Prueba el Predictor:</h3>
<input type="text" id="inputUsuario" placeholder="Escribe una palabra y presiona espacio..." autocomplete="off">
<div id="listaSugerencias" class="sugerencias"></div>
</section>
</main>
<script src="script.js"></script>
</body>
</html>
body { font-family: 'Segoe UI', sans-serif; background: #f4f4f9; color: #333; padding: 20px; }
.contenedor { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.btn-primario { background: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; }
.btn-primario:hover { background: #0056b3; }
.btn-primario:disabled { background: #ccc; cursor: not-allowed; }
.caja-texto { background: #e9ecef; padding: 15px; border-radius: 4px; min-height: 60px; white-space: pre-wrap; }
#inputUsuario { width: 100%; padding: 12px; font-size: 18px; border: 2px solid #ddd; border-radius: 4px; margin-top: 10px; }
.sugerencias { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.sugerencia-btn { background: #28a745; color: white; border: none; padding: 8px 15px; border-radius: 20px; cursor: pointer; }
.sugerencia-btn:hover { background: #218838; }
.texto-estado { font-style: italic; color: #666; }
.error { color: #dc3545; font-weight: bold; }
Implementar la obtención de datos desde un archivo local, el procesamiento del modelo N-Grama y la manipulación eficiente del DOM.
(Insertar aquí el código de script.js con la función obtenerTextoBase actualizada que se muestra arriba, junto con el resto de funciones construirBigramas, manejarEntradaUsuario y los Event Listeners que ya tenías).
// 1. Referencias al DOM (Cachear para evitar reflows innecesarios)
const btnCargar = document.getElementById('btnCargarTexto');
const estadoCarga = document.getElementById('estadoCarga');
const contenedorTexto = document.getElementById('contenedorTexto');
const inputUsuario = document.getElementById('inputUsuario');
const listaSugerencias = document.getElementById('listaSugerencias');
// Variable global para almacenar el modelo de bigramas
let modeloBigramas = {};
// 2. Función asíncrona para consumir el archivo JSON local
async function obtenerTextoBase() {
try {
// Estado de carga en el DOM
btnCargar.disabled = true;
estadoCarga.textContent = "Cargando datos desde el servidor local...";
estadoCarga.className = "texto-estado";
// Fetch al archivo JSON local en lugar de una API externa
const respuesta = await fetch('datos.json');
if (!respuesta.ok) {
throw new Error(`Error HTTP: ${respuesta.status}. Verifica que el archivo datos.json exista.`);
}
const datos = await respuesta.json();
const textoObtenido = datos.texto; // Extraemos la propiedad 'texto' del JSON
// Actualizar DOM de forma segura (textContent previene XSS)
contenedorTexto.textContent = textoObtenido;
estadoCarga.textContent = "Texto cargado exitosamente. ¡Escribe en el campo de abajo!";
// Procesar el texto para crear el modelo
modeloBigramas = construirBigramas(textoObtenido);
console.log("Modelo generado:", modeloBigramas);
} catch (error) {
console.error("Fallo en la petición:", error);
estadoCarga.textContent = `Error al cargar: ${error.message}. Asegúrate de usar un servidor local.`;
estadoCarga.className = "texto-estado error";
} finally {
btnCargar.disabled = false;
}
}
// 3. Lógica del Modelo N-Grama (Bigrama: N=2)
function construirBigramas(texto) {
// Extraer solo palabras (alfanuméricas), convertir a minúsculas
const palabras = texto.toLowerCase().match(/\b\w+\b/g) || [];
const modelo = {};
for (let i = 0; i < palabras.length - 1; i++) {
const palabraActual = palabras[i];
const siguientePalabra = palabras[i + 1];
if (!modelo[palabraActual]) {
modelo[palabraActual] = [];
}
// Evitar duplicados en las predicciones
if (!modelo[palabraActual].includes(siguientePalabra)) {
modelo[palabraActual].push(siguientePalabra);
}
}
return modelo;
}
// 4. Manejo de Eventos y Actualización del DOM
function manejarEntradaUsuario(evento) {
const textoActual = evento.target.value.trim().toLowerCase();
// Obtener la última palabra escrita (separada por espacios)
const palabras = textoActual.split(/\s+/);
const ultimaPalabra = palabras[palabras.length - 1];
// Limpiar sugerencias anteriores de forma eficiente
listaSugerencias.innerHTML = '';
if (ultimaPalabra && modeloBigramas[ultimaPalabra]) {
const predicciones = modeloBigramas[ultimaPalabra];
// Crear un DocumentFragment para manipulación eficiente del DOM (evita múltiples reflows)
const fragmento = document.createDocumentFragment();
predicciones.forEach(prediccion => {
const boton = document.createElement('button');
boton.textContent = prediccion;
boton.className = 'sugerencia-btn';
// Evento para autocompletar al hacer clic
boton.addEventListener('click', () => {
inputUsuario.value = textoActual + ' ' + prediccion + ' ';
inputUsuario.focus();
listaSugerencias.innerHTML = ''; // Limpiar tras seleccionar
});
fragmento.appendChild(boton);
});
listaSugerencias.appendChild(fragmento);
}
}
// 5. Registro de Event Listeners
btnCargar.addEventListener('click', obtenerTextoBase);
// Usamos 'input' para respuesta en tiempo real.
// En producción, se aplicaría 'debounce' para evitar exceso de cálculos.
inputUsuario.addEventListener('input', manejarEntradaUsuario);
Crea este archivo en la misma carpeta del proyecto. Contiene un texto de ejemplo relevante para procesamiento de lenguaje.
json
{
"texto": "La inteligencia artificial transforma la manera en que interactuamos con la tecnología. Los modelos de lenguaje procesan grandes cantidades de texto para predecir la siguiente palabra. JavaScript permite construir estas interfaces de forma dinámica y eficiente en el navegador."
}
Seguridad (XSS): Se usa textContent en lugar de innerHTML al mostrar el texto de la API, evitando la ejecución de scripts maliciosos si la API fuera comprometida.
Rendimiento del DOM: Se utiliza DocumentFragment para insertar múltiples botones de sugerencia en una sola operación de renderizado, evitando reflows repetitivos.
Manejo de Errores: El bloque try...catch...finally garantiza que la interfaz siempre informe al usuario del estado, incluso si la API falla (ej. sin conexión).
Delegación de Eventos: Si la lista de sugerencias fuera enorme, se podría agregar un solo addEventListener al contenedor listaSugerencias en lugar de uno por cada botón.
Responder de forma manuscrita o en un documento de texto, justificando cada respuesta con base en la práctica realizada y la teoría de las Unidades III y IV.
Análisis del Modelo N-Grama: Explica con tus propias palabras cómo el algoritmo construirBigramas transforma un texto plano en una estructura de datos útil para la predicción. ¿Qué limitación tendría este modelo si el texto base es muy corto?
Flujo Asíncrono: ¿Por qué es necesario usar await antes de respuesta.json()? ¿Qué sucedería en la interfaz de usuario si se intentara mostrar el texto antes de que la promesa se resuelva?
Rendimiento del DOM: En la función manejarEntradaUsuario, se utiliza document.createDocumentFragment(). Explica técnicamente por qué esta práctica es más eficiente que hacer listaSugerencias.appendChild(boton) directamente dentro del bucle forEach.
Seguridad y Robustez: Si la API externa cambiara su estructura y devolviera un objeto en lugar de un array, el código actual fallaría. ¿Cómo modificarías la función obtenerTextoBase para validar que los datos recibidos tienen el formato esperado antes de procesarlos?
API Alternativa: Si baconipsum.com presentara lentitud, se puede usar https://jsonplaceholder.typicode.com/posts/1 y usar la propiedad body del objeto recibido. El concepto de N-Grama funciona igual.
Extensión para Avanzados: Retar a los estudiantes a implementar un debounce (retraso de 300ms) en el evento input para evitar que el modelo se consulte en cada tecla pulsada, optimizando aún más el rendimiento.
Conexión con Seguridad: Resaltar que nunca se debe confiar ciegamente en los datos de una API (de ahí la validación y el uso de textContent en lugar de innerHTML).