Saltar al contenido principal

🧱 Módulo 3: Arquitectura del bot y flujos con addKeyword + addAnswer

En este módulo profundizaremos en la arquitectura de bot-whatsapp y aprenderás a construir flujos conversacionales básicos y sólidos con addKeyword y addAnswer, incluyendo captura de datos del usuario.


3.1 Componentes clave de la arquitectura

La arquitectura de bot-whatsapp se basa en 3 componentes principales:

1) Provider (Proveedor de mensajería)

Conecta el bot con WhatsApp. Ejemplos: BaileysProvider, MetaProvider, VenomProvider.

2) Database Adapter (Adaptador de almacenamiento)

Gestiona sesiones/estados. Ejemplos: JsonFileAdapter, MongoAdapter, MySQLAdapter.

3) Flow (Flujo conversacional)

Define respuestas y lógica del bot. Se crea con addKeyword(...) y se extiende con addAnswer, addAction, flowDynamic, gotoFlow, etc.


3.2 Arquitectura mínima lista para correr

const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require("@bot-whatsapp/bot");
const { BaileysProvider } = require("@bot-whatsapp/provider-baileys");
const { JsonFileAdapter } = require("@bot-whatsapp/database-json");

// Flujo mínimo de bienvenida
const flowBienvenida = addKeyword(["hola", "inicio"])
.addAnswer("Hola 👋, soy tu asistente virtual.")
.addAnswer("Escribe *menu* para ver opciones.");

const main = async () => {
const adapterDB = new JsonFileAdapter();
const adapterFlow = createFlow([flowBienvenida]);
const adapterProvider = createProvider(BaileysProvider);

await createBot({
flow: adapterFlow,
provider: adapterProvider,
database: adapterDB,
});
};

main();

Explicación rápida:

  • createFlow([...]) registra los flujos.
  • createProvider(BaileysProvider) define el conector WhatsApp.
  • JsonFileAdapter() guarda el estado en archivos (ideal para desarrollo).
  • createBot({...}) levanta el bot con todo lo necesario.

3.3 addKeyword: activar un flujo por palabra clave

Qué hace: dispara el flujo cuando el usuario escribe la palabra/frase clave.

API básica: addKeyword('texto' | ['t1','t2',...])

const flowSaludo = addKeyword(["hola", "alo"])
.addAnswer("👋 ¡Hola! Bienvenido/a.")
.addAnswer("¿En qué puedo ayudarte hoy?");

Explicación:

  • Si el usuario escribe hola o alo, el flujo se activa.
  • Cada .addAnswer(...) envía un mensaje separado en orden.

3.4 addAnswer: responde con texto, medios y más

Qué hace: envía mensajes (texto, imágenes, PDF, botones, etc.) y permite opciones como delay o capture.

Ejemplo: envío de imagen desde URL pública

const flowImagen = addKeyword("imagen")
.addAnswer("📷 Te envío una imagen de ejemplo…", {
media: "https://i.imgur.com/0HpzsEm.png",
})
.addAnswer("¿Necesitas otro recurso (PDF/Video)?");

Explicación:

  • media: 'URL' adjunta un archivo remoto (imagen, PDF, audio, etc.).
  • Asegúrate de que la URL sea pública y accesible.

3.5 Ejemplo con retardo y botones (menú rápido)

🔔 Nota: Los botones funcionan mejor en proveedores oficiales (Meta/Twilio). En proveedores web gratuitos pueden variar.

const flowMenu = addKeyword(["menu", "opciones"])
.addAnswer("Un momento, cargando menú…", { delay: 1000 }) // 1s de espera
.addAnswer("Selecciona una opción:", {
buttons: [
{ body: "🛍️ Ver productos" },
{ body: "📦 Mis pedidos" },
{ body: "👤 Soporte" },
],
});

Explicación:

  • { delay: 1000 } envía el mensaje con 1 segundo de espera (mejor UX).
  • buttons: [...] muestra 3 opciones táctiles al usuario.

3.6 Captura de datos del usuario con capture: true

Qué hace: espera la siguiente respuesta del usuario y la entrega al handler para procesarla (validar, guardar en DB, etc.).

Ejemplo A — Capturar nombre del usuario

const flowNombre = addKeyword(["mi nombre", "nombre"]).addAnswer(
"¿Cómo te llamas?",
{ capture: true },
async (ctx, { flowDynamic }) => {
const nombre = (ctx.body || "").trim();
if (!nombre || nombre.length < 2) {
return flowDynamic(
"Nombre no válido. Intenta de nuevo con al menos 2 caracteres."
);
}
await flowDynamic(`¡Mucho gusto, *${nombre}*! 🙂`);
}
);

Explicación clave:

  • { capture: true } hace que este .addAnswer(...) espere la próxima entrada del usuario.
  • ctx.body es el texto que el usuario escribió.
  • Validamos longitud básica y respondemos dinámicamente.

Ejemplo B — Capturar y validar correo electrónico

const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const flowCorreo = addKeyword(["correo", "email"]).addAnswer(
"📧 Escribe tu correo electrónico:",
{ capture: true },
async (ctx, { flowDynamic }) => {
const email = (ctx.body || "").trim();
if (!EMAIL_REGEX.test(email)) {
return flowDynamic(
"Formato inválido. Ejemplo válido: usuario@dominio.com"
);
}
await flowDynamic(`Gracias. Guardaremos: *${email}*`);
}
);

