Auto-eventos Zoho Calendar por ciclo de vida de task (s68)
Auto-eventos Zoho Calendar por ciclo de vida de task (s68)
Qué hace
Cada operación CRUD sobre /api/tasks genera o elimina automáticamente eventos all-day en Zoho Calendar para los asignados de la task, sincronizados con su fase de ciclo de vida. El flujo es transparente para el usuario: los eventos aparecen en su calendario Zoho sin que nadie pulse nada.
Reglas de negocio
| Acción sobre la task | Eventos generados / eliminados |
|---|---|
| Crear con ≥1 assignee | Crea auto_start (día = hoy) |
Crear con due_date | Además crea auto_end_planned (día = due_date) |
Cerrar (status → done) | Elimina auto_end_planned + crea auto_end_real (día = hoy) |
Reabrir (done → otro) | Elimina auto_end_real + recrea auto_end_planned (si due_date) |
Cambiar due_date (task abierta) | Elimina auto_end_planned viejo + crea nuevo con nueva fecha |
| Borrar task | Elimina todos los eventos auto_* en Zoho |
Arquitectura
Librería principal
functions/_lib/task-zoho-auto.ts exporta tres hooks públicos:
export async function onTaskCreated(env, task, actor): Promise<void>
export async function onTaskUpdated(env, task, oldStatus, newStatus, oldDueDate, newDueDate, actor): Promise<void>
export async function onTaskDeleted(env, taskId): Promise<void>
Cada hook se llama desde el handler correspondiente vía context.waitUntil(...) para ejecutarse en background sin bloquear la respuesta HTTP (~500 ms de latencia Zoho).
Modelo de evento Zoho
- 1 call POST por evento, con
organizer(el actor que crea la task o el primer assignee identificable) +attendees(resto de assignees con token Zoho conocido). - Los attendees reciben invitación nativa Zoho y la aceptan una vez por task.
- Formato all-day:
dateandtime.start = "YYYYMMDD"(sin hora niZ),isallday: true.
Organizer resolution
actor con token Zoho → primer assignee con token Zoho → skip (silencioso)
Si ningún assignee tiene token Zoho autorizado, no se crea ningún evento.
Persistencia D1
Los eventos auto se registran en task_calendar_events con el nuevo campo link_type:
link_type TEXT NOT NULL DEFAULT 'manual'
CHECK (link_type IN ('manual', 'auto_start', 'auto_end_planned', 'auto_end_real'))
- Migration 0028 (
migrations/0028_task_calendar_link_type.sql): añadelink_typeconDEFAULT 'manual'(retrocompatible) e índice(task_id, link_type). - Al borrar una task el CASCADE FK elimina las filas D1; los eventos Zoho remotos se eliminan explícitamente en
onTaskDeletedantes del DELETE.
Hooks de API
| Endpoint | Handler | Hook invocado |
|---|---|---|
POST /api/tasks | functions/api/tasks/index.ts | onTaskCreated |
PUT /api/tasks/[id] | functions/api/tasks/[id]/index.ts | onTaskUpdated |
DELETE /api/tasks/[id] | functions/api/tasks/[id]/index.ts | onTaskDeleted |
Todos los Env de estos handlers extienden ZohoEnv y reciben ZOHO_CLIENT_ID / ZOHO_CLIENT_SECRET.
UI
Badges en vistas de task
Los badges de eventos en TaskCalendar, TaskKanban, TaskTableView y KanbanMini pasan de "N ev" a texto legible pluralizado:
- 1 evento →
"1 evento" - N eventos →
"N eventos"
TaskModal — panel de enlaces Zoho
En TaskZohoLinks.tsx, los eventos con link_type que empieza por auto_ muestran la etiqueta itálica auto en lugar del botón Desvincular. Los eventos auto no se pueden desvincular manualmente desde la UI; se gestionan solos por el ciclo de vida.
Limitaciones conocidas
- Cambiar assignees tras crear la task no propaga a eventos ya creados en Zoho.
- Si Zoho falla al crear/borrar un evento, se loguea a consola (
console.warn) pero la operación sobre la task continúa normal (fail-silent). - El modelo de organizer único (1 call, attendees como invitados) supone que los assignees aceptan la invitación Zoho una vez por task. Si esto molesta en el futuro, refactorizar a N calls per-user independientes.
Variables de entorno requeridas
| Variable | Descripción |
|---|---|
ZOHO_CLIENT_ID | OAuth2 client ID de la app Zoho |
ZOHO_CLIENT_SECRET | OAuth2 client secret |
DB | D1 Database binding (ya existía) |
Véase también
- [[entity—zoho—service—task-zoho-auto]]
- [[decision—20260517—integracion-zoho-calendar-mail-pivot]]
- [[entity—d1—table—task-calendar-events]]