Skip to main content
Nivel: Intermedio · Etiquetas: WhatsApp, Formularios, Persistencia, SQLite Funcionalidades: flujos persistentes · validación y maxAttempts · formularios · menús con choose · store SQLite · datos de usuario (ctx.user) · comandos slash

Requisitos

  • Node.js 22.12 o superior y una carpeta vacía con "type": "module" en su package.json (Inicio rápido).
better-sqlite3 es un módulo nativo: con pnpm, aprueba su script de build (Instalar better-sqlite3).

El bot completo

Cómo funciona

  1. El store. SQLiteStore guarda chats, checkpoints de flujos y datos de usuario en citas.db. Sin él, el bot usa un store en memoria y lo olvida todo al reiniciar.
  2. Un flujo persistente. .persistent() guarda un checkpoint al empezar el flujo y después de cada paso. Tras un reinicio, el siguiente mensaje del usuario despierta el flujo y se repite la pregunta pendiente: ese mensaje no cuenta como respuesta. .timeout('10m') cierra una reserva que nadie termina.
  3. Validación. validate devuelve true o el texto que se envía antes de volver a preguntar. maxAttempts: 3 cuenta todas las respuestas, también la primera; tras la tercera respuesta inválida la conversación termina con el mensaje integrado de “demasiados intentos”.
  4. Un formulario. .form() recoge campos tipados: date acepta AAAA-MM-DD y DD/MM/AAAA y comprueba min, y select muestra un menú numerado y guarda la opción elegida. flatten: true copia cada respuesta a c.state.day y c.state.time. En la Cloud API de WhatsApp el mismo formulario puede salir como un único WhatsApp Flow nativo; en el resto de canales se pregunta campo a campo. Un reinicio a mitad del formulario lo vuelve a preguntar desde el primer campo, porque el formulario es un solo paso.
  5. Confirmación. .choose() guarda la clave elegida (yes o no). En el paso .do(), c.end(texto) detiene el flujo con un último mensaje, así que no se guarda nada si el usuario dice que no.
  6. Guardar la cita. En los pasos de un flujo ctx es un mensaje de runtime sin helpers, así que userOf(ctx) da el mismo perfil que los handlers leen como ctx.user. mi cita lo lee, también después de un reinicio.
min: today() se calcula al arrancar el bot. Si el bot corre durante días, vuelve a comprobar la fecha en un paso .do() o reinícialo cada día.

Pruébalo

:reset en el simulador construye un bot nuevo desde la factory, que es lo que hace un reinicio: el nuevo SQLiteStore abre el mismo archivo y el flujo se retoma.

Ejecútalo en WhatsApp o Telegram

node bot.js muestra un código QR: escanéalo desde WhatsApp → Dispositivos vinculados. Para Telegram, instala @botmesh/telegram y cambia el provider del bloque de arranque por new TelegramProvider({ token: process.env.TELEGRAM_TOKEN ?? '' }), como en O conectar Telegram.

Siguientes pasos