CreaRack-SL

Integración Zoho ↔ Workspace · Pivot a Calendar + Mail · per-user OAuth · task como hub de acciones

Contexto

Esta decisión reemplaza [[decision—20260516—integracion-zoho-workspace]] (s67, Fase A en PROD desde 16-05-2026) tras la conversación s68 (17-05-2026) en la que Edu replanteó la premisa de fondo.

Premisa que cambió

El ADR s67 asumía que Zoho era SoT también para tasks. Tras 1 día con Fase A en PROD, Edu verbalizó:

“Nuestro gestor de tareas actual es muy completo y funciona perfectamente, creo que es más capaz que el de Zoho, además lo hemos construido a nuestra medida. He estado pensando que lo que realmente necesitamos es sincronizar nuestro gestor de tareas con el calendario y los emails de Zoho, pero no necesariamente utilizar el gestor de tareas de Zoho.”

Observación correcta: Zoho Tasks (lista plana, sin kanban, sin vinculación cross-entidad, sin asignación múltiple nativa) es objetivamente menos capaz que KanbanMini + página /tasks del workspace (drag-drop, multi-assignee, attachments, notas vinculadas del Cuaderno, integración MCP para crear desde Claude).

Estado heredado de s67

  • Fase A en PROD: widget ZohoAgendaWidget en bento home leyendo Zoho Calendar + Zoho Tasks via Service Account de Edu.
  • KanbanMini intacto leyendo D1 (Ruta Y coexistencia).
  • Migration 0026_create_zoho_oauth.sql aplicada en D1 remoto (tabla singleton zoho_oauth_tokens).
  • 27 tasks MCP en D1 sin migrar a Zoho (postpuestas a Fase D en ADR s67).

Decisión

Pivot arquitectónico de 3 ejes:

  1. D1 sigue como SoT único para tasks. Zoho Tasks no se utiliza. Se cancela cualquier sync bidireccional, edición remota y migración masiva de tasks.
  2. Zoho Calendar y Zoho Mail entran como canales de acción explícita desde la task del workspace. La task se convierte en hub de operaciones; Zoho son brazos ejecutores.
  3. Per-user OAuth sustituye al Service Account de la Fase A. Cada miembro del staff (Edu, Dani, Txell) autoriza su propio refresh_token. Obligatorio para Mail (cada uno solo puede ver su propio buzón), coherente para Calendar (los eventos van al primary de cada uno).

Modelo “task como hub”

Acciones explícitas en TaskModal.tsx (no sync silenciosa):

Acción en taskComportamiento
+ Crear eventoAPI directa a Zoho Calendar. Evento creado en el primary del creador (organizer). Otros assignees añadidos como attendees (Zoho envía invitación nativa). Disabled si la task no tiene assignees.
+ Vincular emailPicker con filtro from/subject que lista los últimos N emails del usuario. Selección → vínculo persistente en D1.
+ Nuevo emailDeep-link a Zoho Mail compose con subject y body pre-rellenados desde la task. El usuario revisa firma/adjuntos antes de enviar.

Indicadores en cards (KanbanMini + TaskTableView + TaskKanban) muestran counts vinculados como badges numéricos sin iconos (Regla workspace feedback_workspace_buttons_text_only): por ejemplo [2 ev] [5 mail]. Sólo aparecen si count > 0.

Decisión per-user OAuth (cambio crítico vs s67)

EjeService Account (s67)Per-user OAuth (s68 · este ADR)
Tokens en D11 fila singleton (id = 1)1 fila por team_member (Edu / Dani / Txell)
Setup inicialEdu autoriza una vez, todos heredanCada uno autoriza una vez (~30 s)
Calendar — quién es organizerSiempre Edu (raro si task asignada a Dani+Txell)El creador real de la task. Otros como attendees
Mail — visibilidadSolo emails de Edu (resto ven los de Edu, sin sentido)Cada uno ve los suyos (único viable)
Página settings1 botón “Conectar Zoho”3 cards de estado (una por persona)

El driver del cambio es Mail: Service Account es incompatible con cuentas de email separadas por persona. Una vez forzados a per-user OAuth para Mail, mantener Service Account para Calendar es asimétrico y empeora UX (invitaciones aceptadas, organizer raro).

Plan operativo en una sola fase

Toda la Fase B revisada se entrega en una sesión (estimado ~10 h netas). El concepto de “fases C/D” del ADR s67 desaparece.

Pasos

  1. Migration 0027 — altera zoho_oauth_tokens (singleton → multi-user con team_member TEXT PRIMARY KEY) + crea task_calendar_events + task_emails.
  2. Lib functions/_lib/zoho.ts acepta userKey (team_member) en todas sus funciones. OAuth handlers per-user (state lleva quién autoriza).
  3. Backend nuevos endpoints: /api/zoho/calendar POST + /api/zoho/mail/search GET + /api/zoho/mail/compose-url GET + /api/tasks/{id}/links/{type} CRUD.
  4. Frontend TaskModal con secciones “Eventos vinculados” + “Emails vinculados” replicando el patrón existente de “Notas del Cuaderno”.
  5. Cards (KanbanMini, TaskTableView, TaskKanban) con badges numéricos.
  6. Widget Fase A: ZohoAgendaWidget → ZohoCalendarWidget (quita sección “Tareas Zoho”, mantiene agenda + filtro).
  7. Settings UI con 3 cards de estado de conexión por persona.

