Ciclo de vida de tickets
Estados de los tickets
Sección titulada «Estados de los tickets»| Estado | Cadena almacenada | Descripción |
|---|---|---|
backlog |
backlog |
Registrado, aún sin triar en la cola activa |
open |
open |
Triado y listo para trabajarlo, sin empezar |
inProgress |
inProgress |
En curso |
blocked |
blocked |
Empezado pero bloqueado por algo externo |
inReview |
inReview |
Trabajo terminado, a la espera de revisión |
done |
done |
Terminado correctamente (terminal) |
failed |
failed |
Terminado con un error (terminal) |
cancelled |
cancelled |
Abandonado (terminal) |
Los valores de estado se almacenan en camelCase (inProgress, inReview), nunca in_progress. Un valor almacenado nulo se interpreta como open; un valor desconocido lanza una excepción en lugar de convertirse.
tryParseLoose acepta los alias que proporciona el agente, ignorando las mayúsculas y minúsculas y - / _ / espacio:
| Estado | Alias aceptados |
|---|---|
open |
todo, ready, pending, new |
inProgress |
doing, started, active, wip |
blocked |
stuck |
inReview |
review, reviewing |
done |
closed, complete, completed, finished |
failed |
error, errored |
cancelled |
canceled, abandoned, dropped |
Transiciones de estado
Sección titulada «Transiciones de estado»backlog ──→ open ──→ inProgress ──→ inReview ──→ done │ │ │ │ └──→ failed ┘ └──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failedEste grafo vincula solo a los agentes y a la automatización. TicketWorkflowService.transitionStatus lo comprueba cuando force es false —las herramientas MCP y los reconciliadores. Una transición ilegal ahí se registra y se ignora. Todos los cambios de estado que hace un usuario desde la interfaz pasan force: true y omiten el grafo por completo, así que una persona puede mover un ticket a cualquier estado que liste el selector (todos los estados salvo failed), incluido reabrir uno terminal. Los agentes marcan failed con fail_ticket.
Un estado siempre puede transicionar a sí mismo. Los estados terminales no tienen aristas de salida.
| Desde | Destinos permitidos |
|---|---|
backlog |
open, cancelled |
open |
inProgress, blocked, cancelled |
inProgress |
blocked, inReview, done, failed, cancelled |
blocked |
inProgress, cancelled |
inReview |
inProgress, done, failed, cancelled |
done |
— (terminal) |
failed |
— (terminal) |
cancelled |
— (terminal) |
Prioridades de los tickets
Sección titulada «Prioridades de los tickets»Se almacena como un entero en la escala nativa 0–4 de Linear. Un valor desconocido se interpreta como none.
| Prioridad | Se almacena como |
|---|---|
none |
0 |
urgent |
1 |
high |
2 |
medium |
3 |
low |
4 |
Proveedores de tickets
Sección titulada «Proveedores de tickets»Un valor almacenado desconocido o nulo se interpreta como local.
| Proveedor | Estado |
|---|---|
local |
Se almacena por completo en la base de datos del espacio de trabajo; es el valor predeterminado |
linear |
Propiedad del sistema remoto, con un espejo local |
Los adaptadores de sincronización están en packages/cc_infra/lib/src/tickets/sync/ para Linear,
GitHub Issues, Jira Cloud y ClickUp. GitHub Issues es solo un proveedor de
adaptadores de sincronización (vendorId: github): no es un valor de
TicketProvider, así que una incidencia de GitHub descargada se almacena con
originKind: externalSync y provider: local de forma predeterminada. La
asignación del proveedor está en ticket_sync_links.
Origen de los tickets
Sección titulada «Origen de los tickets»createTicket escribe manual. Una descarga de un proveedor escribe
externalSync. Los hijos delegados se quedan en manual.
| Valor | Significado |
|---|---|
manual |
Valor predeterminado en createTicket (filas humanas, de agente y delegadas) |
externalSync |
Lo establece TicketSyncEngine en una descarga de un proveedor |
Campos de los tickets
Sección titulada «Campos de los tickets»El agregado abarca dos aspectos en una sola fila. El espejo (proveedor, claves, título, descripción, prioridad, etiquetas, estados, marcas de tiempo) es una caché para un proveedor remoto y una actualización lo reescribe por completo. La superposición (asignación, delegación, espacio, padre, proyecto, PR enlazadas) es exclusiva de Control Center y una actualización nunca la toca.
| Campo | Tipo | Descripción |
|---|---|---|
id |
String |
UUID v4 |
workspaceId |
String |
Espacio de trabajo propietario |
provider |
TicketProvider |
Backend propietario de los datos canónicos (local de forma predeterminada) |
externalKey |
String? |
Clave nativa del proveedor (p. ej., LIN-123). El createTicket local escribe aquí un UUID aparte, no el id de la fila |
url |
String? |
URL web en el proveedor remoto |
title |
String |
Resumen corto; no puede estar vacío |
description |
String? |
Cuerpo Markdown más largo |
priority |
TicketPriority |
none de forma predeterminada |
labels |
List<String> |
Etiquetas de formato libre |
status |
TicketStatus |
Estado normalizado canónico |
rawStatus |
String? |
El nombre de estado nativo del proveedor remoto, conservado para mostrarlo sin pérdida |
externalSyncedAt |
DateTime? |
Última sincronización multiproveedor; nula hasta la primera. La asignación por proveedor está en ticket_sync_links |
parentTicketId |
String? |
Padre en el árbol de delegación o desglose |
projectId |
String? |
Proyecto propietario; nunca se envía a un remoto |
assignedAgentId |
String? |
Id del principal asignado (solo metadatos: asignar no despacha nada) |
assigneeType |
PrincipalType |
Qué tipo de principal nombra assignedAgentId: user o agent (agent de forma predeterminada) |
createdByType |
PrincipalType? |
Tipo de principal que creó el ticket (user o agent); nulo en las filas heredadas o del sistema |
createdById |
String? |
Id del principal que lo creó |
assignedTeamId |
String? |
Equipo asignado (solo metadatos) |
delegatedByAgentId |
String? |
Agente que delegó este ticket |
delegationDepth |
int |
Profundidad en la cadena de delegación; la raíz es 0 y se comprueba contra el límite de profundidad máxima |
delegationRootTicketId |
String? |
Raíz de la cadena de delegación; nula en un ticket raíz |
spaceId |
String? |
Conversación de la que surgió el ticket, o el espacio del agente que delega |
errorMessage |
String? |
Se establece cuando status es failed |
linkedPrIds |
List<String> |
Ids de nodo de las PR a las que está enlazado este ticket |
metadata |
Map<String, dynamic> |
Bolsa de formato libre |
createdAt |
DateTime |
Momento de creación |
startedAt |
DateTime? |
Cuándo empezó el trabajo |
blockedAt |
DateTime? |
Cuándo se bloqueó el ticket |
cancelledAt |
DateTime? |
Cuándo se canceló el ticket |
completedAt |
DateTime? |
Cuándo se completó el ticket correctamente |
finishedAt |
DateTime? |
Cuándo alcanzó el ticket cualquier estado terminal |
updatedAt |
DateTime |
Última mutación (actualización del espejo o cambio en la superposición) |
version |
int |
Contador de concurrencia optimista, incrementado en cada mutación |
originKind |
TicketOriginKind |
Cómo se creó el ticket (manual de forma predeterminada) |
collaborators |
List<TicketCollaborator> |
El repositorio los hidrata a petición |
Derivados: isTerminal, isRemote (provider != local), isAssignedToUser y displayKey (externalKey ?? id; por eso los tickets locales muestran el UUID aparte del momento de creación, no el id de la fila).
Relaciones entre tickets
Sección titulada «Relaciones entre tickets»Las aristas almacenadas son filas direccionales en ticket_links:
| Tipo | Cadena almacenada | Significado |
|---|---|---|
blocks |
blocks |
El origen bloquea al destino |
relatesTo |
relates_to |
Relación simétrica |
duplicateOf |
duplicate_of |
El origen es un duplicado del destino |
El vocabulario que hablan la interfaz y ticket_relation es TicketRelationKind, derivado de una arista almacenada y de en qué extremo se sitúa el sujeto:
| Tipo | Derivado de |
|---|---|
blockedBy |
Una fila blocks en la que el sujeto es el destino |
blocking |
Una fila blocks en la que el sujeto es el origen |
relatedTo |
Una fila relates_to (simétrica) |
duplicateOf |
Una fila duplicate_of en la que el sujeto es el origen |
duplicatedBy |
Una fila duplicate_of en la que el sujeto es el destino |
subIssueOf |
tickets.parent_ticket_id en el sujeto |
parentOf |
tickets.parent_ticket_id en el otro ticket |
Los enlaces de padre y subincidencia no son filas de ticket_links: viven en tickets.parent_ticket_id.
Eventos de dominio
Sección titulada «Eventos de dominio»Estas ocho clases viven en packages/cc_domain/lib/core/domain/events/ticketing_events.dart. No hay TicketStarted, TicketDelegated, TicketCollaboratorAdded ni ExternalTicketWebhookReceived.
| Evento | Se dispara cuando |
|---|---|
TicketCreated |
Se crea el ticket |
TicketAssigned |
Se asigna el ticket a un principal o a un equipo |
TicketCompleted |
El trabajo termina correctamente |
TicketFailed |
El trabajo falla |
TicketCancelled |
Se cancela el ticket |
TicketStatusChanged |
Cualquier cambio de estado |
TicketReassigned |
Se reasigna el ticket |
TicketDetailsUpdated |
Cambian el título, la descripción, la prioridad o las etiquetas |
TicketAssigned es una señal de auditoría y notificación y un disparador de pipeline. Asignar un ticket a un agente no inicia ninguna ejecución: no hay ningún despachador de tickets que vigile assignedAgentId. La asignación a un equipo es la excepción: TeamRoutingService despacha al líder del equipo. Añadir un colaborador escribe una fila y no publica nada. Un webhook de proveedor se aplica a través de TicketSyncEngine sin ningún evento de dominio.
Punto de paso de las mutaciones
Sección titulada «Punto de paso de las mutaciones»TicketWorkflowService posee todas las mutaciones.
| Mecanismo | Detalle |
|---|---|
| Punto de paso único | Todas las mutaciones pasan por _mutate, que carga la fila y llama a _assertWorkspace antes de aplicarla |
| Aislamiento de espacios de trabajo | _assertWorkspace lanza WorkspaceMismatchException cuando el workspaceId de la fila cargada no coincide con el de quien llama |
| Concurrencia optimista | _mutate escribe con expectedVersion establecido al version que acaba de leer y reintenta ante una ConcurrencyConflictException releyendo el estado actualizado |
| Reclamación exclusiva | tryCheckout / releaseCheckout; un segundo agente que reclame un ticket ya reclamado recibe una CheckoutConflictException |
| Guarda de transición | canTransitionTo, aplicada solo cuando force es false |