|  |  | # 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 del proxy interno (por defecto 3000).
 | 
						
						
						
							|  |  | - BOT_PORT: puerto interno del bot (por defecto 3007).
 | 
						
						
						
							|  |  | - WEB_PORT: puerto interno de la web SvelteKit (por defecto 3008).
 | 
						
						
						
							|  |  | - NODE_ENV: 'development' | 'test' | 'production'.
 | 
						
						
						
							|  |  | - METRICS_ENABLED: 'true'|'false'|'1'|'0' (por defecto habilitado salvo en test). Ej.: METRICS_ENABLED='true'
 | 
						
						
						
							|  |  | - 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).
 | 
						
						
						
							|  |  | - REMINDERS_GRACE_MINUTES: minutos de gracia tras la hora programada para enviar recordatorios atrasados (por defecto 60).
 | 
						
						
						
							|  |  | - GROUP_GATING_MODE: 'off' | 'discover' | 'enforce' (control de acceso por grupos; por defecto 'off'). Ej.: GROUP_GATING_MODE='discover'
 | 
						
						
						
							|  |  | - REACTIONS_ENABLED: 'true'|'false' para activar reacciones (por defecto 'false'). Ej.: REACTIONS_ENABLED='true'
 | 
						
						
						
							|  |  | - REACTIONS_SCOPE: 'groups'|'all' para limitar reacciones a grupos o permitir en DMs (por defecto 'groups'). Ej.: REACTIONS_SCOPE='groups'
 | 
						
						
						
							|  |  | - REACTIONS_TTL_DAYS: días para permitir la reacción ✅ al completar respecto al mensaje origen (por defecto 14). Ej.: REACTIONS_TTL_DAYS='14'
 | 
						
						
						
							|  |  | - RQ_REACTIONS_MAX_ATTEMPTS: reintentos máximos para jobs de reacción (si no se define, aplica el global). Ej.: RQ_REACTIONS_MAX_ATTEMPTS='3'
 | 
						
						
						
							|  |  | - ADMIN_USERS: lista separada por comas de IDs/JIDs autorizados para /admin (se normalizan a dígitos). Ej.: ADMIN_USERS='34600123456, 5554443333, +34 600 111 222'
 | 
						
						
						
							|  |  | - ALLOWED_GROUPS: lista separada por comas de group_id@g.us para sembrado inicial en arranque. Ej.: ALLOWED_GROUPS='12345-67890@g.us, 11111-22222@g.us'
 | 
						
						
						
							|  |  | - NOTIFY_ADMINS_ON_DISCOVERY: 'true'/'false' para avisar por DM a ADMIN_USERS al descubrir un grupo (modo 'discover'). Ej.: NOTIFY_ADMINS_ON_DISCOVERY='true'
 | 
						
						
						
							|  |  | - DATA_DIR: directorio base para la base de datos SQLite (por defecto ./data).
 | 
						
						
						
							|  |  | - DB_PATH: ruta absoluta o relativa al archivo SQLite; si se define, tiene prioridad sobre DATA_DIR. Ej.: DB_PATH='./data/tasks.db'
 | 
						
						
						
							|  |  | - MIGRATIONS_LOG_LEVEL: 'silent' para silenciar logs del migrador (en test se silencian automáticamente).
 | 
						
						
						
							|  |  | - WEB_BASE_URL: base pública de la interfaz web para construir enlaces absolutos (p. ej., /login?token=...). Obligatoria para /t web. Ej.: WEB_BASE_URL='https://wtask.org'
 | 
						
						
						
							|  |  | - DEV_AUTOSEED_DB: 'true'/'false' para sembrar automáticamente la BD en desarrollo cuando está vacía (apps/web). Ej.: DEV_AUTOSEED_DB='true'
 | 
						
						
						
							|  |  | - DEV_DEFAULT_USER: ID de usuario por defecto en desarrollo (bypass y semilla). Idealmente numérico (solo dígitos). Ej.: DEV_DEFAULT_USER='34600123456'
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Endpoints operativos
 | 
						
						
						
							|  |  | - GET /metrics
 | 
						
						
						
							|  |  |   - 200 si Metrics.enabled() y formato Prometheus por defecto.
 | 
						
						
						
							|  |  |   - 404 si métricas deshabilitadas; 405 si método no permitido.
 | 
						
						
						
							|  |  | - GET /health
 | 
						
						
						
							|  |  |   - 200 siempre (proxy interno), útil para healthcheck del contenedor.
 | 
						
						
						
							|  |  | - APIs web (requieren sesión)
 | 
						
						
						
							|  |  |   - GET /api/me/tasks?status=open|recent&search=...
 | 
						
						
						
							|  |  |     - status=open (por defecto): orden por due_date asc (NULL al final). Aplica gating por AllowedGroups + membresía activa (group_members). Búsqueda con LIKE ESCAPE '\'. Filtros dueBefore y soonDays (días). Paginación page/limit y hasMore/total.
 | 
						
						
						
							|  |  |     - status=recent: tareas asignadas al usuario completadas en las últimas 24 h; orden por completed_at DESC; incluye campos completed y completed_at; mismo gating y búsqueda.
 | 
						
						
						
							|  |  |   - GET /api/me/groups
 | 
						
						
						
							|  |  |     - Devuelve solo grupos permitidos donde el usuario está activo. Incluye counts.open y counts.unassigned por grupo.
 | 
						
						
						
							|  |  |   - GET /api/groups/:id/tasks?unassignedFirst=true
 | 
						
						
						
							|  |  |     - Requiere que el usuario sea miembro activo del grupo y que el grupo esté permitido. Orden por due_date (NULL al final); admite parámetros unassignedFirst, onlyUnassigned y limit (clamp a 100).
 | 
						
						
						
							|  |  |   - GET /api/me/preferences
 | 
						
						
						
							|  |  |     - Devuelve las preferencias del usuario para recordatorios como { freq, time }. Si no hay registro previo, responde { freq: 'off', time: '08:30' }.
 | 
						
						
						
							|  |  |   - POST /api/me/preferences
 | 
						
						
						
							|  |  |     - Actualiza preferencias. Valida freq ∈ {off,daily,weekly,weekdays} y time en formato HH:MM (24h); normaliza hora (p. ej., 7:5 → 07:05). Upsert con updated_at; si freq='off' y no se envía time, conserva la última hora guardada (o '08:30' por defecto).
 | 
						
						
						
							|  |  |   - POST /api/tasks/:id/claim
 | 
						
						
						
							|  |  |     - Reclama la tarea para el usuario actual (idempotente). Requiere sesión; valida que la tarea esté abierta y aplica gating: t.group_id IS NULL o (grupo permitido y membresía activa).
 | 
						
						
						
							|  |  |   - POST /api/tasks/:id/unassign
 | 
						
						
						
							|  |  |     - Elimina la asignación del usuario actual (idempotente) si existe. Requiere sesión; tarea abierta y gating equivalente.
 | 
						
						
						
							|  |  |   - POST /api/tasks/:id/complete
 | 
						
						
						
							|  |  |     - Marca como completada (idempotente). Si es de grupo y no tiene responsables, auto-asigna al usuario que completa antes de marcarla como completada. Gating: si tiene group_id, cualquier miembro activo del grupo de un grupo allowed; si no tiene group_id, solo un asignado. Devuelve la tarea con completed y completed_at.
 | 
						
						
						
							|  |  |   - PATCH /api/tasks/:id
 | 
						
						
						
							|  |  |     - Actualiza { due_date: 'YYYY-MM-DD' | null, description?: string }. Valida due_date y normaliza/sanea description (texto plano, 1–1000 chars, colapsa espacios). Requiere sesión, tarea abierta y gating.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Arranque y servicios
 | 
						
						
						
							|  |  | - src/server.ts::start() (bot)
 | 
						
						
						
							|  |  | - proxy.ts y startup.sh (contenedor único con Bun):
 | 
						
						
						
							|  |  |   - El proxy escucha en PORT (3000 por defecto) y enruta /webhook y /metrics → BOT_PORT; el resto → WEB_PORT.
 | 
						
						
						
							|  |  |   - startup.sh normaliza DB_PATH/DATA_DIR a absolutas, arranca bot, espera tablas web_tokens/web_sessions y arranca la web antes del proxy.
 | 
						
						
						
							|  |  |   - Valida entorno (logs de variables presentes/faltantes).
 | 
						
						
						
							|  |  |   - Aplica migraciones up-only.
 | 
						
						
						
							|  |  |   - Inicia HTTP y (según entorno) schedulers.
 | 
						
						
						
							|  |  |   - Compresión HTTP: desactivada temporalmente (el proxy fuerza Accept-Encoding: identity hacia la web y elimina Content-Encoding/Vary/Content-Length en las respuestas; además, SvelteKit se construye con precompress=false para no generar .br/.gz).
 | 
						
						
						
							|  |  |   - En tests, el migrador silencia logs; puede forzarse en cualquier entorno con MIGRATIONS_LOG_LEVEL='silent'.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Schedulers
 | 
						
						
						
							|  |  | - GroupSyncService.startGroupsScheduler() y .startMembersScheduler()
 | 
						
						
						
							|  |  |   - Saltan en test; intervalos controlados por env.
 | 
						
						
						
							|  |  | - RemindersService.start()
 | 
						
						
						
							|  |  |   - Tick cada minuto, filtra por zona horaria y preferencias, con ventana de gracia configurable (60 min por defecto) tras la hora programada.
 | 
						
						
						
							|  |  | - MaintenanceService.start()
 | 
						
						
						
							|  |  |   - Tarea diaria; borra miembros inactivos según retención.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Datos y backups
 | 
						
						
						
							|  |  | - Data path: /app/data/tasks.db (por defecto).
 | 
						
						
						
							|  |  | - startup.sh normaliza DB_PATH y DATA_DIR a rutas absolutas para que bot y web apunten al mismo archivo, y espera a que existan web_tokens/web_sessions antes de iniciar la web.
 | 
						
						
						
							|  |  | - 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.
 | 
						
						
						
							|  |  | - DB_PATH permite aislar BD por rama/entorno sin tocar DATA_DIR; útil para pruebas en CapRover.
 | 
						
						
						
							|  |  | - En Docker/CapRover, el volumen por defecto es /app/data. Para persistencia, usa rutas de DB_PATH dentro de ese directorio (p. ej., /app/data/tasks-next.db).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Semilla de desarrollo (apps/web)
 | 
						
						
						
							|  |  | - Activación: establecer DEV_AUTOSEED_DB='true'. La semilla solo se ejecuta en desarrollo cuando la tabla tasks está vacía.
 | 
						
						
						
							|  |  | - Usuario por defecto: definir DEV_DEFAULT_USER con un ID numérico (p. ej., 34600123456). Se crea como usuario, se hace miembro activo de varios grupos y se usa para asignaciones.
 | 
						
						
						
							|  |  | - Ruta del archivo en dev (apps/web): por defecto tmp/tasks.db (véase apps/web/src/lib/server/env.ts). En producción, la web usa /app/data por defecto.
 | 
						
						
						
							|  |  | - Regenerar la BD de dev: detener el servidor web, borrar el archivo de BD y reiniciar con DEV_AUTOSEED_DB='true'.
 | 
						
						
						
							|  |  |   - Ejemplo: rm -f tmp/tasks.db
 | 
						
						
						
							|  |  | - Datos que se crean:
 | 
						
						
						
							|  |  |   - Usuarios: 3–5 (incluido el usuario por defecto).
 | 
						
						
						
							|  |  |   - Grupos: “Familia”, “Trabajo”, “Voluntariado”, “Compras” (allowed) y “Varios” (pending).
 | 
						
						
						
							|  |  |   - Allowed groups: allowed para los grupos principales; “Compras” allowed sin membresía del usuario por defecto (sirve para validar gating); “Varios” en pending.
 | 
						
						
						
							|  |  |   - Membresías: el usuario por defecto activo en Familia, Trabajo y Voluntariado; otros usuarios repartidos para soportar múltiples responsables.
 | 
						
						
						
							|  |  |   - Preferencias: recordatorios diarios a las 08:30 para el usuario por defecto.
 | 
						
						
						
							|  |  |   - Tareas: ~30–35 con mezcla rica:
 | 
						
						
						
							|  |  |     - Personales (sin grupo) y de grupo.
 | 
						
						
						
							|  |  |     - due_date en pasado, hoy, futuro y NULL.
 | 
						
						
						
							|  |  |     - Sin responsables, con 1 responsable y con múltiples responsables (incluye “tú”).
 | 
						
						
						
							|  |  |     - Completadas recientemente (≤24h) y antiguas (>48h), con completed_by coherente.
 | 
						
						
						
							|  |  | - Idempotencia: si ya existen tareas no vuelve a sembrar. Para resembrar, borra el archivo de BD o define un DB_PATH nuevo.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Métricas de referencia
 | 
						
						
						
							|  |  | - sync_runs_total, identity_alias_resolved_total, contadores/gauges específicos de colas y limpieza.
 | 
						
						
						
							|  |  | - Control de acceso por grupos (multicomunidades):
 | 
						
						
						
							|  |  |   - allowed_groups_total_pending, allowed_groups_total_allowed, allowed_groups_total_blocked (gauges).
 | 
						
						
						
							|  |  |   - unknown_groups_discovered_total (counter).
 | 
						
						
						
							|  |  |   - messages_blocked_group_total (counter).
 | 
						
						
						
							|  |  |   - commands_blocked_total (counter).
 | 
						
						
						
							|  |  |   - sync_skipped_group_total (counter).
 | 
						
						
						
							|  |  |   - admin_actions_total_allow, admin_actions_total_block (counters).
 | 
						
						
						
							|  |  | - Reacciones del bot:
 | 
						
						
						
							|  |  |   - reactions_enqueued_total{emoji=robot|warn|check|other}
 | 
						
						
						
							|  |  |   - reactions_sent_total{emoji=...}
 | 
						
						
						
							|  |  |   - reactions_failed_total{emoji=...}
 | 
						
						
						
							|  |  | - 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í.
 | 
						
						
						
							|  |  | - En apps/web, kit.csrf.checkOrigin=false debido al proxy interno; considerar alternativas si se elimina el proxy.
 | 
						
						
						
							|  |  | - Tests web con bun:test: construcción programática de apps/web (build/), arranque real del servidor y peticiones HTTP reales; ver tests/web/helpers/server.ts.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Formato de fechas en comandos
 | 
						
						
						
							|  |  | - Se aceptan únicamente YYYY-MM-DD y YY-MM-DD (YY se expande a 20YY).
 | 
						
						
						
							|  |  | - También se soportan tokens naturales: "hoy" y "mañana".
 | 
						
						
						
							|  |  | - Se rechazan otros formatos (p. ej., dd-mm, mm/dd, fechas inválidas).
 | 
						
						
						
							|  |  | - Las fechas se guardan normalizadas como YYYY-MM-DD y se muestran como dd/MM en listados y mensajes.
 |