docs: crear ADRs y guía de arquitectura y migraciones

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
pull/1/head
brobert 1 month ago
parent 8b701e9435
commit ce223a2955

@ -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

@ -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).

@ -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).

@ -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.

@ -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).

@ -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.

@ -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.<tu-comando>.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.

@ -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.

@ -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.

@ -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).

@ -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í.

@ -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.

@ -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).
Loading…
Cancel
Save