You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
24 KiB
24 KiB
Task WhatsApp Chatbot
A WhatsApp chatbot for task management, designed to work with Evolution API in a secure internal network environment.
📌 Overview
This service provides a WhatsApp interface for task management within WhatsApp groups. It:
- Listens for
/tareacommands in WhatsApp groups via Evolution API webhooks. - Stores tasks, users, and groups in a SQLite database.
- Synchronizes group information periodically from the Evolution API.
- Manages user permissions and group membership (partially implemented).
- Integrates with Evolution API for WhatsApp connectivity.
- Sends direct messages to acknowledge the creator and notify each assignee; includes mentions with phone numbers for quick action.
- DM-only responses: the bot does not post messages in groups; all outputs are sent via DM to the author (except the optional creation summary below).
- Advanced listings: "/t ver" supports scopes "grupo", "mis", "sin" and "todos" with pagination ("… y X más").
- Complete tasks: "/t x " (aliases: hecho, completar, done) with compact DM feedback.
- Claim/unassign tasks: "/t tomar " and "/t soltar " with compact DM feedback.
- Natural date parsing: supports "hoy"/"mañana" with TZ (env TZ; default Europe/Madrid).
- Unified date display: all dates are rendered as dd/MM; help available via DM with "/t" or "ayuda".
- Optional: group notification on task creation controlled by NOTIFY_GROUP_ON_CREATE (default false), including proper mentions for visibility.
🔐 Security Model
- Internal Networking: The webhook should ideally only accept connections from Evolution API via internal Docker networking (configuration dependent).
- Environment Variables: Sensitive configuration (API keys, URLs) is managed through environment variables.
- Group Restrictions: Designed to operate within pre-approved WhatsApp groups (validation logic pending integration).
- Input Validation: Basic validation exists for webhook structure; needs enhancement for command arguments and user/group IDs.
🧱 Architecture
graph TD
A[Webhook Received] --> B{Valid Payload?}
B -->|No| C[Ignore]
B -->|Yes| D{Normalize IDs & Check Group Active?}
D -->|No| C[Ignore/Log]
D -->|Yes| E[Ensure User Exists in DB]
E --> G{/tarea Command?}
G -->|No| C
G -->|Yes| J[Process Command Logic]
J -- Success/Error --> K[Queue Response(s)]
K --> L[Process Queue & Send Response via API]
subgraph Database Interaction
E --> DB[(SQLite DB)]
J --> DB
end
subgraph Evolution API
L --> EA((Evolution API))
EA -- Webhook --> A
end
(Diagram updated for planned flow)
Decisiones de diseño de la cola de respuestas (estado actual)
- Persistencia: 100% persistente (tabla única).
- Worker: continuo en background.
- Locking: status=processing (sin lease).
- Orden: sin orden estricto por chat.
- Reintentos: activados con backoff exponencial + jitter (configurables).
- Errores: 4xx = fallo definitivo; 5xx/red = reintento hasta RQ_MAX_ATTEMPTS con
next_attempt_at. - Idempotencia: no.
- Esquema DB: tabla única response_queue.
- Transporte: envío desde ResponseQueue (fetch directo a Evolution API).
- Concurrencia: N workers globales.
- Integración: encolar y worker continuo (arranca con el servidor).
- Configuración: defaults fijos (sin nuevas env vars por ahora).
- Limpieza: retención periódica de historiales aplicada (sent y failed antiguos).
- Seguridad: no enviar al número del bot (CHATBOT_PHONE_NUMBER).
- Pruebas: unitarias de cola con mocks de fetch.
Actualización Phase 4 — Etapa 1 (Completada):
- Reintentos activados con backoff exponencial + jitter.
- Nuevos campos:
next_attempt_at,last_status_code. - Selección de pendientes filtrando por
(status='queued' AND next_attempt_at <= now). - Config por entorno:
RQ_MAX_ATTEMPTS,RQ_BASE_BACKOFF_MS,RQ_MAX_BACKOFF_MS.
Arquitectura de la cola persistente (MVP)
- Estados: queued | processing | sent | failed.
- Campos actuales por mensaje: id (PK), recipient, message, status, attempts (0), last_error (nullable), metadata (nullable), created_at, updated_at.
- Índices recomendados: (status, created_at) para seleccionar pendientes rápidamente.
- Sin orden estricto por chat; el envío puede intercalarse entre destinatarios.
- Concurrencia: N workers globales operando en bucle, cada uno toma mensajes en estado queued y los marca processing. Estado: la tabla response_queue ya está creada e incluida en los tests de DB.
Flujo del worker continuo (MVP)
- Se inicia al arrancar el servidor (desactivado en tests).
- Ciclo: seleccionar hasta un pequeño batch de mensajes queued, marcar processing, enviar a Evolution API, marcar sent o failed según respuesta.
- Sin reintentos; logs mínimos y no sensibles.
Limitaciones actuales
- Sin orden garantizado por chat (pendiente serialización por destinatario).
- Sin idempotencia (pendiente
idempotency_keye índice único). - Métricas/observabilidad básicas pendientes (endpoint /metrics).
- Sin garantía de orden cross-chat; fair scheduling pendiente.
Plan incremental posterior
- Añadir reintentos con backoff exponencial y jitter. (Completado)
- Garantizar orden por chat (serialización por recipient).
- Introducir lease (lease_until) para tolerancia a fallos y recuperación.
- Limpieza/retención y métricas/observabilidad.
- Opcional: idempotencia e índices adicionales.
✅ Current Status (as of latest commit)
Implemented
- Webhook server setup (
src/server.ts) receiving Evolution API events. - Corrección: extracción robusta de remitente en DMs vs grupos para evitar 'Invalid sender ID'.
- Sistema de migraciones up-only para SQLite (
src/db/migrator.ts,src/db/migrations/), ejecutado al arranque; backup automático con VACUUM INTO y baseline cuando existe esquema previo. - Group synchronization service (
src/services/group-sync.ts) to fetch/store/cache groups. Includes active group caching andisGroupActivechecks in server. - Webhook registration and verification with Evolution API (
src/services/webhook-manager.ts). - WhatsApp ID normalization utility (
src/utils/whatsapp.ts). - User creation/update function (
src/db.ts::ensureUserExists) integrated into the main flow. - Command handling for
/tarea nuevaend-to-end (src/services/command.ts):- Parses description and selects the last future date (YYYY-MM-DD) as due date.
- Extracts assignees from explicit mentions and plain-text @tokens; defaults to creator only when none found.
- Cleans description to remove @mentions tokens.
- Persists task and assignments atomically via
TaskService. - Builds response with assignment list and includes Evolution API “mentioned” JIDs via
ResponseQueue.
- Command handling for listings, completion and assignment (tomar/soltar):
/t verwith scopesgrupo,mis,sinandtodoswith top-N and “… y X más”; DM-only policy enforced./t x <id>to complete tasks (aliases:hecho,completar,done) with compact feedback.
- Task persistence service (
src/tasks/service.ts) withcreated_byand assignment inserts in a transaction; supports DB injection for tests. - Response queue persistente con workers en background y envío vía Evolution API (
src/services/response-queue.ts), persistiendo metadata{ mentioned: [...] }y enviándola comomentioneden el payload, con reintentos (backoff exponencial + jitter), recuperación deprocessingy limpieza/retención. - Contacts service and friendly names:
ContactsServiceresolves display names via webhooks (CONTACTS_UPDATE/CHATS_UPDATE) and Evolution API fallback; used to render names in outgoing texts (falls back to numbers). Skips network calls under NODE_ENV=test for fast and isolated unit tests. - Notification UX: Always send DM acknowledgment to the creator in a single line (format: ✅ Tarea creada: "description"), DM to each assignee (excluding the creator); optional group notification controlled by
NOTIFY_GROUP_ON_CREATE(default false) with proper mentions. - Natural date tokens support (“hoy”/“mañana”) with TZ configurable (env TZ; default Europe/Madrid) in task creation.
- Unified dd/MM date formatting across outgoing texts.
- Help by DM for "/t" and "/t ayuda" with a concise guide and examples.
- Environment variable validation (
src/server.ts,src/services/webhook-manager.ts). - Health check endpoint (
/health). - Rate limiting per user: 15 commands/min (configurable via RATE_LIMIT_PER_MIN and RATE_LIMIT_BURST); disabled under NODE_ENV=test; polite DM notice throttled to 1/min.
- Database isolation in unit tests: Using in-memory instances to avoid conflicts.
Incomplete / Missing Core Functionality
- ResponseQueue reliability: reintentos con backoff, recuperación de
processing, métricas y limpieza/retención. - ContactsService improvements (optional): refine caching policy and endpoints; basic friendly-name resolution is already implemented and used in outgoing texts.
- More robust error handling and observability around API/DB operations.
Known Issues
- Mentions UX: In group chats, mentions are highlighted and each client resolves the chip to their local contact name; we include friendly names in text when available. In direct messages, WhatsApp does not render mention chips for third parties, so we include the number as @digits for quick action; no per-recipient name rewriting occurs.
- Test suite: all unit tests passing.
🛠️ Setup
Environment Variables
(Ensure these are set correctly)
# Evolution API Connection
EVOLUTION_API_URL=http://evolution-api:3000 # Or your API URL
EVOLUTION_API_KEY=your-api-key
EVOLUTION_API_INSTANCE=main # Your instance name
# WhatsApp Specific
WHATSAPP_COMMUNITY_ID=your-community-id # ID of the main community to sync groups from
CHATBOT_PHONE_NUMBER=1234567890 # Bot's normalized phone number (e.g., for assigning tasks)
# Webhook Configuration
WEBHOOK_URL=http://your-service-internal-url:3007 # URL Evolution API calls *back* to this service
PORT=3007 # Port this service listens on
# Runtime Environment
NODE_ENV=production # Or development
TZ=Europe/Madrid # Default timezone used for "hoy/mañana" parsing and date displays
# Optional
# GROUP_SYNC_INTERVAL_MS=3600000 # Sync interval in ms (default: 24h)
# NOTIFY_GROUP_ON_CREATE=false # If 'true', also post a brief summary with mentions to the group
# RATE_LIMIT_PER_MIN=15 # Commands per minute per user (token refill rate)
# RATE_LIMIT_BURST=15 # Bucket capacity (defaults to RATE_LIMIT_PER_MIN)
Development Setup
# Install dependencies
bun install
# Copy .env.example to .env and fill in values
cp .env.example .env
# Start development server (watches for changes)
bun run dev
# Run tests
bun test
📅 Roadmap & Priorities (Updated Plan)
MVP — Próximos pasos (prioridad actual)
- Recordatorios diarios por DM: preferencia por usuario (daily|off, por defecto off), hora fija local (p. ej., 08:30 TZ), un DM con resumen compacto de “tus tareas”.
- Refinar ContactsService: mejorar heurística de nombre, invalidación temprana en CHATS_UPDATE/CONTACTS_UPDATE, TTL configurable, robustez ante fallos, sin llamadas de red en tests.
- Sincronización mínima de miembros: obtener y cachear miembros de grupos activos con TTL, uso no bloqueante; base para futuras validaciones/UX.
- Ampliar test suite: cubrir recordatorios, actualización de nombres, sync de miembros y casos extremos de ResponseQueue.
Phase 1: User & Group Foundation (Highest Priority - In Progress)
- Create WhatsApp ID Normalization Utility: (
src/utils/whatsapp.ts) Handle different ID formats. - Implement
ensureUserExists: (src/db.ts) Add users to DB on first interaction. - Implement
isGroupActiveCheck: (src/services/group-sync.ts,src/server.ts) Cache exists ingroup-sync.ts. Needs integration intosrc/server.ts. - Integrate Validation in Server: (
src/server.ts) Use normalization,ensureUserExists, and active group check before processing commands. - Implement DB isolation in tests: (
tests/unit/services/group-sync.test.ts) Use in-memory instances to avoid conflicts.
Phase 2: Refinar /tarea nueva (Alta prioridad)
- Completar
TaskService.createTaskconcreated_byy asignaciones (incluyendoensureUserExistspara asignados) y transacción atómica. - Mejorar
CommandServicepara parsear menciones y devolver el id de la tarea creada y resumen de asignados.
Phase 3: Comandos adicionales y refinamientos (Alta prioridad)
- Implementar
/tarea mostrar [group|mine]para listar pendientes. - Implementar
/tarea completar <task_id>con validaciones básicas. - Soportar mensajes de texto extendido y captions de media (además de conversation).
- Implementar tomar/soltar (claim/unassign) con feedback por DM.
Phase 4: Fiabilidad de la cola (Media; observabilidad pospuesta post-MVP)
- Añadir reintentos con backoff exponencial y jitter.
- Recuperar ítems en estado
processingtras reinicios (lease o expiración y requeue). - Limpieza/retención de historiales.
Phase 4 — Desglose y estado
- Etapa 1 — Reintentos con backoff exponencial + jitter (Completada)
- Parámetros: RQ_MAX_ATTEMPTS (6), RQ_BASE_BACKOFF_MS (5000), RQ_MAX_BACKOFF_MS (3600000).
- Lógica: 2xx → sent; 4xx → failed definitivo; 5xx/red → reintento con
next_attempt_athasta MAX_ATTEMPTS.
- Etapa 2 — Recuperación de items en
processingmediante lease/expiración (Completada) - Etapa 3 — Métricas y observabilidad (Pospuesta post-MVP)
- Etapa 4 — Limpieza/retención (Completada)
Phase 5: Advanced Features (Low Priority)
- Add task reminders system.
- Implement user permissions system.
- Add rate limiting.
- Create task history tracking.
Etapas siguientes (priorización propuesta)
- Phase 4 — Etapa 4: Limpieza y retención
- Objetivo: evitar crecimiento indefinido y mantener la BD sana.
- Implica: retención/archivado de response_queue, posible VACUUM, config RQ_RETENTION_DAYS y job periódico.
- Fiabilidad avanzada de ResponseQueue
- Objetivo: mejorar entrega y evitar duplicados/ruido.
- Implica: orden por destinatario, idempotency_key con índice único, límites de inflight/backpressure.
- UX Iteración A (MVP centrado en valor)
- Objetivo: fluidez de uso y silencio en grupos.
- Implica: alias y sinónimos, “solo DM”, soporte “hoy/mañana”, comandos tomar/soltar, ayuda breve.
- UX Iteración B (listas y completar)
- Objetivo: visibilidad y cierre rápido.
- Implica: “ver grupo” y “ver mis” con tope y “y X más…”, completar (x/hecho/done) robusto, formatos compactos.
- Recordatorios (Phase 5)
- Objetivo: nudge suave por DM.
- Implica: tabla user_preferences, comando “configurar”, job diario de resumen.
- Permisos y pertenencia a grupos
- Objetivo: control de quién puede qué, y pertenencia válida.
- Implica: roles y/o verificación de pertenencia; posibles migraciones y sincronización de miembros.
- Historial de tareas (auditoría ligera)
- Objetivo: trazabilidad de cambios.
- Implica: tabla task_events; eventos en crear/asignar/tomar/soltar/completar; consulta “historial”.
- Rate limiting (operación segura)
- Objetivo: proteger ante abuso o loops.
- Implica: token bucket por usuario/grupo; configuración de límites.
- Sincronización de miembros (opcional)
- Objetivo: conocer miembros activos por grupo para features avanzadas.
- Implica: endpoints Evolution API para miembros; cache/migraciones.
- Métricas y observabilidad (deferido del MVP)
- Objetivo: visibilidad con bajo coste.
- Implica: counters/gauges/histograms y endpoint /metrics; logging estructurado. Se pospone al final de esta lista.
🔑 Key Considerations & Caveats
- WhatsApp ID Normalization: Crucial for consistently identifying users and groups. Needs careful implementation to handle edge cases. (Utility function exists).
- Response Latency: Sending responses requires an API call back to Evolution. Ensure the
ResponseQueueprocessing is efficient. - Cola de respuestas: Persistente en DB; pendiente añadir reintentos/backoff y limpieza/retención.
- Group Sync: The current full sync might be slow or rate-limited with many groups. Delta updates are recommended long-term.
- Error Handling: Failures in command processing or response sending should be logged clearly and potentially reported back to the user. Database operations should use transactions for atomicity (especially task+assignment creation).
- State Management: The current design is stateless. Complex interactions might require state persistence later.
- Security: Ensure group/user validation logic is robust once integrated.
🧪 Testing
Running Tests
bun test
Test Coverage
- Database initialization and basic operations (
db.test.ts). - Webhook validation (basic) (
webhook-manager.test.ts). - Command parsing (basic structure) (
command.test.ts). - Environment checks (
server.test.ts). - Basic error handling (
server.test.ts). - WhatsApp ID normalization (
whatsapp.test.ts). - Group sync operations (
group-sync.test.ts). - Needed: Tests for
ensureUserExistsintegration,isGroupActiveintegration,CommandServicelogic,ResponseQueueprocessing (mocking API),TaskServiceoperations. - All unit tests passing. Added unit tests for CommandService (date parsing "hoy/mañana", DM help, dd/MM formatting, default assignment rules).
🧑💻 Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/implement-user-validation) - Add/update tests for new functionality
- Ensure tests pass (
bun test) - Submit a pull request
📚 Documentation
For detailed API documentation and architecture decisions, see the docs/ directory (if created).
📐 Diseño UX acordado (MVP y siguientes iteraciones)
Este apartado documenta las decisiones de UX aprobadas para el MVP y su evolución inmediata. Objetivo: mínima fricción, cero ruido en grupos y mensajes compactos.
Principios
- Silencio en grupos: el bot NO publica mensajes en grupos. Cuando alguien usa un comando en un grupo, el bot responde solo por DM al autor, sin dejar mensaje en el grupo.
- Homogeneidad: mismos comandos y comportamiento tanto en la comunidad “Casa” como en la del AMPA.
- Mensajes compactos: máximo 2–3 líneas, usando emojis y formato WhatsApp (negritas, monoespacio) para legibilidad.
- Aprendizaje progresivo: alias cortos y ayuda accesible por DM.
Comando base y alias
- Prefijo admitido: “/t” y “/tarea”.
- Subcomandos y sinónimos (aceptar cualquiera, mapear a una acción canónica):
- Crear: n, nueva, crear, +
- Ver: ver, mostrar, listar, ls
- Completar: x, hecho, completar, done
- Tomar: tomar, claim
- Soltar: soltar, unassign
- Ayuda: ayuda, help, ?
- Configurar: configurar, config
Gramática de “crear tarea”
- Texto libre: descripción.
- Fecha: soportar tokens “hoy” y “mañana” (MVP). Futuro: +2d, +1w, lun/mar/…
- Menciones: “@…” y menciones reales del cliente.
- Asignación por defecto:
- En grupos: si no hay menciones → tarea queda “sin dueño”.
- En DM: si no hay menciones → asignada al creador.
- Comandos de gestión de asignación:
- /t tomar → el usuario se asigna la tarea.
- /t soltar → elimina su asignación, devolviendo la tarea a “sin dueño” si no quedan asignados.
Listados
- /t ver grupo → devuelve por DM las pendientes del grupo desde el que se invoca (incluye sección “sin dueño”).
- /t ver mis → devuelve por DM las pendientes del usuario agregadas de todos sus grupos.
- Listas extensas: mostrar top N (p. ej., 10) y resumen “y X más…”.
Completar
- /t x (alias: /t hecho , /t completar )
- Registro de quién completó. Por ahora no se restringe a asignados (permite fluidez); política configurable en el futuro.
- Confirmación solo por DM.
Ayuda y onboarding
- “/t” sin parámetros o “ayuda” → siempre por DM, con guía corta y 2–3 ejemplos.
- En grupos: no se escribe nada en el grupo; únicamente el DM al autor.
Mensajes: plantillas compactas
- Confirmación al crear (DM al creador):
- ✅ 26 “Acta de la reunión”
- 📅 12/09
- 👥 sin dueño (Junta AMPA) — o — 👤 @Juan
- DM a asignados:
- 🔔 Tarea 26 — 📅 12/09
- “Acta de la reunión”
- Grupo: Junta AMPA
- Completar: /t x 26
- Listado (enviado por DM):
- Junta AMPA
-
- “Acta…” — 📅 12/09 — 👤 @Juan
-
- “Carteles fiesta” — 📅 10/09 — 👥 sin dueño
- … y 3 más
-
- Junta AMPA
- Completar (feedback por DM):
- ✔️ 26 completada — “Acta…”
- Gracias, Juan.
Preferencias (MVP)
- Única preferencia: frecuencia de recordatorios por DM: daily | off.
- MVP sin web: el usuario escribe “configurar” por DM y el bot le ofrece elegir “diario” u “off”.
- Por defecto: off (evitar spam). Futuro: hora y zona horaria configurables; magic link a web de configuración.
Recordatorios
- Resumen diario por DM (si el usuario eligió “diario”):
- Buenos días, Ana — hoy tienes 3 tareas:
-
- “Acta…” — 12/09 — Junta AMPA
-
- “Pagar comedor” — hoy — Casa
-
- “…” — 15/09 — Casa
-
- Completar: /t x
- Buenos días, Ana — hoy tienes 3 tareas:
- Un solo DM con secciones por comunidad para evitar múltiples mensajes.
No objetivos del MVP
- No asignar por defecto a “todo el grupo” (evita DMs masivos y responsabilidad difusa).
- No canal “Tareas” compartido por defecto (riesgo de ruido). Se considerará en el futuro solo si hay demanda y opt‑in.
Plan de implementación (iteraciones)
- Iteración A — UX base y silencios
- Alias de comandos y sinónimos en CommandService.
- Respuestas de todos los comandos únicamente por DM (incluido cuando se invocan en grupos).
- Mensajes compactos con plantillas.
- Soporte de “hoy/mañana”.
- Default sin dueño en grupos; asignar al creador en DMs.
- Nuevos comandos: tomar y soltar.
- Ayuda por DM.
- Iteración B — Listados y completar
- /t ver grupo, /t ver mis.
- /t x con registro de quién completa.
- Tests para alias, hoy/mañana, ver y x.
- Iteración C — Recordatorios
- Preferencia reminder_freq (daily|off) por usuario via “configurar” por DM.
- Job diario que envía el resumen (solo “tus tareas” en MVP).
- Iteración D — (Opcional) Miembros de grupo
- Sincronizar miembros si se necesita incluir “sin dueño” por grupo en recordatorios.
Cambios técnicos asociados (resumen)
- src/services/command.ts
- Mapeo de sinónimos a acciones canónicas.
- Parser de “hoy/mañana”.
- Subcomandos: ver grupo|mis, x, tomar, soltar.
- Render de mensajes compactos.
- src/server.ts
- Detección de contexto grupo vs DM; nunca responder en grupo (solo DM al autor).
- src/tasks/service.ts
- Permitir tareas sin asignaciones.
- Métodos: claimTask(user_id), unassignTask(user_id), completeTask(id, completed_by).
- src/services/response-queue.ts
- Envío de DMs para ayuda, confirmaciones, listados y recordatorios.
- (Futuro) Preferencias
- Tabla user_preferences(user_id PK, reminder_freq TEXT, updated_at).
- “configurar” por DM en MVP; más adelante, web con magic link (user_tokens).
Testing sugerido
- Alias de comandos y “/t” sin parámetros → DM de ayuda.
- Crear en grupo sin menciones → sin dueño; no hay mensaje en el grupo; DM al autor.
- Crear en DM sin menciones → asignada al creador.
- “hoy/mañana” en fechas.
- ver grupo y ver mis → DM con paginación/resumen.
- completar, tomar y soltar → reglas y feedback por DM.