Skip to main content

Actividad del ticket

Cada ticket de soporte registra su propia línea de tiempo de cambios (audit log), puede marcarse como inválido sin perderse (soft-delete) y admite la generación de un plan de mejora con IA a partir de su contenido. Estas tres capacidades viven sobre chat_tickets y su tabla de historial chat_ticket_status_history.

Audit log / actividad

Toda la actividad del ticket se guarda en chat_ticket_status_history, una tabla append-only (migración 107). Cada fila describe un único cambio: qué campo cambió, su valor anterior y nuevo, cuándo, quién lo hizo y de qué fuente proviene.
chat_ticket_status_history (
  id, ticket_id, zendesk_ticket_id,
  field, previous_value, new_value,
  changed_at, changed_by_user_id,
  source, zendesk_audit_id, metadata
)
ColumnaDescripción
fieldCampo o evento que cambió (ver abajo)
previous_value / new_valueValores antes y después del cambio
changed_by_user_idActor del cambio (FK a users), si se conoce
sourceOrigen: webhook | import | backfill | manual
zendesk_audit_idID del audit de Zendesk, único para que el backfill sea idempotente

Qué se registra

  • Creación del ticket — al crear un ticket se escribe una entrada con field = 'created'.
  • Cambios de campo — el update() del ticket compara contra AUDITABLE_FIELDS y escribe una entrada por cada campo que cambió, junto con el actor. Campos auditados: title, status, priority, assignee, resolution_team, zone, category, destination, payer, repair_status, estimated_cost_range, actual_cost, expected_resolution_at e internal_notes.
  • Transiciones desde Zendesk — el webhook, la importación y el backfill escriben las transiciones venidas de Zendesk con su source correspondiente (webhook / import / backfill).
status, priority y assignee se editan desde el detalle (sección Gestión, guardado inmediato). Ya estaban en AUDITABLE_FIELDS, así que estos cambios manuales quedan registrados con source = 'manual' y el actor que los hizo.
La escritura del historial es best-effort: si falla recordFieldChanges el update() del ticket no se rompe, solo se loguea el error.

Endpoint

GET /chat/tickets/:id/history
Devuelve { entries: [...] } ordenado por changed_at. Solo accesible para Admin/Moderator. El servicio resuelve el nombre del actor (changedByName) con un join contra users (usa display_name, nombre completo o email); este campo no es una columna, se calcula en la respuesta.

Frontend

TicketHistoryTimeline muestra la actividad como un feed estilo WhatsApp, ordenado del más reciente al más antiguo:
  • "X creó el ticket" (evento created).
  • "X dio la primera respuesta" (evento first_response).
  • "X cambió <Campo>: valor anterior → valor nuevo" para cambios de campo.
Las entradas se colorean por tipo (creación, primera respuesta, apertura, cierre) y marcan las reaperturas (transición de estado cerrado → abierto) con una etiqueta. Cuando no hay actor conocido, se muestra un fallback según la fuente (Zendesk, Sincronización).
La métrica automática de primera respuesta y los dashboards de actividad por equipo/persona son follow-up.

Soft-delete de tickets inválidos

En Zendesk los tickets no se pueden eliminar. Para que CX pueda “borrar” tickets de basura, pruebas o duplicados, se implementa un soft-delete sobre chat_tickets (migración 135): se marcan inválidos, se ocultan de listas y KPIs, pero quedan recuperables. No toca Zendesk.
ColumnaDescripción
invalidboolean, default false. true = oculto de listas y KPIs
invalid_reasonMotivo opcional
invalidated_atTimestamp de la invalidación (NULL si invalid = false)
invalidated_byUsuario que lo marcó inválido (FK a users)

Endpoints

PATCH /chat/tickets/:id/invalidate   { reason?: string }
PATCH /chat/tickets/:id/restore
  • invalidate setea invalid = true con el motivo, el timestamp y el usuario actual.
  • restore revierte los cuatro campos a su estado inicial (vuelve a mostrar el ticket).
Las listas y KPIs filtran por invalid = false por defecto. Para revisar los inválidos se usa el filtro onlyInvalid. Un índice parcial sobre invalid = true mantiene esas consultas baratas.

UI

Acción “Marcar inválido” (con motivo opcional), banner en el detalle del ticket inválido con opción de restaurar, y filtro “Solo inválidos” en la lista.

Planes de mejora con IA

CX puede pedir que la IA genere un plan de mejora por ticket a partir de su contenido. Se persiste el último plan generado sobre chat_tickets (migración 136), de uso libre y sin tabla de cuotas.
ColumnaDescripción
improvement_planÚltimo plan generado (texto en español)
improvement_plan_atTimestamp de la última generación

Endpoint

POST /chat/tickets/:id/improvement-plan
Reúne el contexto del ticket (título, descripción, categoría, equipo, zona) más sus comentarios y lo envía al AiService (Claude). El prompt pide 3-5 viñetas accionables en español: causa raíz probable, acción correctiva y cómo prevenir que se repita. Devuelve { plan, generatedAt } y guarda ambos valores en el ticket. Cada llamada reemplaza el plan anterior.

UI

TicketImprovementPlanCard muestra el último plan persistido con un botón Generar / Regenerar. Tras generar, el plan recién creado se muestra de inmediato (antes del refetch) y se indica la fecha de generación.