CreaRack-SL

Incidente s68 — Eventos Zoho Calendar no se borraban (ETAG_MISSING)

Resumen

La noche del 17-05-2026 (sesión s68) se detectó que al cerrar una tarea en CreaRack, el evento correspondiente seguía visible en Zoho Calendar aunque la fila en D1 había sido borrada correctamente. La causa raíz fue la ausencia del header etag en la petición DELETE al API de Zoho Calendar.


Síntoma

  • Tarea cerrada → onTaskUpdated dispara deleteEventsByType → borra fila en zoho_calendar_links (D1) ✅
  • Petición DELETE a Zoho API → respuesta 400 ETAG_MISSING → evento permanece en Zoho ❌
  • El catch genérico silenciaba el error; la discrepancia pasaba desapercibida hasta inspección visual del calendario.

Respuesta exacta de Zoho:

{"error_code": "ETAG_MISSING", "message": "ETAG_MISSING"}

Causa raíz

La Zoho Calendar API implementa concurrencia optimista vía ETag: cada recurso tiene una versión (etag) que debe enviarse en el header etag de todas las operaciones de escritura (DELETE, PUT). Sin él, el servidor rechaza la petición con HTTP 400.

El código anterior en deleteEventsByType y en el bloque de onTaskDeleted omitía este header:

// ANTES (bug)
await zohoFetch(env, member, url, { method: 'DELETE' });

Fix aplicado (commit e779e6a)

Se introdujo la función fetchEventEtag que realiza un GET previo al evento para obtener el ETag actual, y luego lo incluye en el DELETE:

// DESPUÉS (fix)
const etag = await fetchEventEtag(env, member, row.calendar_uid, row.zoho_event_uid);
if (!etag) {
  console.warn(`[task-zoho-auto] DELETE ${row.zoho_event_uid}: no etag (ya borrado en Zoho)`);
  continue; // skip — evento ya no existe
}
await zohoFetch(env, member, url, { method: 'DELETE', headers: { etag } });

El patrón también cubre el caso de evento ya borrado en Zoho: si el GET devuelve 404 (!res.ok), fetchEventEtag retorna null y el DELETE se omite, evitando errores espurios.


Impacto

DimensiónDetalle
SeveridadMedia — datos inconsistentes entre D1 y Zoho Calendar (no bloqueo de prod)
VentanaIndeterminada (bug presente antes de s68, detectado esa noche)
AfectadosTodos los usuarios con integración Zoho Calendar activa al cerrar tareas
Pérdida de datosNo — los eventos sólo quedaban “huérfanos” en Zoho; ningún dato interno perdido

Acciones tomadas

  1. Diagnóstico vía endpoint de debug que retornó el body ETAG_MISSING.
  2. Fix en functions/_lib/task-zoho-auto.ts — patrón GET → ETag → DELETE.
  3. Commit directo a main e779e6a por @Esquembri, co-autorado con Claude Opus 4.7.
  4. Limpieza manual de eventos huérfanos en Zoho (si necesaria, fuera de scope del commit).

Lecciones aprendidas

  • La Zoho Calendar API requiere ETag en todas las operaciones de escritura. Documentar este patrón para futuros endpoints (PUT de actualización de evento también lo necesitará).
  • El catch genérico en deleteEventsByType ocultaba el 400 de Zoho. El nuevo código añade console.warn explícito con status + body para detectar fallos silenciosos.
  • Considerar un test de integración que valide que el DELETE incluye el header etag.

Véase también

  • [[entity—zoho—service—task-zoho-auto]]
  • [[entity—zoho—schema—zohoevent]]
  • [[entity—zoho—schema—zohoenv]]
  • [[entity—zoho—schema—zohotaskuser]]
  • [[entity—zoho—schema—zohogroup]]