docs: crear ADRs y guía de arquitectura y migraciones
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>pull/1/head
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…
Reference in New Issue