FactuGES_ERP/docs/customer-invoices/proforma-tax-config.md
David 0a2e7795f1 feat: Refactor tax handling in proforma and recurring proforma modules
- 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.
2026-09-10 13:21:49 +02:00

149 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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_code` sigue representando el regimen fiscal general
- `tax_config` representa los controles fiscales de cabecera usados por la UI
- `taxes` sigue representando solo desglose fiscal materializado
- `totals` sigue representando solo importes calculados desde lineas valoradas
## Campos persistidos en `proformas`
- `tax_mode`
- `default_iva_code`
- `uses_equivalence_surcharge`
- `default_rec_code`
- `uses_retention`
- `default_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.
- `inactive` o `out_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 al `ProformaItemTax` persistido; 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:
```ts
{
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 desde `taxes`
## 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_config` desde el cliente actual
- la proforma conserva su `tax_config` persistido
## 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