Scopes Zoho actualizados

  • Quitar (eran en ADR s67): ZohoMail.tasks.READ, ZohoMail.tasks.CREATE.
  • Añadir: ZohoCalendar.event.CREATE, ZohoMail.messages.READ.
  • Mantener: ZohoCalendar.event.READ, ZohoCalendar.calendar.READ, ZohoMail.accounts.READ.

Cada miembro debe re-autorizar tras el cambio de scopes.

Alternativas consideradas y descartadas

Alternativa 1 · Continuar ADR s67 (Zoho = SoT también para tasks)

Descartada tras observación de Edu. KanbanMini + página /tasks ya supera funcionalmente a Zoho Tasks. Migrar 27 tasks D1 → Zoho introduciría regresión UX (sin drag-drop, sin notas vinculadas, sin attachments en Zoho Tasks free) y dependencia en una herramienta inferior para el caso de uso del equipo. La complejidad de Fase C/D del ADR s67 (sync bidireccional + conflict resolution) deja de tener justificación.

Alternativa 2 · Sync automática task → Calendar event (puente silencioso)

Descartada. Propuesta por mí (assistant) tras el pivot inicial: cuando una task tiene due_date, generar evento en Calendar automáticamente. Edu la rechazó explícitamente: “sin puente, acciones explícitas desde la task”. Razones implícitas: control del usuario sobre qué se publica en Calendar, evitar ruido en agenda con tasks que no son reuniones, sin necesidad de conflict resolution en sync invertida.

Alternativa 3 · Mantener Service Account + emails compartidos

Inviable técnicamente. Service Account de Edu solo puede acceder a la INBOX de Edu. Mostrar emails de Edu a Dani y Txell rompe principio de privacidad mínima y no aporta valor (cada uno opera sus propios emails). Sin solución técnica vía Zoho — sería per-user obligatorio aunque quisiéramos mantener el modelo singleton.

Alternativa 4 · Esperar a tener feedback de uso de Fase A antes de pivotar

Descartada. Fase A solo lleva 1 día en PROD. El coste de mantener el rumbo del ADR s67 supera el de pivotar ahora: pivotar ahora = ~10 h de trabajo + cancela Fases C/D del ADR original (~3-4 días); seguir con s67 = invertir 3-4 días más en una arquitectura que sabemos subóptima.

Consecuencias

Esperadas positivas

  • KanbanMini conserva su UX completa (drag-drop, notas vinculadas, attachments, multi-assignee).
  • Cada miembro ve sus eventos en su calendar y sus emails en su buzón (UX natural).
  • Eventos creados desde el workspace aparecen en el calendar de todos los implicados vía mecanismo de invitación nativo de Zoho.
  • Cancelación de Fase C (polling sync inverso) y Fase D (edición bidireccional + conflict resolution) del ADR s67 = ~3-4 días de trabajo evitados.
  • Mail entra al alcance (no estaba en s67) sin coste extra de arquitectura (lo trae per-user OAuth necesariamente).
  • Task = hub centraliza la trazabilidad: en una sola vista se ven los eventos y emails relacionados con el trabajo en curso.

Esperadas negativas / riesgos

  • Setup ~30 s adicional para Dani y Txell (autorizar OAuth la primera vez).
  • 3 tokens en D1 a refrescar en lugar de 1. Manejo de errores OAuth granular por persona.
  • Si Dani crea un evento desde una task con Txell como assignee, Txell recibe invitación Zoho que debe aceptar (1 click). UX más explícita pero un paso extra vs “aparece sin más en mi calendar”.
  • Mail picker con filtro requiere endpoint /api/zoho/mail/search con paginación, manejo de carpetas y rate limits Zoho.

Mitigaciones

  • Status badge per-user en /settings/integrations/zoho deja explícito quién está conectado y quién no.
  • Lib getValidAccessToken(env, userKey) falla con mensaje claro ("Zoho no autorizado para {userKey} · ve a /settings/integrations/zoho") si la persona no autorizó.
  • Si Dani no ha autorizado y otro miembro le asigna una task con evento Calendar: el evento se crea desde el creador (que sí está autorizado), Dani recibe invitación por email (no necesita autorización Zoho para verla en su cliente de email habitual).

Criterios de éxito (medibles a 3 meses)

  • Adopción: ≥3 tasks del workspace tienen evento Calendar vinculado por semana.
  • Adopción Mail: ≥5 tasks del workspace tienen email vinculado por mes.
  • Setup completo: los 3 miembros del staff han pasado el OAuth per-user en los primeros 7 días tras release.
  • 0 incidentes de “no veo mis emails en el picker” / “el evento no apareció” en 90 días.
  • Subjetivo Edu: “la task es ahora el sitio natural donde miro qué hay que hacer y qué se ha movido alrededor”.

Estado

  • 17-05-2026 (s68 mañana): ADR firmado por Edu. Arranque inmediato de la implementación en la misma sesión.
  • Marca decision--20260516--integracion-zoho-workspace como superseded mediante actualización separada del front-matter.

Reapertura futura

  • Si Zoho añade webhooks para Mail/Calendar: ADR nuevo de migración polling-style → push.
  • Si emerge necesidad real de bidireccional task↔calendar (caso de uso aún no identificado): ADR nuevo.
  • Mantener este como ADR vigente para cualquier evolución de la integración Calendar+Mail.

Véase también

  • [[decision—20260516—integracion-zoho-workspace]]
  • [[entity—ops—catalogo-servicios-externos]]
  • [[decision—20260516—auditoria-simplificacion-servicios-auxiliares]]