Saltar al contenido
Chatbots de WhatsApp desde cero
Temario

Ejercicio: Entender texto libre

  • ⏱ 50 minutos
  • 📶 Nivel: intermedio

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:

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.

▶ Prueba tu código aquí (opcional)
Consola

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.

    texto.js
    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";
    }