|  |  | # 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.isSnapshotFresh` y `GROUP_MEMBERS_ENFORCE`.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Etapa 2 — Parser de “nueva”
 | 
						
						
						
							|  |  | - Mover `parseNueva` a `parsers/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ón `HH: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_origins` cuando aplique. Actualizar `last_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 `CommandService` a:
 | 
						
						
						
							|  |  |   - parseo de trigger (/t), registro de `last_command_at` y gating global inicial.
 | 
						
						
						
							|  |  |   - delegación al router y clasificación de outcome (ok/error) como ahora.
 | 
						
						
						
							|  |  | - Centralizar CTA y textos estáticos compartidos si aplica.
 | 
						
						
						
							|  |  | - Opcional: centralizar flags en un `config.ts` liviano.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 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_on` a 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 con `formatDDMM`, 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).
 | 
						
						
						
							|  |  | - completar/tomar/soltar:
 | 
						
						
						
							|  |  |   - Multi-IDs: espacios y/o comas, dedup, máx. 10; resumen de resultados.
 | 
						
						
						
							|  |  |   - enforceMembership si `GROUP_MEMBERS_ENFORCE=true` y 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_id` y 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.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ## 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.handle` y `handleWithOutcome`.
 | 
						
						
						
							|  |  | - Mantener `CommandService.dbInstance` para 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.
 |