Auditoría: el historial de transiciones como log de negocio
El snapshot responde dónde está el proceso, pero la pregunta que llega a soporte, a finanzas y al regulador es siempre la otra: por qué está así. Registrar cada transición como un hecho inmutable convierte el modelo en una crónica consultable del procedimiento, con quién lo movió, con qué evento y en qué instante. Esta lección define qué campos hacen útil ese registro, distingue la auditoría del abastecimiento de eventos, y muestra cómo el mismo historial responde investigaciones puntuales, alimenta métricas de tiempo por fase y permite reconstruir un estado para verificarlo.
Un sistema de negocio recibe dos clases de preguntas y solo suele estar preparado para una. La primera es de posición —en qué estado está este pedido— y la responde cualquier consulta a la columna correspondiente. La segunda es de historia —por qué está así, quién lo dejó ahí, cuándo, y qué pasó justo antes— y la responden, en la mayoría de las organizaciones, dos ingenieros rebuscando en registros de aplicación con marcas de tiempo desalineadas hasta reconstruir una hipótesis plausible. Esa asimetría no es inevitable: es el resultado de haber modelado el estado como un valor que se sobrescribe en lugar de como una sucesión de hechos que se añaden. Cambiar esa decisión cuesta una tabla y una escritura por transición, y transforma el sistema en algo capaz de explicarse a sí mismo.
- Registrar cada transición como hecho inmutable con causa, autor e instante.
- Definir los campos mínimos que hacen útil un historial para negocio y para operaciones.
- Diferenciar la auditoría de transiciones del abastecimiento de eventos como fuente de verdad.
- Explotar el historial para investigar casos concretos y para medir el proceso en agregado.
El hecho que se añade frente al valor que se sobrescribe
La escritura del snapshot es destructiva: cada actualización borra la posición anterior. Bastan unas líneas más, en la misma transacción, para conservar el rastro de cómo se llegó hasta aquí.
await tx.transicion.create({
data: {
procesoId,
secuencia: fila.revision + 1,
desde: anterior.value as string,
hacia: siguiente.value as string,
evento: evento.type,
cargaUtil: redactar(evento),
autorTipo: evento.actor?.tipo ?? 'sistema',
autorId: evento.actor?.id ?? null,
correlacionId: contexto.correlacionId,
modeloVersion: MODELO_VERSION,
ocurridoEn: new Date(),
},
})
Merece la pena señalar por qué esta escritura pertenece a la capa de persistencia y no a una acción de la máquina. Una acción que registrara la transición desde dentro obligaría al modelo a conocer la base de datos, rompería su pureza y haría imposible probarlo sin infraestructura; además fallaría en el caso más interesante, el del evento rechazado, porque si no hay transición tampoco se ejecuta ninguna acción y precisamente ese rechazo es lo que conviene anotar. Comparando el snapshot anterior con el siguiente, quien hospeda la máquina obtiene el hecho completo sin pedirle nada al modelo, y puede registrar por igual lo que cambió y lo que se negó a cambiar.
La tabla es de solo inserción y esa restricción no es un adorno: un historial que admite actualizaciones deja de ser evidencia. Conviene reforzarla con permisos que impidan modificar y borrar filas, porque el valor de un registro de auditoría es directamente proporcional a la imposibilidad de retocarlo, y un equipo que sepa que puede corregir el pasado terminará corrigiéndolo el día que un incidente resulte incómodo de explicar.
En el abastecimiento de eventos, la secuencia de hechos es la fuente de verdad y el estado se deriva reproduciéndola; aquí la fuente de verdad sigue siendo el snapshot y el historial es un registro paralelo. La distinción importa porque marca qué garantías se exigen: un registro de auditoría puede tolerar una pérdida puntual sin corromper el sistema, mientras que en el abastecimiento de eventos perder un hecho equivale a perder el estado. Empezar por auditoría y migrar más tarde, si hace falta, es un camino razonable; empezar por abastecimiento sin necesitarlo es una de las complejidades más caras que un equipo puede adoptar por anticipado.
Los campos que convierten un registro en una explicación
Un historial que solo guarda el estado nuevo y la hora responde a la mitad de las preguntas. Los campos que faltan son precisamente los que la gente pide cuando llama por teléfono.
| Campo | Pregunta que responde | Coste de omitirlo |
|---|---|---|
| desde y hacia | qué cambió exactamente | hay que inferir el salto comparando filas |
| evento | qué lo provocó | se conoce el efecto pero no la causa |
| autor tipo e identidad | quién lo movió | no se distingue un operador de una tarea automática |
| correlación | qué petición o mensaje lo originó | imposible unir el caso con los registros técnicos |
| secuencia | en qué orden ocurrió de verdad | los relojes desalineados reordenan la historia |
| versión del modelo | bajo qué reglas se decidió | se juzga una decisión antigua con reglas nuevas |
| carga útil depurada | con qué datos se decidió | se pierde el contexto de la decisión |
| instante de negocio | cuándo ocurrió el hecho real | se confunde con cuándo se registró |
El campo de autor merece una precisión que evita disputas posteriores: debe registrar quién provocó el hecho, no qué componente lo escribió. Anotar que la transición la hizo el servicio de pedidos es información inútil porque siempre es el mismo; anotar que la hizo el operador con una identidad concreta, o el consumidor de la cola de la pasarela, o el planificador de caducidades, convierte el registro en algo que responde a la pregunta de responsabilidad. Cuando el hecho lo desencadena un sistema en nombre de una persona, ambos datos deben viajar, porque la diferencia entre una cancelación automática y una cancelación autorizada por alguien es exactamente lo que distingue un error de procedimiento de una decisión.
Dos campos merecen comentario porque suelen olvidarse. La secuencia monótona por proceso es indispensable en cuanto hay varias instancias del servicio escribiendo: ordenar por marca de tiempo produce historias imposibles cuando dos relojes difieren en milisegundos, y en una investigación esa inversión aparente destruye la credibilidad del registro entero. Y la separación entre el instante en que el hecho ocurrió y el instante en que se anotó es lo que permite ingerir eventos con retraso —una notificación del transportista que llega tres horas tarde— sin que la crónica quede desordenada.
Guardar el evento entero es cómodo y peligroso: los eventos de negocio transportan direcciones, teléfonos, documentos de identidad y a veces datos de pago, y el historial es justo la tabla que nadie borra nunca. Depura antes de escribir, conserva referencias en lugar de valores cuando sea posible, y define una política de retención distinta para el hecho y para su carga útil. Un registro que hay que purgar de urgencia por una petición de supresión deja de ser inmutable, y con ello pierde su condición de prueba.
Responder por qué este pedido está así
La prueba de utilidad del historial es una consulta que devuelva la crónica de un caso en un formato que una persona de soporte pueda leer sin ayuda. Con los campos anteriores, la respuesta se compone sola.
sequenceDiagram participant Soporte participant Historial participant Snapshot Soporte->>Snapshot: en que estado esta el pedido Snapshot-->>Soporte: reembolsando desde hace dos dias Soporte->>Historial: dame la cronica completa Historial-->>Soporte: pagado por evento de pasarela Historial-->>Soporte: cancelado por operador con motivo Historial-->>Soporte: reembolsando y sin abono confirmado Soporte->>Soporte: la causa es un efecto pendiente, no una regla
El historial gana un valor desproporcionado cuando incluye los eventos rechazados: quién intentó cancelar un pedido ya enviado, cuántas veces llegó un pago para un proceso caducado, qué integración insiste en enviar algo que el modelo no admite. Esas filas no cambian ningún estado y responden preguntas que ninguna otra fuente responde, empezando por la más útil de todas en una migración: qué transiciones esperaba el mundo real que tu modelo no contempla. Basta con marcarlas con un campo que distinga el hecho aplicado del intento denegado.
Ese último paso es el que devuelve la inversión. Sin historial, un proceso atascado obliga a formular hipótesis: quizá la regla lo bloquea, quizá alguien lo movió mal, quizá el proveedor no respondió. Con historial, la última transición registrada y la ausencia de la siguiente sitúan la causa en un punto concreto, y el diagnóstico deja de depender de quién conozca mejor el código. La consecuencia organizativa suele ser mayor que la técnica: preguntas que antes escalaban a ingeniería se resuelven en primera línea, porque la explicación está en un lugar donde alguien sin acceso al repositorio puede leerla.
Investigación puntual
La crónica de un caso concreto, ordenada por secuencia, con autor y causa. Es la vista que necesita soporte y la que exige un regulador.
Tiempo por fase
Restando instantes entre transiciones consecutivas sale la duración real de cada etapa, sin instrumentar nada más que lo ya escrito.
Fugas del embudo
Contar cuántos procesos abandonan desde cada estado revela dónde se pierde el negocio, con la granularidad del modelo y no la de una analítica.
Verificación del modelo
Reproducir el historial sobre la definición vigente y comparar con el snapshot detecta divergencias, errores de migración y escrituras fuera de la máquina.
El historial como instrumento de medida
Antes de escribir la primera consulta conviene aceptar una consecuencia de diseño: el historial crece de forma lineal con el volumen del negocio y no deja de crecer nunca. Un proceso con doce transiciones medias y cien mil instancias al mes acumula más de un millón de filas mensuales, y esa tabla acabará siendo la mayor del sistema. La respuesta razonable no es escribir menos, sino separar los usos: índice por proceso y secuencia para la investigación puntual, partición por fecha para poder archivar lo antiguo sin bloquear nada, y agregados precalculados para los paneles, que no deberían recorrer la crónica entera cada vez que alguien abre un informe.
Una vez que cada cambio de fase es una fila, medir el proceso deja de requerir instrumentación adicional. El tiempo medio hasta el envío, el porcentaje de reclamaciones que superan el plazo legal, la proporción de cancelaciones iniciadas por operador frente a las del cliente y la distribución de estados en los que un proceso se queda más de un día son todas consultas sobre la misma tabla, expresadas en el vocabulario del negocio en lugar de en el de la infraestructura.
export async function tiempoEnEstado(desdeEstado: string, dias: number) {
return db.$queryRaw`
select t.desde as estado,
percentile_cont(0.5) within group (order by extract(epoch from s.ocurrido_en - t.ocurrido_en)) as mediana
from transicion t
join transicion s on s.proceso_id = t.proceso_id and s.secuencia = t.secuencia + 1
where t.desde = ${desdeEstado} and t.ocurrido_en > now() - make_interval(days => ${dias})
group by t.desde`
}
Existe además un uso del historial que no es de medida sino de garantía, y es el que cierra el círculo con la lección anterior. Reproducir los eventos registrados de un proceso sobre la definición vigente y comparar el resultado con su snapshot almacenado detecta tres clases de problema que ninguna prueba unitaria alcanza: escrituras hechas fuera de la máquina por un script de reparación olvidado, migraciones de versión que dejaron instancias en un punto que el modelo nuevo no explica, y divergencias entre lo que el grafo dice y lo que el sistema hizo. Ejecutar esa comprobación sobre una muestra periódica cuesta poco y convierte la sospecha en dato.
El valor de estas métricas no está en su sofisticación sino en su procedencia: se calculan sobre los mismos nombres que aparecen en el statechart, que son los que el negocio usa al hablar. Cuando la métrica y el modelo comparten vocabulario, desaparece la traducción intermedia donde suelen nacer las discrepancias entre lo que el panel dice y lo que el sistema hace.
Detrás de la decisión aparentemente menor de añadir una tabla de transiciones hay una toma de postura sobre qué se considera real en un sistema de información. La opción por defecto —una columna que se sobrescribe— afirma implícitamente que lo único real es el presente y que el camino recorrido fue un medio desechable para llegar hasta él. Esa afirmación es cómoda mientras nada se cuestione, y se vuelve insostenible en el momento en que alguien con autoridad pregunta por qué. Entonces se descubre que el sistema sabe muchísimo sobre su estado y absolutamente nada sobre su biografía, y que la organización debe reconstruir a mano, con registros técnicos y memoria humana, un pasado que el software podía haber conservado por unos pocos bytes de escritura. Registrar las transiciones invierte esa postura: declara que un procedimiento no es una posición sino una trayectoria, y que la trayectoria pertenece al dominio con el mismo derecho que el importe o la dirección de entrega. Las consecuencias van bastante más allá de poder responder incidencias. Un sistema con memoria se puede auditar, y por tanto se puede confiar en él ante terceros; se puede medir con su propio vocabulario, y por tanto se puede mejorar sin inventar métricas paralelas; se puede reproducir, y por tanto se puede verificar que el modelo vigente explica lo que realmente ocurrió. Y hay un efecto más sutil que aparece con el tiempo: cuando el historial existe, el equipo empieza a diseñar transiciones pensando en cómo se leerán dentro de un año, y esa mirada mejora los nombres, obliga a separar eventos que se habían fusionado por pereza y saca a la luz decisiones que estaban ocurriendo sin que nadie las hubiera nombrado. Escribir la historia no es solo conservar el pasado: es la disciplina que obliga a decir con precisión lo que el sistema está haciendo en presente.
- Crea la tabla de transiciones de solo inserción con los ocho campos de la referencia y restringe los permisos de actualización y borrado.
- Escribe la transición dentro de la misma transacción que el snapshot y comprueba que un fallo deja ambas cosas sin escribir.
- Añade secuencia monótona por proceso y verifica que la crónica se ordena bien aunque dos instancias escriban en el mismo instante.
- Separa el instante del hecho del instante del registro e ingiere a propósito un evento con tres horas de retraso.
- Construye la vista de crónica que una persona de soporte pueda leer sin ayuda y pruébala con un caso real atascado.
- Calcula la mediana de permanencia por estado del último mes y señala la fase donde el proceso pierde más tiempo.
- Reproduce el historial de diez procesos sobre la definición vigente y compara el resultado con su snapshot actual.
- Añade el registro de intentos denegados y revisa una semana después qué transiciones esperaba el mundo que tu modelo no contempla.