Un cliente cambia su dirección de facturación. Actualizas su ficha, abres una factura emitida el año pasado y descubres que ahora muestra la dirección nueva.
A nivel de base de datos no ha fallado nada. Sencillamente, la aplicación ha hecho la pregunta equivocada: “¿cuáles son los datos actuales de este cliente?” en lugar de “¿qué datos se registraron en este documento emitido?”.
Una factura emitida debe conservar los hechos que se usaron al emitirla. Mantener una clave ajena hacia el cliente es útil, pero esa relación por sí sola no preserva el historial del documento.
La información histórica de clientes y proveedores es un requisito documentado en Fracture. Los modelos de este artículo son opciones de diseño ilustrativas, no una descripción del esquema real de ese proyecto. Esto es orientación técnica, no una certificación de cumplimiento de la normativa de facturación de ningún país.
Separa el cliente del documento
La ficha del cliente representa una entidad cuyos datos actuales pueden cambiar. La factura es el registro de una operación concreta. Son objetos con ciclos de vida distintos.
Un modelo simplificado deja clara la diferencia:
cliente
id
nombre_fiscal_actual
identificador_fiscal_actual
direccion_facturacion_actual
factura
id
cliente_id
estado
emitida_en
snapshot_nombre_comprador
snapshot_identificador_fiscal_comprador
snapshot_direccion_comprador
snapshot_nombre_emisor
snapshot_identificador_fiscal_emisor
snapshot_direccion_emisor
moneda
total
linea_factura
factura_id
snapshot_descripcion
cantidad
precio_unitario
descuento
tipo_impositivo
importe_impuesto
total_linea
La clave ajena sirve para navegar y para hacer informes. Los snapshots sirven para representar el documento. Editar los datos actuales de un cliente no debería reescribir los snapshots de las facturas ya emitidas.
Las descripciones y los precios de los productos merecen el mismo trato. Representar una factura antigua haciendo un join contra el catálogo actual puede cambiar en silencio el significado o el importe de la operación.
Decide cuándo el snapshot pasa a ser la verdad
Un borrador no es un documento emitido. Puede tener sentido que un borrador refresque los datos del cliente, pero la interfaz debe explicar ese comportamiento y mostrar qué se va a emitir.
Un flujo defendible sería:
- Crear un borrador editable con referencias al cliente y a los productos.
- Resolver los datos de facturación previstos y calcular los totales para revisarlos.
- Al emitir, validar los valores finales y persistir de forma atómica los snapshots, los importes de las líneas, el identificador del documento y la fecha de emisión.
- Representar el documento emitido a partir de esos valores guardados, no de los datos maestros en vivo.
Es una decisión de política, no el único flujo posible. Si tu producto congela los datos antes, documenta y comunica ese momento de forma explícita. Lo que hay que evitar es la mezcla accidental: copiar la dirección al crear el borrador pero consultar el nombre de la empresa en el momento de la descarga.
Trata la emisión como una transacción
Dos peticiones pueden intentar emitir el mismo borrador a la vez. Deshabilitar un botón en la interfaz no protege frente a reintentos ni frente a llamadas directas a la API.
Usa una transacción de base de datos con el bloqueo o la actualización condicional adecuados, de forma que solo tenga éxito una transición válida de borrador a emitida. Asigna y valida el identificador del documento según los requisitos reales de numeración de la aplicación. Persiste los valores finales de las líneas y los totales dentro de la misma transacción.
Define también cómo se gestionan los cambios concurrentes en los datos del cliente. La pantalla de revisión puede estar mostrando la versión 4 mientras otro usuario guarda la 5. Puedes rechazar la emisión hasta que se revisen los datos nuevos, o emitir el snapshot revisado explícitamente si encaja con las reglas del producto. Lo que hay que evitar es mezclar versiones en silencio.
Usa tipos numéricos exactos (u otra representación exacta explícitamente definida) para los valores monetarios. Persiste las decisiones de redondeo que produjeron los totales emitidos. Recalcular un documento antiguo con una política de redondeo nueva puede alterarlo aunque los snapshots del cliente estén intactos.
Conserva el documento generado cuando haga falta
Que los datos de origen sean estables no garantiza un PDF idéntico en el futuro. Las fuentes, las plantillas, las traducciones y las bibliotecas de renderizado cambian.
Si la aplicación necesita reproducir exactamente el documento emitido, guarda el fichero generado en un almacenamiento controlado y registra su identificador y su checksum. Un checksum puede ayudar a detectar un cambio accidental, pero un hash guardado junto al fichero no es una prueba de integridad independiente.
La generación del documento también introduce una frontera de fallo. Decide qué pasa si la base de datos hace commit pero falla la creación del PDF. Un trabajo de generación duradero permite reintentos, pero la interfaz debe distinguir un registro emitido con el fichero pendiente de un documento listo para enviar. Algunos flujos requieren otra frontera de emisión: deja ese requisito explícito antes de implementarlo.
Las correcciones no deben reescribir el historial en silencio
Bloquear la edición en la interfaz es solo la primera capa. Aplica las restricciones de documento emitido en todas las rutas de escritura del servidor, incluidas las importaciones, las herramientas de administración y los procesos en segundo plano.
Cuando una factura emitida está mal, usa el flujo de corrección que exija la jurisdicción y el contexto de negocio correspondientes. Puede implicar un documento rectificativo enlazado u otro proceso auditable. No cambies el original en silencio dejando a los destinatarios con versiones inconsistentes.
Un registro de auditoría debe identificar quién hizo cada operación, cuándo ocurrió y a qué documento afectó. Restringe el acceso y evita guardar datos personales no relacionados “por si acaso”. Los requisitos de conservación y borrado necesitan revisión legal; la inmutabilidad no es una excusa para guardarlo todo para siempre.
Un test de regresión que caza el error original
Empieza por un escenario pequeño y explícito:
Dado un cliente llamado "Taller Ejemplo" en "Calle Norte 10"
Y una factura emitida que contiene esos datos de comprador
Cuando la dirección del cliente cambia a "Calle Sur 25"
Entonces la ficha del cliente muestra "Calle Sur 25"
Y la factura emitida sigue mostrando "Calle Norte 10"
Y un borrador nuevo sigue la política de refresco documentada
Amplíalo para cubrir cambios en los datos del emisor, renombrado de productos, cambios de impuestos y de redondeo, peticiones de emisión duplicadas y ediciones concurrentes del cliente. Prueba a actualizar un registro emitido a través de la API, no solo desde el formulario.
Prueba también el fallo y la recuperación de la generación del fichero. La aplicación no debería crear una segunda factura solo porque el primer intento de generar el PDF agotó el tiempo de espera.
¿Snapshot o historial de eventos completo?
Los snapshots suelen ser la solución más pequeña cuando el requisito es reproducir un documento emitido. Un modelo de datos maestros versionado o un historial de eventos permite consultas históricas más ricas, pero añade responsabilidades de reconstrucción y de migración.
Elige en función de las consultas y las garantías que necesitas. Como en el diseño de sistemas escalables, el objetivo no es adoptar el patrón más elaborado, sino conseguir que el comportamiento importante sea estable, observable y testeable.