Skip to main content
Traducción. Autoritativo: fs-crm-0004-timeline.md.

Contexto

Esta es la feature que justifica el módulo, y lo único de la suite que los competidores no pueden copiar estructuralmente. Una herramienta de loyalty ve puntos. Una plataforma de correo ve envíos. Un CRM ve notas. Solo un sistema donde los tres comparten un correlation_id (DEC-E7) puede mostrar la cadena causal en vez de tres listas ordenadas por fecha. Es una proyección, no una tabla. Materializarla sería una quinta copia de datos que ya existen, mantenida en sincronía por un job que va a driftear. Se mezcla en vivo desde las fuentes al leer. La trampa de permisos es real: una vista unida es justamente donde se olvida un permiso. Un agente sin core.contacts.read_national_id debe ver el documento enmascarado también acá, y uno sin loyalty.ledger.read no debe enterarse de saldos por el timeline.

Alcance (normativo)

  • Una lectura ordenada por mezcla sobre core.tracked_events, loyalty.ledger_transactions, messaging.sends con estado, crm.notes y crm.activities.
  • Paginación por cursor sobre occurred_at.
  • Filtrado por fuente y por rango de fechas.
  • Renderizado de cadena: las entradas que comparten correlation_id se agrupan como una secuencia causal.
  • Filtrado por permisos por fuente.

Fuera de alcance (normativo)

  • Materializar el timeline. Se calcula en lectura, siempre.
  • Reportería entre contactos. Esto es la historia de un contacto.
  • Editar cualquier cosa desde el timeline. Es una superficie de lectura.
  • Streaming en tiempo real — F1b si se pide.

Comportamiento (normativo)

  1. El timeline se calcula en lectura desde sus fuentes. No hay tabla, ni vista materializada, ni job de sincronización.
  2. Cada fuente se consulta con su predicado de clave de partición. Por eso el rango de fechas es obligatorio, no con valor por defecto, y la API lo dice — un timeline sin rango es un scan completo de las tablas más grandes de la plataforma.
  3. Las entradas se ordenan por mezcla sobre occurred_at descendente, con paginación por cursor. Las fuentes se consultan en paralelo y se mezclan, nunca se traen completas para ordenar después.
  4. Cada fuente se filtra por su propio permiso de forma independiente. Quien no tenga el permiso de una fuente no ve sus entradas, y no se entera de que existen por un hueco o un conteo.
  5. Los campos sensibles se enmascaran según los permisos de quien lee, exactamente como en el módulo dueño (DEC-A4). El timeline nunca se vuelve el atajo.
  6. Las entradas que comparten correlation_id se agrupan como una cadena con su orden causal preservado. Ese agrupamiento es la feature; una lista plana por fecha es lo que ya ofrece cualquier otra herramienta.
  7. Las lecturas entre módulos van por el camino público de lectura de cada uno, nunca sus tablas. El límite se mantiene incluso para leer.
  8. Una fuente no disponible degrada con gracia: el timeline renderiza el resto y dice cuál falta. Una caída de mensajería no debería dejar en blanco la pantalla de un agente.

Datos (normativo)

Ninguna tabla. Esta feature posee una proyección y ningún almacenamiento — que es justamente el punto.

API (normativo)

El permiso único gatea el acceso al timeline; cada fuente se filtra además por su propio permiso. Tener solo crm.timeline.read muestra notas y actividades y nada más.

Criterios de aceptación (normativo)

  1. Primera página p95 <1 s para un contacto con 5 000 eventos entre todas las fuentes.
  2. Un request sin rango de fechas se rechaza con error tipado.
  3. Toda consulta de fuente lleva su predicado de partición — verificado capturando las consultas.
  4. Quien no tenga el permiso de una fuente no ve sus entradas ni las puede inferir por conteos o huecos.
  5. Un documento de identidad aparece enmascarado para quien no tenga core.contacts.read_national_id.
  6. Las entradas que comparten correlation_id se renderizan como una cadena en orden causal — el caso compra → puntos → correo → apertura → canje, end to end.
  7. Una fuente que devuelve error degrada con gracia y nombra la fuente faltante.
  8. La paginación por cursor devuelve cada entrada exactamente una vez entre páginas, sin duplicados en los bordes.
  9. Negativo: ninguna tabla de otro módulo se lee directamente, impuesto por dependency-cruiser.

Eventos (normativo)

Ninguno. Una superficie de lectura no emite nada.

Ejecución

Un solo slice, comando síncrono. Fuentes consultadas en paralelo con timeout por fuente; mezcla y filtrado de permisos en backend/api.

Preguntas abiertas

Changelog

Registro de entrega

Aún no implementado.