- 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.
149 lines
5.7 KiB
Markdown
149 lines
5.7 KiB
Markdown
# 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
|