- Introduced ProformaItemTax value object to encapsulate tax properties (code, name, percentage). - Updated ProformaTaxResolver to utilize ProformaItemTax instead of Tax. - Modified recurring proforma materializer to handle new tax structure. - Adjusted proforma aggregate and item entities to accommodate ProformaItemTax. - Enhanced Sequelize mappers to map to and from the new ProformaItemTax structure. - Updated tests to reflect changes in tax handling and ensure correctness.
5.7 KiB
Proforma Tax Config
Estado: CURRENT — SSOT fiscal para Proforma e IssuedInvoice. Las propuestas futuras deben identificarse expresamente como TARGET; la protección de DELETE de TaxDefinition permanece APARCADA.
Objetivo
Persistir la configuracion fiscal de cabecera de una proforma sin confundirla con los impuestos realmente calculados desde lineas valoradas.
Alcance
tax_regime_codesigue representando el regimen fiscal generaltax_configrepresenta los controles fiscales de cabecera usados por la UItaxessigue representando solo desglose fiscal materializadototalssigue representando solo importes calculados desde lineas valoradas
Campos persistidos en proformas
tax_modedefault_iva_codeuses_equivalence_surchargedefault_rec_codeuses_retentiondefault_retention_code
Estos códigos son referencias fiscales persistidas de la Proforma. No existe todavía una
protección transversal consolidada que impida borrar una TaxDefinition por estar referenciada.
Fiscalidad viva de Proforma
Tax es el objeto operacional obtenido del catálogo: contiene code, name, rate, group
y calculationBehavior. Antes de entrar en una línea de Proforma se convierte explícitamente a
ProformaItemTax, el snapshot fiscal de la línea: code, name y percentage.
Al cargar una Proforma, si existe la TaxDefinition para un código, prevalece su definición viva
y se vuelve a materializar como ProformaItemTax. Si falta, se conserva el snapshot persistido
(code, name, percentage) para que la Proforma siga siendo legible. Los importes no forman
parte de ProformaItemTax: se derivan siempre de taxableAmount × percentage.
group y calculationBehavior pertenecen exclusivamente a Tax/TaxDefinition operacional;
no se persisten en ProformaItem.
Disponibilidad fiscal
La disponibilidad se evalúa por companyId, código fiscal y proformaDate:
available: puede asignarse, cambiar de estado y emitirse.inactiveoout_of_validity: una lectura existente se resuelve live, pero no admite nueva asignación, cambio de estado ni emisión.missing: la lectura hace fallback alProformaItemTaxpersistido; no permite nueva asignación, cambio de estado ni emisión.
No hay fallback a defaults de Customer o Company. Readiness expone blockers tax_unavailable
con razón inactive, out_of_validity o missing; la emisión revalida en ese instante y no
confía en un readiness previo.
Contrato publico
GET /proformas/:id debe devolver:
{
tax_regime: {
code: string;
name: string;
description: string;
} | null;
tax_config: {
tax_mode: "single" | "per_line";
default_iva_code: string | null;
uses_equivalence_surcharge: boolean;
default_rec_code: string | null;
uses_retention: boolean;
default_retention_code: string | null;
};
}
POST /proformas y PATCH /proformas/:id pueden aceptar tax_config como bloque independiente de items.
Regla funcional
- una proforma puede existir con
items = [] - en ese caso
taxes = [] - en ese caso todos los totales monetarios deben quedar a
0 - la UI debe hidratar los controles de fiscalidad desde
tax_config, no desdetaxes
Resolucion de defaults en create
Los defaults del cliente solo se usan al crear la proforma y el resultado se persiste.
Fallback actual:
tax_mode:single- IVA por defecto:
iva_21 - recargo de equivalencia: desactivado salvo indicacion explicita
- retencion: desactivada salvo indicacion explicita
- codigo de retencion por defecto si
uses_retention = true:retention_15
Si el cliente cambia despues de crear la proforma:
- no se reconstruye
tax_configdesde el cliente actual - la proforma conserva su
tax_configpersistido
Regla de update
tax_config se actualiza de forma parcial y se mergea sobre la configuracion persistida actual.
No debe reconstruirse desde:
taxes- el cliente actual
- defaults implícitos de frontend
Para items: undefined no toca la colección ni revalida su fiscalidad existente; [] borra
todas las líneas; una lista reemplaza completamente las líneas y valida todos sus impuestos como
activos y vigentes. No existe mantenimiento diferencial por itemId.
IssuedInvoice: snapshot histórico
Una factura emitida es un snapshot histórico inmutable. IssuedInvoiceItem e
IssuedInvoiceTax conservan, por IVA/REC/retención según corresponda, código, nombre,
porcentaje e importe. Rehidratar, construir DTO o reporte no consulta TaxDefinition ni
Proforma; cambios posteriores de nombre, porcentaje, actividad o vigencia no alteran la factura.
No existe fallback name = code: code != null && name == null es una inconsistencia legacy.
Grouping fiscal actual (P10)
La identidad actual del breakdown es ivaCode | recCode | retentionCode: las tres dimensiones
participan, no solo IVA. El porcentaje no forma parte de la key, pero dentro de un grupo el mismo
código con nombre distinto o porcentaje distinto produce fail-fast. Esto no afirma que un código
tenga un porcentaje históricamente inmutable; afirma que no pueden coexistir valores contradictorios
para el mismo código dentro del mismo documento.
Se eliminaron las constraints equivalentes a UNIQUE(document_id, iva_code) de los breakdowns
de Proforma e IssuedInvoice: contradecían la identidad con REC/retención. El dominio garantiza
la agrupación única; la BD todavía no tiene una UNIQUE exacta para los tres slots nullable debido
a la semántica SQL de NULL. Es deuda futura no bloqueante.
No incluido
- nueva tabla para configuracion fiscal
- recalculo fiscal fuera del flujo normal de lineas
- cambios de Verifacti
- cobros o vencimientos vivos
- rectificativas