Explicación clave:

  • Se usa una expresión regular simple para validar el correo.
  • Si es inválido, se corta con un mensaje de ayuda.
  • Si es válido, confirmamos y podrías guardar en DB aquí.

Ejemplo C — Capturar opción de un menú simple

const flowSeleccion = addKeyword(["selección", "elige"]).addAnswer(
"Elige una opción (1-3):\n1) Productos\n2) Pedidos\n3) Soporte",
{ capture: true },
async (ctx, { flowDynamic }) => {
const opcion = (ctx.body || "").trim();
switch (opcion) {
case "1":
return flowDynamic("Has elegido *Productos* 🛍️");
case "2":
return flowDynamic("Has elegido *Pedidos* 📦");
case "3":
return flowDynamic("Has elegido *Soporte* 👤");
default:
return flowDynamic("Opción inválida. Responde con 1, 2 o 3.");
}
}
);

Explicación clave:

  • Se guía al usuario con una instrucción clara y se evalúa su respuesta.
  • Buena base para integrar gotoFlow a subflujos específicos.

3.7 Glue Code: integrar todos los flujos y correr

const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require("@bot-whatsapp/bot");
const { BaileysProvider } = require("@bot-whatsapp/provider-baileys");
const { JsonFileAdapter } = require("@bot-whatsapp/database-json");

// --- Flujos básicos ---
const flowSaludo = addKeyword(["hola", "alo"])
.addAnswer("👋 ¡Hola! Bienvenido/a.")
.addAnswer("Escribe *menu* para opciones o *imagen* para un ejemplo.");

const flowImagen = addKeyword("imagen").addAnswer(
"📷 Te envío una imagen de ejemplo…",
{
media: "https://i.imgur.com/0HpzsEm.png",
}
);

const flowMenu = addKeyword(["menu", "opciones"])
.addAnswer("Un momento, cargando menú…", { delay: 1000 })
.addAnswer("Selecciona una opción:", {
buttons: [
{ body: "🛍️ Ver productos" },
{ body: "📦 Mis pedidos" },
{ body: "👤 Soporte" },
],
});

// --- Flujos con captura ---
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const flowNombre = addKeyword(["mi nombre", "nombre"]).addAnswer(
"¿Cómo te llamas?",
{ capture: true },
async (ctx, { flowDynamic }) => {
const nombre = (ctx.body || "").trim();
if (!nombre || nombre.length < 2) {
return flowDynamic(
"Nombre no válido. Intenta de nuevo con al menos 2 caracteres."
);
}
await flowDynamic(`¡Mucho gusto, *${nombre}*! 🙂`);
}
);

const flowCorreo = addKeyword(["correo", "email"]).addAnswer(
"📧 Escribe tu correo electrónico:",
{ capture: true },
async (ctx, { flowDynamic }) => {
const email = (ctx.body || "").trim();
if (!EMAIL_REGEX.test(email)) {
return flowDynamic(
"Formato inválido. Ejemplo válido: usuario@dominio.com"
);
}
await flowDynamic(`Gracias. Guardaremos: *${email}*`);
}
);

const flowSeleccion = addKeyword(["selección", "elige"]).addAnswer(
"Elige una opción (1-3):\n1) Productos\n2) Pedidos\n3) Soporte",
{ capture: true },
async (ctx, { flowDynamic }) => {
const opcion = (ctx.body || "").trim();
switch (opcion) {
case "1":
return flowDynamic("Has elegido *Productos* 🛍️");
case "2":
return flowDynamic("Has elegido *Pedidos* 📦");
case "3":
return flowDynamic("Has elegido *Soporte* 👤");
default:
return flowDynamic("Opción inválida. Responde con 1, 2 o 3.");
}
}
);

// --- Bootstrap del bot ---
const main = async () => {
const adapterDB = new JsonFileAdapter();
const adapterFlow = createFlow([
flowSaludo,
flowImagen,
flowMenu,
flowNombre,
flowCorreo,
flowSeleccion,
]);
const adapterProvider = createProvider(BaileysProvider);

await createBot({
flow: adapterFlow,
provider: adapterProvider,
database: adapterDB,
});
};

main();

Explicación:

  • Registramos todos los flujos en createFlow([...]).
  • Puedes separar cada flujo en su propio archivo si el proyecto crece.
  • A partir de aquí, es sencillo añadir gotoFlow, state, flowDynamic con API/DB, etc.

3.8 Checklist para evitar errores comunes

  • ✅ Importa desde paquetes correctos (@bot-whatsapp/bot, @bot-whatsapp/provider-*, @bot-whatsapp/database-*).
  • ✅ Declara cada flujo antes de pasarlo a createFlow([...]).
  • ✅ Usa arrays si necesitas varias palabras clave ['hola', 'alo'].
  • ✅ En media, usa URLs públicas o rutas absolutas válidas.
  • ✅ Si usas botones, prueba en un proveedor compatible (Meta/Twilio).
  • ✅ En capturas, valida ctx.body y guía al usuario ante formatos inválidos.

✅ Resumen rápido

ConceptoPara qué sirve
addKeywordDispara el flujo con palabras/expresiones clave.
addAnswerEnvía mensajes (texto/medios), soporta delay/capture.
capture: trueEspera la siguiente respuesta del usuario.
flowDynamicRespuestas dinámicas (basadas en lo que envía el user).
createFlowRegistra y une todos los flujos en el bot.

¿Listo para el siguiente paso? En el Módulo 4 añadiremos addAction, ctx, state y gotoFlow para construir menús encadenados y flujos con validaciones.