9.7 KiB
Plan de refactor de CommandService (enfoque A: handlers por acción)
Este documento describe un plan incremental y sin cambios de UX para refactorizar src/services/command.ts en módulos más pequeños y testeables, manteniendo CommandService como punto de entrada público (handle/handleWithOutcome) y preservando textos y métricas existentes.
Objetivos
- Reducir tamaño y complejidad cognitiva de
command.ts. - Separar responsabilidades por comando y por utilidades comunes.
- Mantener compatibilidad de API, textos, límites y métricas (no romper tests ni dashboards).
- Mejorar testabilidad y facilidad de evolución.
Principios
- Compatibilidad primero: no cambiar mensajes ni nombres de métricas.
- Refactor incremental: PRs pequeñas por etapa/handler.
- Dependencias hacia abajo: handlers dependen de servicios existentes (tasks, contacts, group-sync, etc.), nunca al revés.
- Flags centralizadas o inyectadas: evitar leer las mismas ENV en múltiples sitios.
- Sin reordenar líneas de salida salvo que sea necesario (p. ej., quitar la última línea en blanco, como ahora).
Estructura objetivo
- src/services/commands/index.ts ← router/orquestador (sustituye a processTareaCommand)
- src/services/commands/shared.ts ← helpers comunes (alias, fechas, IDs, membresía, límites)
- src/services/commands/parsers/nueva.ts
- src/services/commands/handlers/
- ver.ts
- nueva.ts
- completar.ts
- tomar.ts
- soltar.ts
- configurar.ts
- web.ts
- src/services/onboarding.ts ← JIT y bundle de 2 DMs (disparado desde “nueva”)
- (opcional) src/services/web-access.ts ← gestión de tokens web (invalidate, generate, hash, URL)
Servicios existentes a reutilizar (no mover):
- TaskService, GroupSyncService, ContactsService, IdentityService, AllowedGroups, ResponseQueue, Metrics, utils/crypto, utils/formatting, utils/icons, utils/whatsapp, db.
Etapas del refactor
Etapa 0 — Red de seguridad y decisiones
- Congelar mensajes de usuario y métricas actuales.
- Inventario de dependencias por comando (DB y servicios).
- Añadir/reforzar tests de integración por comando:
- Parseo multi-IDs (completar/tomar).
- Gating de grupo (enforce).
- TZ y vencidas en “ver”.
- Web tokens (invalida previos, expira 10 min).
- “nueva”: asignación por contexto, @yo, hoy/mañana, YY-MM-DD→20YY.
Etapa 1 — Router y shared
- Crear
commands/index.ts:- Resolver alias → acción canónica.
- Delegar por acción a handlers (temporalmente a un fallback que llama a la lógica actual).
- Mantener Help v1/v2 y CTA.
- Crear
commands/shared.ts:- ACTION_ALIASES y SCOPE_ALIASES.
- ymdInTZ(TZ), todayYMD.
- resolveTaskIdFromInput.
- parseMultipleIds(tokens, max=10) con truncado y dedup.
- enforceMembership(userId, task, flags) usando
GroupSyncService.isSnapshotFreshyGROUP_MEMBERS_ENFORCE.
Etapa 2 — Parser de “nueva”
- Mover
parseNuevaaparsers/nueva.ts. - Tests unitarios: hoy/mañana (con y sin acento), YYYY-MM-DD y YY-MM-DD→20YY, @yo, limpieza de puntuación, excluir @menciones de la descripción.
Etapa 3 — Handlers pequeños
- configurar.ts: upsert en
user_preferences, validaciónHH:MM, etiquetas de respuesta. - web.ts: invalidar tokens vigentes, generar token, hash (sha256), insertar y construir URL. Métrica
web_tokens_issued_total. - Conectar ambos en el router.
Etapa 4 — Handler de lectura “ver”
- Soportar scopes “mis” y “todas”, límite 10, DM vs grupo (transición a DM).
- Cálculo de vencidas según TZ (dd/MM con ⚠️ si vencida).
- Agrupar por grupo, “y X más”, nombres de asignados (ContactsService).
- Métricas:
commands_alias_used_total(info/mias/todas),ver_dm_transition_total.
Etapa 5 — Handlers de mutación multi-ID
- completar.ts, tomar.ts, soltar.ts.
- Reutilizar parseMultipleIds, resolveTaskIdFromInput y enforceMembership.
- Mantener mensajes por caso (already/completed/not_found/bloqueadas) y resumen final, ayudas de uso en vacío.
Etapa 6 — Handler “nueva” y Onboarding
- nueva.ts:
- Normalización de menciones (@tokens y menciones del contexto), exclusión bot, IdentityService, plausibilidad, @yo.
- Asignación por contexto (grupo sin menciones → sin dueño; DM sin menciones → creador).
- Guardar
task_originscuando aplique. Actualizarlast_command_at. - Acks al creador y DMs a asignados (idéntico formato, menciones).
- onboarding.ts:
- JIT por menciones/tokens irrecuperables (flags: ONBOARDING_PROMPTS_ENABLED, ONBOARDING_ENABLE_IN_TEST).
- Bundle de 2 DMs con cap (
ONBOARDING_EVENT_CAP), cooldown (ONBOARDING_DM_COOLDOWN_DAYS), gating AllowedGroups, delays (ONBOARDING_BUNDLE_DELAY_MS), ResponseQueue y métricas:- onboarding_prompts_sent_total / skipped_total
- onboarding_bundle_sent_total
- onboarding_recipients_capped_total
- onboarding_dm_skipped_total
- Mantener
ResponseQueue.setOnboardingAggregatesMetrics()tras comando.
Etapa 7 — Limpieza
- Reducir
CommandServicea:- parseo de trigger (/t), registro de
last_command_aty gating global inicial. - delegación al router y clasificación de outcome (ok/error) como ahora.
- parseo de trigger (/t), registro de
- Centralizar CTA y textos estáticos compartidos si aplica.
- Opcional: centralizar flags en un
config.tsliviano.
Etapa 8 — Hardening y observabilidad
- Revisar ciclos de import (handlers no deben importar CommandService).
- Confirmar puntos de métricas y logs condicionales (
NODE_ENV !== 'test'). - Tests de humo por handler con DB en memoria.
- Documentar contratos en
commands/README.md(opcional).
Detalles técnicos por handler
- configurar:
- Validar mapa de alias: diario/daily, l‑v/weekdays, semanal/weekly, off/apagar/ninguno.
- Hora opcional
HH:MM(clamp 00–23; minutos 00–59). - Upsert con
last_reminded_ona NULL al cambiar frecuencia.
- web:
- Solo por DM (en grupo: mensaje instructivo).
- Requiere
WEB_BASE_URL; si falta, advertir. - Invalidar tokens vigentes (used_at = now) con
expires_at > now. - Generar token base64url (32 bytes), guardar hash SHA‑256, expira en 10 min.
- ver:
- “mis”: agrupar por
group_id(nombre desde cache de grupos), due_date conformatDDMM, marcar vencidas según TZ local (no del host). - “todas”: “Tus tareas” + “Sin responsable” por grupo (DM: usando snapshot fresca de membresía).
- En grupo: no listar; responder por DM (transición).
- “mis”: agrupar por
- completar/tomar/soltar:
- Multi-IDs: espacios y/o comas, dedup, máx. 10; resumen de resultados.
- enforceMembership si
GROUP_MEMBERS_ENFORCE=truey snapshot fresca disponible.
Utilidades compartidas (shared.ts)
- ACTION_ALIASES y SCOPE_ALIASES (incluyendo: n/+/crear/nueva, ver/ls/listar/mostrar, x/hecho/completar/done, tomar/claim, soltar/unassign, ayuda/help/info/?, configurar/config, web).
- ymdInTZ(d, TZ) y todayYMD.
- formatters: reutilizar
formatDDMM,codeId,padTaskId,bold,italic,code,section. - parseMultipleIds(tokens: string[], max=10): retorna números válidos > 0, dedup, truncado.
- resolveTaskIdFromInput(n):
TaskService.getActiveTaskByDisplayCode. - enforceMembership(sender, task, flags): si
task.group_idy snapshot fresca, bloquear si no es miembro activo.
Flags/ENV a centralizar o respetar
- FEATURE_HELP_V2
- GROUP_GATING_MODE, GROUP_MEMBERS_ENFORCE
- TZ (por defecto: Europe/Madrid)
- WEB_BASE_URL, CHATBOT_PHONE_NUMBER
- ONBOARDING_PROMPTS_ENABLED, ONBOARDING_ENABLE_IN_TEST
- ONBOARDING_EVENT_CAP, ONBOARDING_DM_COOLDOWN_DAYS, ONBOARDING_BUNDLE_DELAY_MS
Sugerencia: leer una vez y pasar por contexto/env a los handlers.
Métricas a preservar
- commands_alias_used_total (labels: action=info/mias/todas)
- ver_dm_transition_total
- web_tokens_issued_total
- commands_unknown_total
- commands_blocked_total
- onboarding_prompts_sent_total / onboarding_prompts_skipped_total
- onboarding_bundle_sent_total
- onboarding_recipients_capped_total
- onboarding_dm_skipped_total
- setOnboardingAggregatesMetrics() tras recibir comandos
Riesgos y mitigaciones
- Cambios de texto rompen tests/UX: mantener textos y orden de líneas.
- Ciclos de import: mantener dependencias unidireccionales hacia servicios.
- Lectura repetida de ENV: centralizar o inyectar en contexto.
- Puntos de métricas: conservar nombres y ubicaciones lógicas.
Criterios de aceptación por etapa
- Compila y pasa tests existentes.
- Salidas de texto idénticas (incluye saltos de línea).
- Mismas métricas y side effects (ResponseQueue).
- Para multi-IDs: mismo conteo y resumen.
- Para “ver”: mismas reglas de vencidas y agrupación.
- Para “web”: mismo comportamiento de invalidad y expiración de tokens.
Estrategia de PRs
- 1 PR por etapa o por handler (máximo ~300–500 LOC cambiadas).
- Cada PR:
- Mantiene API de
CommandService. - Sustituye una pieza con tests.
- Checklist (abajo).
- Sin reformat masivo no relacionado.
- Mantiene API de
Checklist rápida por PR
- Textos exactos (incluye emojis, mayúsculas, espacios).
- Métricas: nombres y contadores como antes.
- Flags: respetadas (valores por defecto idénticos).
- Lógica de límites (máx. 10 IDs, “y X más”, etc.).
- En grupo vs DM (transiciones e instrucciones).
- Onboarding: condiciones y métricas (si aplica).
- Sin ciclos de import; dependencias correctas.
- Tests de humo/integración cubren ruta feliz y errores principales.
Notas de compatibilidad
- No cambiar el contrato público:
CommandService.handleyhandleWithOutcome. - Mantener
CommandService.dbInstancepara DI mínima y compatibilidad con tests (inyectar en handlers cuando lo necesiten). - Logs condicionales (
NODE_ENV !== 'test') como hoy.
Con este plan podrás avanzar en pasos pequeños, verificando en cada etapa que el comportamiento externo se mantiene mientras mejoras la mantenibilidad interna.