You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
taskbot/docs/refactor-command-service.md

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.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, lv/weekdays, semanal/weekly, off/apagar/ninguno.
    • Hora opcional HH:MM (clamp 0023; minutos 0059).
    • 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 SHA256, 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 ~300500 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.