Ejercicio: Entender texto libre
Objetivo
Construir una versión reducida de src/texto.js: normalizar lo que escribe el cliente, extraer cantidades, buscar productos por sinónimos y detectar la intención de un mensaje. Son las herramientas con las que el bot entiende «quiero dos leches» sin necesitar inteligencia artificial.
Cómo se trabaja
La parte A se hace en tu terminal con el código de referencia; la parte B son cuatro funciones de JavaScript puro que se comprueban en esta página. Trabajas con textos cortos del minimarket y con un catálogo de tres productos que las comprobaciones te pasan como argumento.
Reglas que debes implementar:
normalizar: minúsculas, sin tildes, todo lo que no sea letra o número se vuelve espacio, espacios colapsados y recortados. Si no recibe un texto, devuelve"".extraerCantidad: busca el primer número escrito en cifras (2) o en palabras (un,uno,una,dos,tres,cuatro,cinco,seis,diez).media docenavale 6;docenamultiplica por 12 (dos docenasson 24;una docenason 12;docenasola son 12). Si no hay nada, devuelve 1.buscarProductos(texto, productos): cada producto tieneidysinonimos(frases ya normalizadas). Una frase coincide solo si todas sus palabras aparecen en lo escrito (se acepta el plural simple consoes). Gana la frase con más palabras; si hay empate, se devuelven todos los ids empatados.detectarIntencion: tabla de frases por intención, probadas en este orden:cancelar,humano,horario,catalogo,confirmar,saludo. Se buscan palabras completas.confirmarysaludosolo valen si el mensaje tiene 4 palabras o menos. Si nada coincide,"desconocida".
| Intención | Frases |
|---|---|
cancelar |
cancelar, ya no quiero |
humano |
persona, asesor |
horario |
horario, a que hora |
catalogo |
catalogo, que venden |
confirmar |
si, ok, listo |
saludo |
hola, buenas |
💡 Reto extra
Añade tolerancia a errores de tipeo con la distancia de Levenshtein: acepta que «mantequila» coincida con «mantequilla», pero solo en palabras de 5 letras o más, para no confundir «sal» con «sol». Compara tu resultado con buscarProductos de src/texto.js.
Pasos
Marca cada paso a medida que lo completes. Tu avance se guarda en este navegador.
💡 Pista 1: Quitar tildes
texto.normalize("NFD") separa cada letra de su tilde. Luego .replace(/[̀-ͯ]/g, "") borra las tildes sueltas. Después reemplaza lo que no sea a-z, 0-9 o espacio por un espacio.
💡 Pista 2: Palabras completas, no pedazos
Para saber si una frase aparece como palabras completas, rodea el texto con espacios y busca la frase también rodeada de espacios: (" " + t + " ").includes(" " + frase + " "). Así si no coincide dentro de simple.
💡 Pista 3: Buscar productos
Divide lo escrito con split(" "). Para cada sinónimo, divídelo en palabras y verifica con every que cada una esté en lo escrito (igual, o con s/es al final). El puntaje es el número de palabras de la frase; guarda el máximo y los ids que lo alcanzan.
🔎 Comprueba tu trabajo
Pega tu código y pulsa Comprobar. La revisión ocurre en tu navegador; nada se envía a ningún sitio. Cada punto en rojo incluye una pista. Pega solo el JavaScript, sin las etiquetas <script>.
✅ Ver una solución posible
Intenta resolverlo antes de mirar. Tu versión puede ser distinta y también correcta.
function normalizar(texto) {
if (typeof texto !== "string") return "";
return texto
.toLowerCase()
.normalize("NFD")
.replace(/[̀-ͯ]/g, "")
.replace(/[^a-z0-9\s]/g, " ")
.replace(/\s+/g, " ")
.trim();
}
const NUMEROS = { un: 1, uno: 1, una: 1, dos: 2, tres: 3, cuatro: 4, cinco: 5, seis: 6, diez: 10 };
function extraerCantidad(texto) {
const t = normalizar(texto);
if (/\bmedia docena\b/.test(t)) return 6;
let n = null;
for (const palabra of t.split(" ")) {
if (/^\d+$/.test(palabra)) { n = Number(palabra); break; }
if (palabra in NUMEROS) { n = NUMEROS[palabra]; break; }
}
if (/\bdocenas?\b/.test(t)) return (n ?? 1) * 12;
return n ?? 1;
}
function mismaPalabra(escrita, deCatalogo) {
return escrita === deCatalogo || escrita === deCatalogo + "s" || escrita === deCatalogo + "es";
}
function buscarProductos(texto, productos) {
const escritas = normalizar(texto).split(" ").filter(Boolean);
let mejor = 0;
let ids = [];
for (const producto of productos) {
let puntaje = 0;
for (const frase of producto.sinonimos) {
const palabras = frase.split(" ");
const coincide = palabras.every((p) => escritas.some((e) => mismaPalabra(e, p)));
if (coincide) puntaje = Math.max(puntaje, palabras.length);
}
if (puntaje === 0) continue;
if (puntaje > mejor) { mejor = puntaje; ids = [producto.id]; }
else if (puntaje === mejor) ids.push(producto.id);
}
return ids;
}
const FRASES = {
cancelar: ["cancelar", "ya no quiero"],
humano: ["persona", "asesor"],
horario: ["horario", "a que hora"],
catalogo: ["catalogo", "que venden"],
confirmar: ["si", "ok", "listo"],
saludo: ["hola", "buenas"],
};
const ORDEN = ["cancelar", "humano", "horario", "catalogo", "confirmar", "saludo"];
const SOLO_CORTOS = ["confirmar", "saludo"];
function detectarIntencion(texto) {
const t = normalizar(texto);
if (!t) return "desconocida";
const palabras = t.split(" ").length;
for (const intencion of ORDEN) {
if (SOLO_CORTOS.includes(intencion) && palabras > 4) continue;
if (FRASES[intencion].some((f) => (" " + t + " ").includes(" " + f + " "))) return intencion;
}
return "desconocida";
}