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/plan-onboarding-usuarios.md

154 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Plan de Onboarding de Usuarios y Resolución de Alias (JID opaco → número)
Resumen y diagnóstico (basado en el código actual)
- Fuentes automáticas actuales de aprendizaje de alias (sin intervención de usuarios):
1) Mensajes en grupos: en src/server.ts, si participantAlt y participant difieren, se hace IdentityService.upsertAlias(participant, participantAlt, 'message.key'), mapeando un @lid “opaco” al JID real @s.whatsapp.net.
2) Sincronización de miembros: en src/services/group-sync.ts, si el payload trae p.id y p.jid, se hace upsertAlias(id, jid, 'group.participants') y además se asegura el usuario con ensureUserExists.
3) Actualizaciones de contactos/chats: en src/services/contacts.ts, si se recibe objeto con id=@lid y jid=@s.whatsapp.net, se hace upsertAlias(alias, jid, 'contacts.update').
- Alta de usuarios sin DM: src/services/group-sync.ts::reconcileGroupMembers llama ensureUserExists(userId) para cada miembro activo; si tenemos número, el usuario “existe” aunque no haya enviado DM.
- Normalización robusta: utils/whatsapp.normalizeWhatsAppId elimina dominio y sufijo “:xx”, quedando el número limpio si venía en el JID.
- Problema detectado: en src/services/command.ts, al parsear menciones, si una mención llega como @lid sin alias aún, se descarta (aunque a veces podría venir como JID real). Resultado: “se puede crear tarea pero no asignar responsables” en algunos casos, especialmente con usuarios “silenciosos” (no escriben en grupo) cuando la API no aporta número en ninguno de los eventos.
Objetivo
- Minimizar fricción y tiempo hasta poder asignar y autenticar: que los usuarios queden asignables con la mínima acción (idealmente sin DM).
- Evitar spam y códigos por-usuario. Publicar, solo si hace falta, un único mensaje por grupo con enlace wa.me.
- Cubrir casos de usuarios “silenciosos” (no escriben en grupo) garantizando una vía de mapeo fiable (DM “hola”).
Estrategia general
- Exprimir al máximo el aprendizaje automático que ya tenemos (participants y contacts).
- Ajustar CommandService para no descartar menciones válidas con números reales.
- Medir cobertura y publicar un único mensaje por grupo con wa.me únicamente si faltan usuarios por resolver; con cooldown.
- DM “activar” como último recurso confiable para cerrar huecos de usuarios silenciosos; considerar “código por grupo” solo como fallback extremo si una instancia no correlaciona jamás sin token.
Fases
Fase A0 — Verificación y observabilidad (rápida, sin UX visible)
- Estado: completada (deploy tras commit d25efb0).
- Métricas nuevas:
- alias_coverage_ratio{group_id}: gauge con el porcentaje aproximado de miembros activos con número resoluble (o alias).
- onboarding_prompts_sent_total / onboarding_prompts_skipped_total: counters para controlar ruido.
- onboarding_assign_failures_total: counter de menciones no resolubles al crear tareas.
- Logs de desarrollo (NODE_ENV=development): en src/server.ts, comparar participant vs participantAlt (normalizados) y mentionedJid normalizados para comprobar la frecuencia de correlación automática en tu instancia Evolution.
Fase A1 — Aprendizaje “agresivo” al entrar en grupos (Completada)
- Al recibir groups.upsert (src/server.ts):
- syncGroups → refreshActiveGroupsCache → syncMembersForActiveGroups (ya implementado).
- Efectos:
- Crea usuarios con ensureUserExists(userId).
- Si el payload incluye id + jid, rellena alias automáticamente.
- Mantener activo ContactsService.updateFromWebhook para capturar correlaciones adicionales en los primeros minutos.
Fase A2 — Ajuste clave sin fricción: conservar menciones con números (Completada)
- Estado: completada (deploy tras commit 8b1af56).
- En src/services/command.ts, al construir los candidatos a assignees:
- Si normalizeWhatsAppId(token) produce dígitos y resolveAliasOrNull devuelve null, CONSERVAR ese número (no descartarlo).
- Asegurar ensureUserExists para esos IDs conservados antes de usarlos.
- Filtrar CHATBOT_PHONE_NUMBER para evitar autoasignaciones al bot.
- Métrica: se incrementa onboarding_assign_failures_total con labels {group_id, source, reason} cuando una mención/token no es resoluble ni plausible.
- Configuración: ONBOARDING_FALLBACK_MIN_DIGITS (por defecto 8) controla la longitud mínima para considerar un número “plausible”.
- Efecto: reduce drásticamente los fallos de asignación por mención sin necesidad de DM.
Fase A3 — Mensaje único por grupo con wa.me (Completada)
- Condición de publicación:
- Tras A1 y un breve grace period (≈12 min) para permitir contacts/chats updates, calcular alias_coverage_ratio{group_id}.
- Si cobertura = 100% → NO publicar.
- Si cobertura < 100% publicar UNA vez un mensaje por grupo con el texto:
- Para poder asignarte tareas y acceder a la web, envía activar al bot por privado. Solo hace falta una vez. Enlace: https://wa.me/<NUM_BOT>
- Control de ruido:
- Persistir timestamp de último envío por grupo (cooldown, p. ej., 7 días) y re-publicar solo si entran nuevos miembros sin resolver tras el cooldown.
- Fallback extremo (probablemente no necesario con tu stack):
- Si en una instancia concreta el DM “hola” no basta para correlacionar, usar “código por grupo” como texto pre-rellenado en wa.me: “alta XYZ123” (único por grupo, nunca por-usuario), almacenado con caducidad. Aplicarlo solo si la métrica demuestra que el caso existe.
Fase A4 — Asistentes “just-in-time” y UX mínima (Completada)
- Si una asignación falla por mención no resoluble:
- Enviar DM al asignador (ResponseQueue) con: “No puedo asignar a X aún. Pídele que toque este enlace y diga activar: https://wa.me/<NUM_BOT>”.
- Primer DM “activar” de un usuario:
- Asegurar ensureUserExists y responder con: “Listo, ya puedes reclamar/ser responsable en: …”.
- Opcional web: si el usuario llega sin estar identificado, mostrar banner con botón a wa.me “activar”.
Fase A5 — Optimizaciones post-A1 (pendiente)
- Optimizar encadenado tras groups.upsert para sincronizar solo los grupos afectados cuando el payload lo permita.
- Añadir debounce/backoff por grupo (25 s) para coalescer ráfagas de upserts en corto intervalo.
- Añadir tests: uno que valide el filtrado a “grupos afectados” y otro que verifique que el debounce evita ejecuciones duplicadas dentro de la ventana.
Fase Final — Pruebas E2E
- Objetivo: validar end-to-end en un entorno de staging con Evolution API que los prompts A3 funcionan sin efectos secundarios.
- Casos a verificar:
- Envío al grupo (@g.us) mediante sendText: que el backend acepte recipient con @g.us y el mensaje se entregue.
- Publicación condicional: cobertura < 100% tras el grace se envía; cobertura = 100% se omite; cooldown activo se omite.
- Gating: en modo enforce, grupos no allowed se omite.
- Configuración: sin CHATBOT_PHONE_NUMBER o ONBOARDING_PROMPTS_ENABLED=false se omite.
- Métricas: alias_coverage_ratio, onboarding_prompts_sent_total y onboarding_prompts_skipped_total con su reason se actualizan.
- Preparación recomendada:
- Instancia Evolution apuntando a un grupo de pruebas; CHATBOT_PHONE_NUMBER configurado; METRICS_ENABLED=true.
- Reducir ONBOARDING_GRACE_SECONDS y ONBOARDING_COOLDOWN_DAYS para acelerar validación.
- Confirmar que ResponseQueue.process está activo y que los workers pueden enviar.
Criterios de aceptación
- p95 del tiempo desde que un usuario toca el enlace a quedar asignable < 1 minuto.
- En la mayoría de grupos no se publica ningún mensaje (cobertura 100% tras primer sync + contacts).
- Caída significativa de onboarding_assign_failures_total respecto al baseline.
- Sin spam: un único mensaje por grupo y cooldown aplicado.
Archivos a ver/editar/crear
A) Núcleo bot
- src/services/command.ts (EDITAR)
- Puntos: construcción de mentionsNormalizedFromContext y normalizedFromAtTokens.
- Cambio: fallback a número normalizado cuando resolveAliasOrNull no resuelva; ensureUserExists; filtrar CHATBOT_PHONE_NUMBER; incrementar onboarding_assign_failures_total cuando una mención no sea resoluble en absoluto.
- src/services/group-sync.ts (EDITAR)
- Tras reconcileGroupMembers, computar cobertura aproximada (miembros activos con número conocido / miembros activos totales).
- Exponer alias_coverage_ratio{group_id} vía Metrics.set. Si coverage < 100% y cooldown vencido disparar encolado de un mensaje único por grupo (ResponseQueue).
- Método util: getActiveGroupIdsForUser, isSnapshotFresh, etc., ya existen; añadir tracking de onboarding_prompted_at (ver persistencia).
- src/server.ts (EDITAR mínimo)
- Logs de desarrollo comparando participant vs participantAlt y mentionedJid normalizados para verificar correlación automática.
- Opcional: hook tras groups.upsert para forzar el chequeo de cobertura post-sync (o dejarlo en scheduler de miembros).
- src/services/contacts.ts y src/services/identity.ts (VER)
- Ya aportan alias automáticamente; no requieren cambios inmediatos.
- src/services/response-queue.ts (VER/EDITAR si hiciera falta)
- Asegurar que puedes encolar mensajes hacia group_id@g.us sin cambios (parece OK con sendText si el backend lo admite). Añadir, si quieres, etiquetas/metadata para identificar onboarding”.
B) Persistencia
- Opción simple recomendada: columna en groups
- Nueva columna: groups.onboarding_prompted_at TEXT NULL.
- Lógica: publicar si coverage < 100% y (onboarding_prompted_at IS NULL o han pasado X días).
- Alternativa: tabla dedicada group_onboarding (group_id PK, last_prompt_at, last_coverage, last_pending_count, last_sent_by).
- Archivos:
- src/db/migrations/index.ts (EDITAR): añadir migración para onboarding_prompted_at (o tabla nueva).
- src/db.ts (VER): no requiere cambios, migrador ya está integrado.
C) Mensajería y copy
- Texto base:
- Para poder asignarte tareas y acceder a la web, envía hola al bot por privado. Solo hace falta una vez. Enlace: https://wa.me/<NUM_BOT>
- Fallback extremo (solo si es necesario, ver A3):
- “… o envía alta XYZ123 si el enlace no funciona.”
- Dónde generarlo: en GroupSyncService (o un OnboardingService nuevo) cuando coverage < 100% y cooldown vencido; encolar con ResponseQueue para recipient = group_id@g.us.
D) UI web (opcional a posteriori)
- Banner SSR-safe cuando App.Locals.userId no esté disponible: botón wa.me con hola”; desaparecer al resolver.
Métricas propuestas
- alias_coverage_ratio{group_id} (gauge).
- onboarding_prompts_sent_total / onboarding_prompts_skipped_total (counters).
- onboarding_assign_failures_total (counter).
- identity_alias_upserts_total, identity_alias_resolved_total / identity_alias_unresolved_total (ya existen).
- time_to_link_p95 (opcional; aproximable por primer avistamiento primer DM/alias resuelto”).
Caveats y buenas prácticas
- No spamear: un único mensaje por grupo + cooldown; nada si coverage=100%.
- Privacidad: DM hola iniciado por el usuario; no mostrar datos sensibles en grupos.
- Entornos de test: suprimir publicación y logs invasivos; respetar NODE_ENV='test'.
- Resiliencia: si Evolution deja de enviar participantAlt/p.jid, el flujo de DM cubre a los silenciosos; si los envía, el DM apenas será necesario.
Checklist de ejecución
- A0: Añadir métricas y logs de verificación (dev).
- A1: Confirmar que el sync de miembros corre al entrar; mantener contacts.update. (hecho)
- A2: Ajuste en CommandService (fallback a número + ensureUserExists + métricas de fallo).
- A3: Publicación condicional de mensaje por grupo con cooldown + persistencia mínima.
- A4: DM just-in-time al asignador ante fallo de mención + confirmación al primer DM de usuario.
Archivos adicionales que podríamos necesitar añadir al chat en la implementación
- src/utils/whatsapp.ts (para confirmar normalizeDigits si lo reutilizamos en copy/URLs).
- apps/web/src/app.d.ts (si añadimos banner basado en session/locals).
- tests/unit/* y tests/web/* (para cubrir la nueva lógica de fallback y métricas).