Skip to main content
Nivel: Avanzado · Etiquetas: Producción, Docker, SQLite, Durabilidad, WhatsApp Funcionalidades: Store persistente (SQLiteStore, maxMessagesPerChat) · Procesamiento durable (store.durability()) · Flujos persistentes (.persistent(), .timeout) · Apagado ordenado (bot.stop({ drain })) · Comprobaciones de salud y métricas (toPrometheus, bot.metrics()) · Logging (pino, logIds) · Eventos de operación · Pasar a producción

Requisitos previos

Node.js 22.12 o superior, como en Inicio rápido. Docker es opcional.

Estructura del proyecto

El bot completo

Los archivos del proyecto

package.json (npm install añade las dependencies):

Cómo funciona

1. Un archivo SQLite para todo el estado

SQLiteStore guarda mensajes, chats, contactos, datos de usuario y checkpoints de flujos. Su carpeta debe existir (SQLite crea el archivo, no las carpetas), de ahí el mkdirSync. maxMessagesPerChat acota el log de mensajes, que en los stores persistentes no tiene límite por defecto.

2. Durabilidad

Con durability, cada mensaje admitido se anota en un journal de entrada antes de que corra ningún handler, y cada respuesta en un outbox con una clave de idempotencia antes de llamar al provider. Si el proceso muere a mitad, el siguiente arranque reprocesa el mensaje y se salta las respuestas que ya salieron. store.durability() guarda los dos journals en el mismo archivo. Mantén instanceId estable entre reinicios de un mismo despliegue.

3. Logs sin datos personales

El bot escribe sus logs con el logger de pino que le pasas. Los ids de chat y de remitente salen hasheados en las líneas del propio bot (logIds: 'hash', el valor por defecto). Tus líneas deberían seguir la misma regla: el paso 5 registra el nombre del flujo, el resultado y la duración, nunca el texto ni el teléfono. LOG_LEVEL=debug muestra más mientras depuras.

4. Flujos persistentes

.persistent() guarda la posición del flujo y c.state en el store después de cada paso, así que quien deja ticket a medias continúa tras un despliegue. La transcripción de abajo reinicia el bot en mitad del flujo.

5. Eventos para operación

'conversation:ended' informa de cada flujo con su resultado (completed, cancelled, timedOut…). Un flujo .persistent() cortado por un reinicio informa cancelled y después continúa desde el store, así que no alertes solo por cancelled. 'error' recibe los fallos de los handlers; envíalos a tu herramienta de errores. Mira Operación para el resto.

6. Salud y métricas

/healthz responde mientras el proceso sirve (liveness: reinicia el contenedor cuando falla). /readyz responde 200 solo mientras el bot está conectado y no se está parando (readiness). /metrics es bot.metrics() en formato de texto de Prometheus. Mantén el puerto en privado: el compose lo enlaza a 127.0.0.1.

7. Apagado ordenado

Con SIGTERM (Docker, Kubernetes) o SIGINT (Ctrl+C, PM2), bot.stop({ drain: true, timeout: '15s' }) espera las respuestas en curso, las registra y cierra el store; después se cierra el servidor de salud y nada mantiene vivo el proceso. Una segunda señal espera al mismo apagado. Deja el periodo de gracia del orquestador (stop_grace_period) por encima del timeout del drenado.

8. Un archivo, tres formas de ejecutarlo

npx botmesh dev src/bot.js importa solo la fábrica e inyecta el simulador de la terminal, así que pruebas el mismo bot (con el mismo archivo SQLite) sin teléfono. node src/bot.js y el contenedor ejecutan el bloque isMainModule.

Transcripción de ejemplo

Capturada con LOG_LEVEL=silent npx botmesh dev src/bot.js; con el nivel info por defecto, las líneas de log conversation ended del paso 5 salen entre las respuestas. :reset arranca un bot nuevo, como haría un reinicio o un despliegue, y el flujo continúa desde el store:

Ejecutarlo en WhatsApp

En local: npm start, escanea el QR con el teléfono que hará de bot y pulsa Ctrl+C. Con Docker, crea antes las carpetas de los volúmenes (de tu usuario, uid 1000 como el usuario node de la imagen) y sigue los logs para escanear el QR la primera vez:
Para Telegram, instala @botmesh/telegram y construye el bot con new TelegramProvider({ token: process.env.TELEGRAM_TOKEN }) en su lugar.

Siguientes pasos