From ce223a29559d94085923bdab6a76e3d32020de0c Mon Sep 17 00:00:00 2001 From: brobert Date: Sun, 28 Sep 2025 22:07:56 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20crear=20ADRs=20y=20gu=C3=ADa=20de=20arq?= =?UTF-8?q?uitectura=20y=20migraciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aider (openrouter/openai/gpt-5) --- docs/README.md | 23 +++++++++++++ docs/adr/0001-wal.md | 19 +++++++++++ docs/adr/0002-up-only-migrations.md | 20 ++++++++++++ docs/adr/README.md | 14 ++++++++ docs/architecture.md | 50 +++++++++++++++++++++++++++++ docs/database.md | 46 ++++++++++++++++++++++++++ docs/how-to/adding-command.md | 35 ++++++++++++++++++++ docs/how-to/adding-env.md | 22 +++++++++++++ docs/how-to/adding-migration.md | 28 ++++++++++++++++ docs/how-to/adjusting-group-sync.md | 24 ++++++++++++++ docs/operations.md | 49 ++++++++++++++++++++++++++++ docs/overview.md | 42 ++++++++++++++++++++++++ docs/tests-map.md | 32 ++++++++++++++++++ 13 files changed, 404 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/adr/0001-wal.md create mode 100644 docs/adr/0002-up-only-migrations.md create mode 100644 docs/adr/README.md create mode 100644 docs/architecture.md create mode 100644 docs/database.md create mode 100644 docs/how-to/adding-command.md create mode 100644 docs/how-to/adding-env.md create mode 100644 docs/how-to/adding-migration.md create mode 100644 docs/how-to/adjusting-group-sync.md create mode 100644 docs/operations.md create mode 100644 docs/overview.md create mode 100644 docs/tests-map.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..19e52b6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,23 @@ +# Documentación del Proyecto + +Este directorio contiene documentación técnica viva, orientada a: +- Entender la arquitectura y los flujos principales. +- Operar el sistema (variables de entorno, métricas, schedulers). +- Mapear la cobertura de tests. +- Guías prácticas (how-to) para extender funcionalidades con seguridad. +- Decisiones de arquitectura (ADRs) y su motivación. + +Índice recomendado: +- overview.md — visión general y flujos clave. +- architecture.md — responsabilidades por módulo y dependencias. +- database.md — esquema funcional, PRAGMAs y migraciones. +- operations.md — operación y configuración (env, métricas, schedulers). +- tests-map.md — qué cubre cada suite y huecos de cobertura. +- how-to/ + - adding-command.md + - adding-migration.md + - adjusting-group-sync.md + - adding-env.md +- adr/ + - 0001-wal.md + - 0002-up-only-migrations.md diff --git a/docs/adr/0001-wal.md b/docs/adr/0001-wal.md new file mode 100644 index 0000000..f1928af --- /dev/null +++ b/docs/adr/0001-wal.md @@ -0,0 +1,19 @@ +# 0001 — Activar WAL en SQLite + +Estado +- Aceptada + +Contexto +- El servicio escribe y lee con frecuencia; necesitamos concurrencia razonable sin introducir un servidor externo. + +Decisión +- Activar `PRAGMA journal_mode = WAL`, `PRAGMA synchronous = NORMAL`, `PRAGMA busy_timeout = 5000` y `PRAGMA wal_autocheckpoint = 1000`. + +Consecuencias +- Mejora de concurrencia lectura/escritura. +- Posible crecimiento de archivos WAL hasta checkpoint; mitigado con autocheckpoint. +- Requiere revisar compatibilidad con entornos que no soporten WAL (SQLite puede devolver otro modo). + +Alternativas +- DELETE journal (peor concurrencia). +- Migrar a otro motor (más complejidad operativa). diff --git a/docs/adr/0002-up-only-migrations.md b/docs/adr/0002-up-only-migrations.md new file mode 100644 index 0000000..3300d17 --- /dev/null +++ b/docs/adr/0002-up-only-migrations.md @@ -0,0 +1,20 @@ +# 0002 — Migraciones up-only con checksums + +Estado +- Aceptada + +Contexto +- El repositorio usa SQLite embebido y despliegues sencillos; las revertencias complejas son raras pero el control de integridad del esquema es esencial. + +Decisión +- Mantener migraciones “up-only” registradas en `src/db/migrations/index.ts` con `checksum` estático y `version` incremental. +- Evitar `down`; para correcciones, introducir nuevas migraciones. + +Consecuencias +- Simplicidad operativa y de test. +- Reversiones requieren una migración correctiva. +- Necesidad de disciplina en pruebas de migraciones en memoria. + +Alternativas +- Framework de migraciones con up/down completo (más complejidad). +- Esquemas generados ad-hoc (menos trazabilidad). diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..15cec07 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,14 @@ +# ADRs (Architecture Decision Records) + +Propósito +- Registrar decisiones técnicas significativas, su contexto y consecuencias. +- Evitar debates cíclicos y documentar por qué se eligió una opción. + +Formato sugerido +- Título y número incremental. +- Estado: aceptada/propuesta/superseded. +- Contexto → Decisión → Consecuencias → Alternativas consideradas. + +ADRs existentes +- 0001-wal.md — Modo WAL en SQLite. +- 0002-up-only-migrations.md — Migraciones solo “up” con checksums. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..523d6d3 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,50 @@ +# Arquitectura y responsabilidades + +Módulos principales +- src/server.ts + - Expone endpoints (webhook, /metrics), valida entorno, lanza migraciones y arranque. + - Extrae texto de mensajes con lógica tolerante a formatos (texto/caption). +- src/db.ts + - Abre la base de datos (data/…), aplica PRAGMAs por defecto y expone helpers. + - initializeDatabase() ejecuta migraciones up-only. +- src/db/migrations/index.ts + - Registro de migraciones con versión, nombre, checksum y función up. + - Helpers para inspección del esquema (tableHasColumn). + +Servicios (src/services) +- command.ts: orquesta el manejo de comandos. Entrada: { sender, groupId, message, mentions }. +- contacts.ts: resolución de nombres de contactos vía Evolution API con caché en memoria y TTL. +- group-sync.ts: sincronización de grupos y miembros, cachés en memoria, schedulers configurables. +- identity.ts: gestión de alias ↔ user_id real; caché en memoria y tabla user_aliases. +- maintenance.ts: limpieza programada de miembros inactivos según retención. +- metrics.ts: contadores y gauges in-memory con render a Prometheus o JSON. +- rate-limit.ts: token bucket por usuario, con burst, refill y cooldown de notificación. +- reminders.ts: recordatorios según preferencias (zona horaria, frecuencia, hora). +- response-queue.ts: cola de respuestas con intentos, backoff y limpieza. +- webhook-manager.ts: configuración de webhooks salientes (si aplica). + +Utilidades (src/utils) +- whatsapp.ts: normalización de IDs, helpers para distinguir usuario/grupo. +- formatting.ts: utilidades de representación (IDs, fechas abreviadas, etc.). + +Tareas (src/tasks) +- model.ts: interfaces Task, TaskAssignment, etc. +- service.ts: operaciones de alto nivel sobre tareas (crear, asignar, soltar, etc.). + +Dependencias internas +- La mayoría de servicios aceptan/injectan una instancia Database estática configurable para test. +- utils no dependen de servicios; servicios pueden usar utils y db. +- server.ts invoca servicios y metrics; no debe contener lógica de dominio pesada. + +Puntos de extensión +- Nuevos comandos: ver how-to/adding-command.md. +- Nuevas tablas/campos: ver how-to/adding-migration.md. +- Ajustes de sincronización de grupos/miembros: ver how-to/adjusting-group-sync.md. +- Nuevas variables de entorno: ver how-to/adding-env.md. +- Nuevas métricas: usar Metrics.inc/set/get y documentar en operations.md. + +Observabilidad y errores +- Logging pragmático en operaciones críticas; errores de migración detienen el arranque. +- Métricas: + - sync_runs_total, contadores de alias resueltos, etc. + - Estados de gauges/counters expuestos en /metrics (Prom/JSON). diff --git a/docs/database.md b/docs/database.md new file mode 100644 index 0000000..1c51ae6 --- /dev/null +++ b/docs/database.md @@ -0,0 +1,46 @@ +# Base de datos, PRAGMAs y migraciones + +PRAGMAs por defecto (src/db.ts) +- busy_timeout = 5000 ms +- journal_mode = WAL (si no es soportado, SQLite ajusta) +- synchronous = NORMAL +- wal_autocheckpoint = 1000 +- foreign_keys = ON + +Formato de timestamps +- Persistencia como 'YYYY-MM-DD HH:MM:SS[.mmm]'. +- Uso de strftime('%Y-%m-%d %H:%M:%f', 'now') en inserciones/updates. + +Esquema funcional (resumen) +- users + - user_id (PK), created_at, updated_at. +- user_aliases + - alias (PK), user_id (FK → users), source, created_at, updated_at. +- groups + - id (PK, JID), name, active (boolean), last_verified (timestamp). +- group_members + - group_id (FK → groups), user_id (FK → users), is_admin (boolean), is_active (boolean), + first_seen_at, last_seen_at, last_role_change_at. +- user_preferences + - user_id (FK → users), reminder_freq ('daily'|'weekly'|'weekdays'|'off'), + reminder_time ('HH:MM' | null), last_reminded_on ('YYYY-MM-DD' | null), updated_at. +- tasks + - id (PK autoinc), description, due_date ('YYYY-MM-DD' | null), group_id (JID | null), + created_by (user_id), created_at. +- task_assignments + - task_id (FK → tasks), user_id (FK → users), assigned_by (user_id), assigned_at. +- response_queue + - id (PK), recipient (user_id/JID), message (texto), metadata (JSON | null), + attempts (int), available_at (timestamp), created_at (timestamp). + +Nota: El detalle exacto está en las migraciones (src/db/migrations). Usa tableHasColumn para detectar columnas en migraciones idempotentes. + +Migraciones +- Política up-only con checksum estático por migración. +- Migrator.migrateToLatest(instancia, opciones) se invoca en initializeDatabase() y en server.start(). +- Tests utilizan Database(':memory:') y initializeDatabase(memdb) para asegurar el esquema. + +Integridad y rendimiento +- foreign_keys = ON asegura relaciones consistentes. +- WAL mejora concurrencia de lectura/escritura. +- busy_timeout y synchronous=NORMAL balancean seguridad y rendimiento. diff --git a/docs/how-to/adding-command.md b/docs/how-to/adding-command.md new file mode 100644 index 0000000..7c69b32 --- /dev/null +++ b/docs/how-to/adding-command.md @@ -0,0 +1,35 @@ +# Cómo añadir un nuevo comando + +Objetivo +- Incorporar un comando de texto que procese mensajes entrantes y responda por la cola. + +Pasos +1) Parseo en CommandService + - Ubicación: src/services/command.ts + - Añade lógica para detectar el patrón (p.ej., "/t listar", "/t info 0012"). + - Normaliza IDs con utils/whatsapp.normalizeWhatsAppId cuando corresponda. + - Registra métricas si procede (Metrics.inc). + +2) Lógica de dominio + - Si afecta a tareas, delega en src/tasks/service.ts. + - Asegúrate de crear usuarios con ensureUserExists si se persisten referencias. + +3) Respuesta al usuario + - Encola la respuesta en src/services/response-queue.ts con el destinatario adecuado (usuario o grupo). + - Incluye metadata JSON si mencionas usuarios (@) para formateo posterior. + +4) Rate limiting y UX + - Verifica RateLimiter.checkAndConsume(sender) si el comando puede ser abusado. + - Usa RateLimiter.shouldNotify para evitar spam de notificaciones de rate limit. + +5) Tests + - Crea una prueba en tests/unit/services/command..test.ts + - Usa Database(':memory:') + initializeDatabase(memdb) e inyecta CommandService/TaskService.dbInstance. + +6) Documentación + - Añade una breve descripción del comando a docs/overview.md (flujos) si es relevante. + - Si el comando expone nuevas métricas o env vars, actualiza docs/operations.md. + +Consejos +- Mantén CommandService como orquestador; la lógica compleja debe vivir en servicios dedicados. +- Usa nombres y mensajes consistentes con el resto de comandos. diff --git a/docs/how-to/adding-env.md b/docs/how-to/adding-env.md new file mode 100644 index 0000000..9dbda81 --- /dev/null +++ b/docs/how-to/adding-env.md @@ -0,0 +1,22 @@ +# Añadir una nueva variable de entorno + +Pasos +1) Definir uso y por defecto + - Elige un nombre claro (prefijos existentes si aplica, p.ej. GROUP_, RATE_LIMIT_, METRICS_). + - Define valor por defecto sensato si procede. + +2) Leer y validar + - Lee la variable en el módulo correspondiente. + - Si es global u obligatoria, extiende `src/server.ts::validateEnv()` para loggear o fallar si falta. + +3) Documentar + - Añade la variable a docs/operations.md con: + - significado, rango válido, default y efectos. + +4) Tests + - Cubre ramas con valores válidos/ inválidos y ausencia de la variable. + - Evita efectos secundarios en test (p.ej., schedulers no deben arrancar). + +Consejos +- Normaliza formatos (booleans como 'true'/'1'/'yes'). +- Mantén consistencia en nombres y semánticas con variables similares. diff --git a/docs/how-to/adding-migration.md b/docs/how-to/adding-migration.md new file mode 100644 index 0000000..d1a7662 --- /dev/null +++ b/docs/how-to/adding-migration.md @@ -0,0 +1,28 @@ +# Cómo añadir una migración + +Contexto +- Migraciones up-only con checksum estático y función `up(db)`. + +Pasos +1) Crear migración + - Edita src/db/migrations/index.ts y añade una entrada `Migration` con: + - version (número incremental), + - name (descriptivo), + - checksum (string calculado una vez), + - up: (db) => { ... } con SQL idempotente cuando sea posible (usa helpers como tableHasColumn). + +2) SQL y convenciones + - Timestamps como 'YYYY-MM-DD HH:MM:SS[.mmm]' con strftime. + - FOREIGN KEYs explícitas y `PRAGMA foreign_keys = ON`. + - Usa `ON CONFLICT` para upserts cuando aplique. + +3) Pruebas + - En un test, crea `const memdb = new Database(':memory:')` y ejecuta `initializeDatabase(memdb)`. + - Valida que la tabla/columna existe y que el esquema es el esperado. + +4) Despliegue + - El arranque (`server.start`) invoca `Migrator.migrateToLatest`. No necesitas pasos manuales. + +Notas +- No hay “down”; si necesitas revertir, crea una nueva migración correctiva. +- Evita cambios destructivos sin plan de migración de datos. diff --git a/docs/how-to/adjusting-group-sync.md b/docs/how-to/adjusting-group-sync.md new file mode 100644 index 0000000..210d4f6 --- /dev/null +++ b/docs/how-to/adjusting-group-sync.md @@ -0,0 +1,24 @@ +# Ajustar la sincronización de grupos y miembros + +Configuración por entorno +- GROUP_SYNC_INTERVAL_MS: intervalo para refrescar lista de grupos activos. +- GROUP_MEMBERS_SYNC_INTERVAL_MS: intervalo para refrescar miembros de grupos activos. +- WHATSAPP_COMMUNITY_ID: comunidad a sincronizar desde Evolution API. + +Operaciones comunes +- Forzar refresco de caché de grupos: + - Llama `GroupSyncService.refreshActiveGroupsCache()` tras cambios relevantes. +- Sincronizar miembros de un grupo concreto: + - `await GroupSyncService.syncMembersForGroup(groupId)`. +- Comprobar frescura de snapshot: + - `GroupSyncService.isSnapshotFresh(groupId)` en función de `MAX_SNAPSHOT_AGE_MS`. + +Puntos de extensión +- API de origen: + - Implementa/ajusta `(GroupSyncService as any).fetchGroupMembersFromAPI(groupId)` si se mockea/inyecta. +- Reconciliación: + - `reconcileGroupMembers(groupId, snapshot)` espera objetos `{ userId, isAdmin }`. + +Buenas prácticas +- No correr schedulers en test (el servicio ya lo evita). +- Usa transacciones al actualizar tablas de grupos/miembros (ya integrado en upsert). diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..5eeb950 --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,49 @@ +# Operación y configuración + +Variables de entorno (principales) +- EVOLUTION_API_URL: base URL de Evolution API. +- EVOLUTION_API_INSTANCE: nombre/ID de instancia en Evolution. +- EVOLUTION_API_KEY: API key para peticiones salientes (contacts, etc.). +- WEBHOOK_URL: URL pública del webhook (puede usarse para auto-registro/config). +- WHATSAPP_COMMUNITY_ID: comunidad cuyos grupos se sincronizan. +- PORT: puerto HTTP (por defecto 3007). +- NODE_ENV: 'development' | 'test' | 'production'. +- METRICS_ENABLED: 'true'|'false'|'1'|'0' (por defecto habilitado salvo en test). +- RATE_LIMIT_PER_MIN: tokens por minuto por usuario (default 15). +- RATE_LIMIT_BURST: capacidad del bucket (default = RATE_LIMIT_PER_MIN). +- GROUP_SYNC_INTERVAL_MS: intervalo de sync de grupos (default 24h; min 10s en dev). +- GROUP_MEMBERS_SYNC_INTERVAL_MS: intervalo de sync de miembros (default 6h; min 10s en dev). +- GROUP_MEMBERS_INACTIVE_RETENTION_DAYS: días para borrar miembros inactivos (default 180). +- TZ: zona horaria para recordatorios (default Europe/Madrid). + +Endpoints operativos +- GET /metrics + - 200 si Metrics.enabled() y formato Prometheus por defecto. + - 404 si métricas deshabilitadas; 405 si método no permitido. + +Arranque y servicios +- src/server.ts::start() + - Valida entorno (logs de variables presentes/faltantes). + - Aplica migraciones up-only. + - Inicia HTTP y (según entorno) schedulers. + +Schedulers +- GroupSyncService.startGroupsScheduler() y .startMembersScheduler() + - Saltan en test; intervalos controlados por env. +- RemindersService.start() + - Tick cada minuto, filtra por zona horaria y preferencias. +- MaintenanceService.start() + - Tarea diaria; borra miembros inactivos según retención. + +Datos y backups +- Data path: data/tasks.db (por defecto). +- Migraciones con backup opcional (withBackup=false por defecto en initializeDatabase). +- Recomendación: planificar copia de seguridad periódica del directorio data/ y retención externa. + +Métricas de referencia +- sync_runs_total, identity_alias_resolved_total, contadores/gauges específicos de colas y limpieza. +- Añadir nuevas métricas usando Metrics.inc/set y documentarlas aquí. + +Buenas prácticas +- No arrancar schedulers en test salvo que FORCE_SCHEDULERS='true'. +- Validar nuevas env en src/server.ts::validateEnv() y documentarlas aquí. diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..7c200a9 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,42 @@ +# Visión general + +Qué hace el sistema +- Ingesta de webhooks de WhatsApp (Evolution API) y ruteo de eventos. +- Parsing de comandos de texto y operaciones sobre tareas. +- Sincronización periódica de grupos y miembros. +- Recordatorios a usuarios según preferencias. +- Cola de respuestas con reintentos/backoff. +- Métricas y health para operación. + +Flujos principales +1) Mensaje entrante + - src/server.ts recibe webhook → extrae texto → delega en src/services/command.ts (CommandService). + - CommandService puede interactuar con src/tasks/service.ts (crear/actualizar/consultar tareas) y encolar respuestas en src/services/response-queue.ts. +2) Schedulers + - src/services/group-sync.ts sincroniza grupos activos y sus miembros. + - src/services/reminders.ts consulta preferencias y encola recordatorios. + - src/services/maintenance.ts aplica retención de miembros inactivos. +3) Persistencia + - src/db.ts abre SQLite (data/tasks.db por defecto), aplica PRAGMAs (WAL, FK, etc.) y migraciones (src/db/migrations). +4) Observabilidad + - src/services/metrics.ts expone contadores/gauges; /metrics en src/server.ts. + +Glosario +- JID: identificador completo de WhatsApp (p.ej., 12345@s.whatsapp.net, 999@g.us). +- user_id: número normalizado (solo dígitos) sin dominio. +- groupId: JID de grupo, termina en @g.us. +- Alias: identificador alternativo que mapea a user_id real (src/services/identity.ts). + +Mapa rápido +- Entrada HTTP: src/server.ts +- DB y migraciones: src/db.ts, src/db/migrations/index.ts +- Servicios: src/services/*.ts (command, contacts, group-sync, identity, maintenance, metrics, rate-limit, reminders, response-queue, webhook-manager) +- Utilidades: src/utils/*.ts (whatsapp, formatting, etc.) +- Tareas: src/tasks/*.ts (modelo y servicio) +- Tests: tests/unit/**/*.test.ts + +Siguientes lecturas +- architecture.md para responsabilidades por módulo. +- database.md para entender tablas y PRAGMAs. +- operations.md para variables de entorno, métricas y schedulers. +- tests-map.md para ver qué se valida y dónde extender cobertura. diff --git a/docs/tests-map.md b/docs/tests-map.md new file mode 100644 index 0000000..1b01a9d --- /dev/null +++ b/docs/tests-map.md @@ -0,0 +1,32 @@ +# Mapa de tests + +Cobertura actual (resumen) +- tests/unit/server.test.ts + - Valida flujo de WebhookServer: health/metrics, manejo de payloads, normalización básica y encolado simulado. +- tests/unit/services/cleanup-inactive.test.ts + - Verifica MaintenanceService.cleanupInactiveMembersOnce con fechas umbral. +- tests/unit/services/command.claim-unassign.test.ts + - Flujo de tomar/soltar tareas a través de CommandService y TaskService, creando datos en memoria. +- tests/unit/services/command.date-parsing.test.ts + - Parsing de fechas y helpers relacionados con zona horaria para comandos. +- tests/unit/services/command.reminders-config.test.ts + - Configuración de recordatorios por comando y persistencia en user_preferences. +- tests/unit/services/group-sync.members.test.ts + - Reconciliación de miembros de grupos (added/updated/deactivated) y timestamps. +- tests/unit/services/group-sync.scheduler.test.ts + - Semántica de shouldSync, intervalos y control de schedulers. +- tests/unit/services/metrics-health.test.ts + - Habilitación/inhabilitación de métricas y formato de salida. +- tests/unit/services/reminders.test.ts + - Lógica de recordatorios: ventanas horarias, frecuencias y encolado. +- tests/unit/services/response-queue.cleanup.test.ts + - Limpieza de cola de respuestas según políticas temporales. +- tests/unit/services/response-queue.test.ts + - Reintentos y backoff de la cola de respuestas. + +Huecos de cobertura sugeridos +- contacts.ts: errores de red, timeouts y caché negativa. +- identity.ts: interacción entre caché en memoria y tabla user_aliases (miss/hit/actualización). +- rate-limit.ts: escenarios de burst y cooldown de notificación. +- webhook-manager.ts: configuración y comportamiento condicional por eventos. +- tasks/service.ts: rutas menos comunes (edición, vencimientos complejos).