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
ZohoAgendaWidgeten bento home leyendo Zoho Calendar + Zoho Tasks via Service Account de Edu. - KanbanMini intacto leyendo D1 (Ruta Y coexistencia).
- Migration
0026_create_zoho_oauth.sqlaplicada en D1 remoto (tabla singletonzoho_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:
- 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.
- 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.
- 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 task | Comportamiento |
|---|---|
+ Crear evento | API 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 email | Picker con filtro from/subject que lista los últimos N emails del usuario. Selección → vínculo persistente en D1. |
+ Nuevo email | Deep-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)
| Eje | Service Account (s67) | Per-user OAuth (s68 · este ADR) |
|---|---|---|
| Tokens en D1 | 1 fila singleton (id = 1) | 1 fila por team_member (Edu / Dani / Txell) |
| Setup inicial | Edu autoriza una vez, todos heredan | Cada uno autoriza una vez (~30 s) |
| Calendar — quién es organizer | Siempre Edu (raro si task asignada a Dani+Txell) | El creador real de la task. Otros como attendees |
| Mail — visibilidad | Solo emails de Edu (resto ven los de Edu, sin sentido) | Cada uno ve los suyos (único viable) |
| Página settings | 1 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
- Migration
0027— alterazoho_oauth_tokens(singleton → multi-user conteam_member TEXT PRIMARY KEY) + creatask_calendar_events+task_emails. - Lib
functions/_lib/zoho.tsaceptauserKey(team_member) en todas sus funciones. OAuth handlers per-user (state lleva quién autoriza). - Backend nuevos endpoints:
/api/zoho/calendarPOST +/api/zoho/mail/searchGET +/api/zoho/mail/compose-urlGET +/api/tasks/{id}/links/{type}CRUD. - Frontend TaskModal con secciones “Eventos vinculados” + “Emails vinculados” replicando el patrón existente de “Notas del Cuaderno”.
- Cards (KanbanMini, TaskTableView, TaskKanban) con badges numéricos.
- Widget Fase A:
ZohoAgendaWidget→ZohoCalendarWidget(quita sección “Tareas Zoho”, mantiene agenda + filtro). - 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/searchcon paginación, manejo de carpetas y rate limits Zoho.
Mitigaciones
- Status badge per-user en
/settings/integrations/zohodeja 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-workspacecomosupersededmediante 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]]
Referenciado desde
- Auto-eventos Zoho Calendar por ciclo de vida de task (s68)
- Endpoints vínculos task ↔ Zoho Calendar (GET/POST/DELETE /api/tasks/{id}/links/calendar)
- Endpoints vínculos task ↔ Zoho Mail (GET/POST/DELETE /api/tasks/{id}/links/email)
- Integración Zoho Calendar + Mail con OAuth per-user (s68)
- Lib staff.ts — Resolución de TeamMember desde CF Access
- resolveActorEmail — resolución de identidad CF Access con fallback JWT
- Runbook · Integración Zoho ↔ Workspace · setup, autorización, troubleshooting
- staff.ts — Resolución de identidad CF Access → TeamMember
- Zoho OAuth — Modo admin ?as=X para pre-autorización de onboarding
- Zoho OAuth per-user: autorización individual por miembro del staff (s68)