912 lines
27 KiB
Markdown
912 lines
27 KiB
Markdown
# Split Proformas / Issued Invoices
|
|
|
|
Estado: historico / transicional.
|
|
No describe el runtime minimo vigente por si solo; la fuente operativa actual para proformas es la combinación de:
|
|
|
|
- `docs/document-series/README.md`
|
|
- `docs/customer-invoices/proforma-series-contract.md`
|
|
- `docs/customer-invoices/proforma-create-contract.md`
|
|
- `docs/customer-invoices/proforma-tax-config.md`
|
|
|
|
Estado: diseno tecnico / fases 1A-1D + 2A-2B
|
|
Estado del backend: persistencia V2 activada; integracion con `document-series` activa para numeracion nueva; sin ejecucion confirmada de migracion DB
|
|
|
|
## Objetivo
|
|
|
|
Preparar el esquema fisico para separar:
|
|
|
|
- `customer_invoices` -> `proformas`
|
|
- `customer_invoices` -> `issued_invoices`
|
|
- `customer_invoice_items` -> `proforma_items`
|
|
- `customer_invoice_items` -> `issued_invoice_items`
|
|
- `customer_invoice_taxes` -> `proforma_taxes`
|
|
- `customer_invoice_taxes` -> `issued_invoice_taxes`
|
|
- `verifactu_records.invoice_id` -> `verifactu_records.issued_invoice_id`
|
|
|
|
## Decisiones de fase 1A
|
|
|
|
- `proforma_reference` es el identificador visible canonico de proforma.
|
|
- `target_invoice_series_code` sustituye la semantica legacy de `series` en proformas.
|
|
- `document_series_id` identifica la serie documental usada para numerar la proforma.
|
|
- `proforma_number` guarda el numero propio asignado a la proforma.
|
|
- `target_invoice_series_code` es opcional y solo puede apuntar a una serie de `issued_invoice`.
|
|
- una serie `PF` o cualquier otra serie de `document_type = proforma` nunca debe persistirse en `target_invoice_series_code`.
|
|
- si `target_invoice_series_code` queda `NULL`, la emision de la factura usara la serie default activa de `issued_invoice`.
|
|
- `issued_invoices` usa `invoice_series_code`, `invoice_number`, `invoice_date` y `source_proforma_id`.
|
|
- `proformas` no guarda `issued_invoice_id`; la relacion se resuelve con `issued_invoices.source_proforma_id`.
|
|
- Los impuestos del esquema actual son por documento, no por linea: `proforma_taxes` y `issued_invoice_taxes`.
|
|
- `payment_method` y `payment_term` en `issued_invoices` son snapshot historico, no estado vivo de cobro.
|
|
- No hay doble escritura.
|
|
- Legacy queda congelado solo para validacion y rollback cuando se haga el cutover.
|
|
|
|
## Estado de fase 1B
|
|
|
|
- Existen mappers Sequelize V2 sobre `proformas`, `proforma_items`, `proforma_taxes`.
|
|
- Existen mappers Sequelize V2 sobre `issued_invoices`, `issued_invoice_items`, `issued_invoice_taxes`.
|
|
- Existen repositorios Sequelize V2 listos para convivir con los repositorios legacy.
|
|
- El wiring activo legacy no se ha sustituido todavia.
|
|
- El dominio/application sigue arrastrando naming legacy en algunos contratos (`series`, `invoiceNumber`, `linkedProformaId`); la traduccion queda encapsulada en Infrastructure V2.
|
|
- `payment_term` sigue sin soporte real en el dominio actual de customer invoices; en persistencia V2 queda preparado pero no conectado funcionalmente.
|
|
|
|
## Estado de fase 1C
|
|
|
|
- El wiring activo del modulo usa persistencia V2 en los composition roots principales.
|
|
- `buildV2ProformaPersistence(...)` queda activo en:
|
|
- `modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts`
|
|
- `modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-public-services.ts`
|
|
- `buildV2IssuedInvoicePersistence(...)` queda activo en:
|
|
- `modules/customer-invoices/src/api/infrastructure/issued-invoices/di/issued-invoices.di.ts`
|
|
- `modules/customer-invoices/src/api/infrastructure/issued-invoices/di/issued-invoice-public-services.ts`
|
|
- La persistencia legacy no se borra, pero deja de ser la ruta operativa principal.
|
|
- La emision `Proforma -> IssuedInvoice` ahora encadena sobre V2:
|
|
- lee `proformas`
|
|
- crea `issued_invoices`
|
|
- crea `issued_invoice_items`
|
|
- crea `issued_invoice_taxes`
|
|
- marca la proforma como `issued`
|
|
- usa `issued_invoices.source_proforma_id`
|
|
- No hay doble escritura: cada composition root sigue instanciando una sola familia de repositorios.
|
|
- `VerifactuRecord` queda preparado para la ruta V2 mediante `issued_invoice_id` como relacion principal.
|
|
- La migracion/backfill real sigue pendiente. Si la BD real no tiene las tablas nuevas, el backend no podra arrancar correctamente con este wiring.
|
|
|
|
## Estado de fase 1D
|
|
|
|
- Existen scripts SQL separados para desarrollo:
|
|
- `docs/customer-invoices/sql/dev-split-proformas-issued-invoices.sql`
|
|
- `docs/customer-invoices/sql/validate-split-proformas-issued-invoices.sql`
|
|
- El SQL de desarrollo es idempotente y conservador:
|
|
- crea tablas V2 con `CREATE TABLE IF NOT EXISTS`
|
|
- hace backfill con `NOT EXISTS`
|
|
- no usa `DROP`, `TRUNCATE` ni `DELETE`
|
|
- no sobrescribe filas ya migradas
|
|
- no toca datos legacy salvo la columna transicional `verifactu_records.issued_invoice_id`
|
|
- Se documenta el orden de ejecucion en `docs/customer-invoices/sql/README.md`.
|
|
- En este workspace no se encontro una configuracion de BD de desarrollo inequivoca (`.env*` no presente en raiz del repo), por lo que no se ejecuto SQL real.
|
|
- Tampoco se hizo validacion funcional contra runtime real porque falta una BD de desarrollo confirmada y migrada para esta fase.
|
|
|
|
## Dependencias compartidas y exclusivas
|
|
|
|
### Compartidas entre legacy y V2
|
|
|
|
- contratos de Application (`IProformaRepository`, `IIssuedInvoiceRepository`)
|
|
- use cases, servicios de dominio y assemblers
|
|
- `TransactionManager`
|
|
- adaptadores legacy de `InvoiceSeriesNumberAssigner` para `IssuedInvoice`
|
|
- resolvers de catalogos
|
|
- servicios de documentos PDF/preview
|
|
|
|
### Exclusivas de legacy
|
|
|
|
- `CustomerInvoiceModel` como storage real de Proforma e IssuedInvoice
|
|
- numerador de proformas sobre `customer_invoices.invoice_number` con `is_proforma = true`
|
|
- repositorios Sequelize legacy de `customer_invoices`
|
|
- `CustomerInvoiceSeriesModel` y `SequelizeInvoiceSeriesRepository` como wiring transicional no prioritario
|
|
|
|
### Exclusivas de V2
|
|
|
|
- tablas `proformas`, `proforma_items`, `proforma_taxes`
|
|
- tablas `issued_invoices`, `issued_invoice_items`, `issued_invoice_taxes`
|
|
- numerador V2 de proformas sobre `document-series`, con snapshot en `proformas.document_series_id`, `proforma_number` y `proforma_reference`
|
|
- mappers y repositorios Sequelize V2
|
|
|
|
## Regla operativa actual de series en proformas
|
|
|
|
- al crear una proforma, `document-series` se invoca con `document_type = proforma` para resolver la numeracion visible de la proforma
|
|
- ese resultado solo alimenta `document_series_id`, `proforma_number` y `proforma_reference`
|
|
- `target_invoice_series_code` no se rellena automaticamente con la serie documental de proforma
|
|
- si el usuario no selecciona serie futura de factura, `target_invoice_series_code` se persiste en `NULL`
|
|
- si el usuario informa `target_invoice_series_code`, el backend lo conserva solo si corresponde a una serie activa de `issued_invoice`
|
|
- al emitir una proforma, la factura definitiva se numera con `document_type = issued_invoice`
|
|
- si la proforma trae `target_invoice_series_code`, se usa como `seriesCode`
|
|
- si `target_invoice_series_code` es `NULL`, `document-series` resuelve la serie default activa de `issued_invoice`
|
|
- `proforma_series_code` es un input de create para `document_type = proforma` y no vuelve a ser editable despues
|
|
|
|
## Estado Fase 2B
|
|
|
|
- `customer-invoices` ya no usa `customer_invoice_series` como fuente primaria para listar series activas ni para asignar numeracion nueva en `issued_invoice` y `proforma`
|
|
- `/catalogs/invoice-series` fue transicional y queda eliminado antes de produccion
|
|
- `document-series` queda como API canonica para listar series activas y asignar numeracion
|
|
- la DDL de `proformas` y la migracion/validacion SQL quedan documentadas, no ejecutadas
|
|
- la propagacion de `branchId` sigue pendiente porque el contexto de sucursal no llega todavia a los casos de uso activos
|
|
|
|
## Estado Fase 2C
|
|
|
|
- se detecto un entorno de desarrollo en `apps/server/.env.development`
|
|
- produccion quedo descartada para ejecucion en esta fase
|
|
- no se ejecuto SQL real desde esta terminal por bloqueo operativo del cliente de BD local
|
|
- la DDL de proformas se endurecio para compatibilidad MariaDB/MySQL mediante `information_schema` y SQL dinamico
|
|
- la validacion funcional y la prueba de concurrencia quedaron documentadas para ejecucion manual sobre la BD de desarrollo segura
|
|
|
|
## Estado Fase 2D
|
|
|
|
- el contrato publico de proformas ya soporta `target_invoice_series_code` en create y update
|
|
- `series` se mantiene como alias legacy temporal en responses
|
|
- si `series` y `target_invoice_series_code` llegan a la vez con distinto valor, el request falla por validacion
|
|
- las responses de proforma prefieren `target_invoice_series_code`
|
|
- `series` se mantiene temporalmente en responses como alias legacy con el mismo valor
|
|
- el cliente interno del modulo ya emite `target_invoice_series_code` en requests nuevos
|
|
|
|
## Estado Fase 2E
|
|
|
|
- create separa `proforma_series_code` de `target_invoice_series_code`
|
|
- `proforma_series_code` solo se usa para numerar la proforma
|
|
- update ya no usa `series` en el flujo nuevo ni permite cambiar la serie propia de proforma
|
|
- `target_invoice_series_code` sigue siendo el unico concepto de serie futura de factura emitida
|
|
- la UI nueva de proformas ya envia `proforma_series_code` y `target_invoice_series_code`
|
|
|
|
## Estado Fase 2F
|
|
|
|
- create de proforma se consolida como creacion de cabecera/documento inicial
|
|
- una proforma sin lineas es valida y debe persistirse con `items = []`
|
|
- `tax_regime_code` queda como configuracion fiscal inicial de la cabecera
|
|
- `tax_config` queda persistido en `proformas` como configuracion fiscal de cabecera
|
|
- `proforma_taxes` solo representa impuestos calculados, no defaults fiscales ni placeholders sobre base `0`
|
|
- si una proforma nace sin lineas, la response debe devolver `taxes = []` y totales monetarios a `0`
|
|
- update sigue siendo el punto donde se anaden lineas y se materializan impuestos/totales reales
|
|
- la UI deja de prometer que la fiscalidad queda "aplicada" en create y pasa a hablar de valores iniciales
|
|
|
|
## Rollback recomendado
|
|
|
|
- no aplicar nada en produccion en esta fase
|
|
- si en desarrollo se ejecuta la DDL/migracion y hay que revertir, hacerlo con backup previo o clon de desarrollo, nunca con borrado destructivo improvisado
|
|
- conservar `customer_invoice_series` solo como legado historico hasta ejecutar la limpieza operativa documentada
|
|
|
|
## Tablas nuevas previstas
|
|
|
|
### proformas
|
|
|
|
- `id`
|
|
- `company_id`
|
|
- `status`
|
|
- `proforma_reference`
|
|
- `proforma_date`
|
|
- `operation_date`
|
|
- `target_invoice_series_code`
|
|
- `reference`
|
|
- `description`
|
|
- `notes`
|
|
- `language_code`
|
|
- `currency_code`
|
|
- `customer_id`
|
|
- snapshot cliente
|
|
- `payment_method_id`
|
|
- `payment_term_id`
|
|
- `tax_regime_code`
|
|
- totales monetarios actuales
|
|
- `created_at`
|
|
- `updated_at`
|
|
- `deleted_at`
|
|
|
|
### issued_invoices
|
|
|
|
- `id`
|
|
- `company_id`
|
|
- `status`
|
|
- `source_proforma_id`
|
|
- `invoice_series_code`
|
|
- `invoice_number`
|
|
- `invoice_date`
|
|
- `operation_date`
|
|
- `reference`
|
|
- `description`
|
|
- `notes`
|
|
- `language_code`
|
|
- `currency_code`
|
|
- `customer_id`
|
|
- snapshot cliente
|
|
- `payment_method_id`
|
|
- `payment_method_name`
|
|
- `payment_method_description`
|
|
- `payment_term_id`
|
|
- `payment_term_name`
|
|
- `payment_term_description`
|
|
- `payment_term_snapshot_json`
|
|
- `tax_regime_code`
|
|
- `tax_regime_description`
|
|
- totales monetarios actuales
|
|
- `created_at`
|
|
- `updated_at`
|
|
- `deleted_at`
|
|
|
|
### Items y taxes
|
|
|
|
- `proforma_items.proforma_id`
|
|
- `issued_invoice_items.issued_invoice_id`
|
|
- `proforma_taxes.proforma_id`
|
|
- `issued_invoice_taxes.issued_invoice_id`
|
|
|
|
### Verifactu
|
|
|
|
- `verifactu_records.issued_invoice_id` nuevo
|
|
- `verifactu_records.invoice_id` legacy temporal durante la transicion de fase 1A
|
|
|
|
## SQL orientativo de backfill
|
|
|
|
### Proformas
|
|
|
|
```sql
|
|
INSERT INTO proformas (
|
|
id,
|
|
company_id,
|
|
status,
|
|
proforma_reference,
|
|
proforma_date,
|
|
operation_date,
|
|
target_invoice_series_code,
|
|
reference,
|
|
description,
|
|
notes,
|
|
language_code,
|
|
currency_code,
|
|
customer_id,
|
|
payment_method_id,
|
|
payment_term_id,
|
|
tax_regime_code,
|
|
subtotal_amount_value,
|
|
subtotal_amount_scale,
|
|
items_discount_amount_value,
|
|
items_discount_amount_scale,
|
|
global_discount_percentage_value,
|
|
global_discount_percentage_scale,
|
|
global_discount_amount_value,
|
|
global_discount_amount_scale,
|
|
total_discount_amount_value,
|
|
total_discount_amount_scale,
|
|
taxable_amount_value,
|
|
taxable_amount_scale,
|
|
iva_amount_value,
|
|
iva_amount_scale,
|
|
rec_amount_value,
|
|
rec_amount_scale,
|
|
retention_amount_value,
|
|
retention_amount_scale,
|
|
taxes_amount_value,
|
|
taxes_amount_scale,
|
|
total_amount_value,
|
|
total_amount_scale,
|
|
created_at,
|
|
updated_at,
|
|
deleted_at
|
|
)
|
|
SELECT
|
|
id,
|
|
company_id,
|
|
status,
|
|
CASE
|
|
WHEN series IS NULL OR series = '' THEN invoice_number
|
|
ELSE CONCAT(series, '-', invoice_number)
|
|
END AS proforma_reference,
|
|
invoice_date,
|
|
operation_date,
|
|
series,
|
|
reference,
|
|
description,
|
|
notes,
|
|
language_code,
|
|
currency_code,
|
|
customer_id,
|
|
payment_method_id,
|
|
NULL AS payment_term_id,
|
|
tax_regime_code,
|
|
subtotal_amount_value,
|
|
subtotal_amount_scale,
|
|
items_discount_amount_value,
|
|
items_discount_amount_scale,
|
|
global_discount_percentage_value,
|
|
global_discount_percentage_scale,
|
|
global_discount_amount_value,
|
|
global_discount_amount_scale,
|
|
total_discount_amount_value,
|
|
total_discount_amount_scale,
|
|
taxable_amount_value,
|
|
taxable_amount_scale,
|
|
iva_amount_value,
|
|
iva_amount_scale,
|
|
rec_amount_value,
|
|
rec_amount_scale,
|
|
retention_amount_value,
|
|
retention_amount_scale,
|
|
taxes_amount_value,
|
|
taxes_amount_scale,
|
|
total_amount_value,
|
|
total_amount_scale,
|
|
created_at,
|
|
updated_at,
|
|
deleted_at
|
|
FROM customer_invoices
|
|
WHERE is_proforma = 1;
|
|
```
|
|
|
|
### Issued invoices
|
|
|
|
```sql
|
|
INSERT INTO issued_invoices (
|
|
id,
|
|
company_id,
|
|
status,
|
|
source_proforma_id,
|
|
invoice_series_code,
|
|
invoice_number,
|
|
invoice_date,
|
|
operation_date,
|
|
reference,
|
|
description,
|
|
notes,
|
|
language_code,
|
|
currency_code,
|
|
customer_id,
|
|
customer_tin,
|
|
customer_name,
|
|
customer_street,
|
|
customer_street2,
|
|
customer_city,
|
|
customer_province,
|
|
customer_postal_code,
|
|
customer_country,
|
|
payment_method_id,
|
|
payment_method_name,
|
|
payment_method_description,
|
|
payment_term_id,
|
|
payment_term_name,
|
|
payment_term_description,
|
|
payment_term_snapshot_json,
|
|
tax_regime_code,
|
|
tax_regime_description,
|
|
subtotal_amount_value,
|
|
subtotal_amount_scale,
|
|
items_discount_amount_value,
|
|
items_discount_amount_scale,
|
|
global_discount_percentage_value,
|
|
global_discount_percentage_scale,
|
|
global_discount_amount_value,
|
|
global_discount_amount_scale,
|
|
total_discount_amount_value,
|
|
total_discount_amount_scale,
|
|
taxable_amount_value,
|
|
taxable_amount_scale,
|
|
iva_amount_value,
|
|
iva_amount_scale,
|
|
rec_amount_value,
|
|
rec_amount_scale,
|
|
retention_amount_value,
|
|
retention_amount_scale,
|
|
taxes_amount_value,
|
|
taxes_amount_scale,
|
|
total_amount_value,
|
|
total_amount_scale,
|
|
created_at,
|
|
updated_at,
|
|
deleted_at
|
|
)
|
|
SELECT
|
|
id,
|
|
company_id,
|
|
status,
|
|
proforma_id,
|
|
series,
|
|
invoice_number,
|
|
invoice_date,
|
|
operation_date,
|
|
reference,
|
|
description,
|
|
notes,
|
|
language_code,
|
|
currency_code,
|
|
customer_id,
|
|
customer_tin,
|
|
customer_name,
|
|
customer_street,
|
|
customer_street2,
|
|
customer_city,
|
|
customer_province,
|
|
customer_postal_code,
|
|
customer_country,
|
|
payment_method_id,
|
|
payment_method_description,
|
|
payment_method_description,
|
|
NULL AS payment_term_id,
|
|
NULL AS payment_term_name,
|
|
NULL AS payment_term_description,
|
|
NULL AS payment_term_snapshot_json,
|
|
COALESCE(tax_regime_code, "01"),
|
|
COALESCE(tax_regime_description, "01: Operación de régimen general."),
|
|
subtotal_amount_value,
|
|
subtotal_amount_scale,
|
|
items_discount_amount_value,
|
|
items_discount_amount_scale,
|
|
global_discount_percentage_value,
|
|
global_discount_percentage_scale,
|
|
global_discount_amount_value,
|
|
global_discount_amount_scale,
|
|
total_discount_amount_value,
|
|
total_discount_amount_scale,
|
|
taxable_amount_value,
|
|
taxable_amount_scale,
|
|
iva_amount_value,
|
|
iva_amount_scale,
|
|
rec_amount_value,
|
|
rec_amount_scale,
|
|
retention_amount_value,
|
|
retention_amount_scale,
|
|
taxes_amount_value,
|
|
taxes_amount_scale,
|
|
total_amount_value,
|
|
total_amount_scale,
|
|
created_at,
|
|
updated_at,
|
|
deleted_at
|
|
FROM customer_invoices
|
|
WHERE is_proforma = 0;
|
|
```
|
|
|
|
### Items
|
|
|
|
```sql
|
|
INSERT INTO proforma_items (
|
|
item_id,
|
|
proforma_id,
|
|
position,
|
|
description,
|
|
quantity_value,
|
|
quantity_scale,
|
|
unit_amount_value,
|
|
unit_amount_scale,
|
|
subtotal_amount_value,
|
|
subtotal_amount_scale,
|
|
item_discount_percentage_value,
|
|
item_discount_percentage_scale,
|
|
item_discount_amount_value,
|
|
item_discount_amount_scale,
|
|
global_discount_percentage_value,
|
|
global_discount_percentage_scale,
|
|
global_discount_amount_value,
|
|
global_discount_amount_scale,
|
|
total_discount_amount_value,
|
|
total_discount_amount_scale,
|
|
taxable_amount_value,
|
|
taxable_amount_scale,
|
|
iva_code,
|
|
iva_percentage_value,
|
|
iva_percentage_scale,
|
|
iva_amount_value,
|
|
iva_amount_scale,
|
|
rec_code,
|
|
rec_percentage_value,
|
|
rec_percentage_scale,
|
|
rec_amount_value,
|
|
rec_amount_scale,
|
|
retention_code,
|
|
retention_percentage_value,
|
|
retention_percentage_scale,
|
|
retention_amount_value,
|
|
retention_amount_scale,
|
|
taxes_amount_value,
|
|
taxes_amount_scale,
|
|
total_amount_value,
|
|
total_amount_scale,
|
|
created_at,
|
|
updated_at
|
|
)
|
|
SELECT
|
|
i.item_id,
|
|
i.invoice_id,
|
|
i.position,
|
|
i.description,
|
|
i.quantity_value,
|
|
i.quantity_scale,
|
|
i.unit_amount_value,
|
|
i.unit_amount_scale,
|
|
i.subtotal_amount_value,
|
|
i.subtotal_amount_scale,
|
|
i.item_discount_percentage_value,
|
|
i.item_discount_percentage_scale,
|
|
i.item_discount_amount_value,
|
|
i.item_discount_amount_scale,
|
|
i.global_discount_percentage_value,
|
|
i.global_discount_percentage_scale,
|
|
i.global_discount_amount_value,
|
|
i.global_discount_amount_scale,
|
|
i.total_discount_amount_value,
|
|
i.total_discount_amount_scale,
|
|
i.taxable_amount_value,
|
|
i.taxable_amount_scale,
|
|
i.iva_code,
|
|
i.iva_percentage_value,
|
|
i.iva_percentage_scale,
|
|
i.iva_amount_value,
|
|
i.iva_amount_scale,
|
|
i.rec_code,
|
|
i.rec_percentage_value,
|
|
i.rec_percentage_scale,
|
|
i.rec_amount_value,
|
|
i.rec_amount_scale,
|
|
i.retention_code,
|
|
i.retention_percentage_value,
|
|
i.retention_percentage_scale,
|
|
i.retention_amount_value,
|
|
i.retention_amount_scale,
|
|
i.taxes_amount_value,
|
|
i.taxes_amount_scale,
|
|
i.total_amount_value,
|
|
i.total_amount_scale,
|
|
now(),
|
|
now()
|
|
FROM customer_invoice_items i
|
|
JOIN customer_invoices ci ON ci.id = i.invoice_id
|
|
WHERE ci.is_proforma = 1;
|
|
|
|
|
|
|
|
|
|
INSERT INTO issued_invoice_items (
|
|
item_id,
|
|
issued_invoice_id,
|
|
position,
|
|
description,
|
|
quantity_value,
|
|
quantity_scale,
|
|
unit_amount_value,
|
|
unit_amount_scale,
|
|
subtotal_amount_value,
|
|
subtotal_amount_scale,
|
|
item_discount_percentage_value,
|
|
item_discount_percentage_scale,
|
|
item_discount_amount_value,
|
|
item_discount_amount_scale,
|
|
global_discount_percentage_value,
|
|
global_discount_percentage_scale,
|
|
global_discount_amount_value,
|
|
global_discount_amount_scale,
|
|
total_discount_amount_value,
|
|
total_discount_amount_scale,
|
|
taxable_amount_value,
|
|
taxable_amount_scale,
|
|
iva_code,
|
|
iva_percentage_value,
|
|
iva_percentage_scale,
|
|
iva_amount_value,
|
|
iva_amount_scale,
|
|
rec_code,
|
|
rec_percentage_value,
|
|
rec_percentage_scale,
|
|
rec_amount_value,
|
|
rec_amount_scale,
|
|
retention_code,
|
|
retention_percentage_value,
|
|
retention_percentage_scale,
|
|
retention_amount_value,
|
|
retention_amount_scale,
|
|
taxes_amount_value,
|
|
taxes_amount_scale,
|
|
total_amount_value,
|
|
total_amount_scale,
|
|
created_at,
|
|
updated_at
|
|
)
|
|
SELECT
|
|
i.item_id,
|
|
i.invoice_id,
|
|
i.position,
|
|
i.description,
|
|
i.quantity_value,
|
|
i.quantity_scale,
|
|
i.unit_amount_value,
|
|
i.unit_amount_scale,
|
|
i.subtotal_amount_value,
|
|
i.subtotal_amount_scale,
|
|
i.item_discount_percentage_value,
|
|
i.item_discount_percentage_scale,
|
|
i.item_discount_amount_value,
|
|
i.item_discount_amount_scale,
|
|
i.global_discount_percentage_value,
|
|
i.global_discount_percentage_scale,
|
|
i.global_discount_amount_value,
|
|
i.global_discount_amount_scale,
|
|
i.total_discount_amount_value,
|
|
i.total_discount_amount_scale,
|
|
i.taxable_amount_value,
|
|
i.taxable_amount_scale,
|
|
i.iva_code,
|
|
i.iva_percentage_value,
|
|
i.iva_percentage_scale,
|
|
i.iva_amount_value,
|
|
i.iva_amount_scale,
|
|
i.rec_code,
|
|
i.rec_percentage_value,
|
|
i.rec_percentage_scale,
|
|
i.rec_amount_value,
|
|
i.rec_amount_scale,
|
|
i.retention_code,
|
|
i.retention_percentage_value,
|
|
i.retention_percentage_scale,
|
|
i.retention_amount_value,
|
|
i.retention_amount_scale,
|
|
i.taxes_amount_value,
|
|
i.taxes_amount_scale,
|
|
i.total_amount_value,
|
|
i.total_amount_scale,
|
|
now(),
|
|
now()
|
|
FROM customer_invoice_items i
|
|
JOIN customer_invoices ci ON ci.id = i.invoice_id
|
|
WHERE ci.is_proforma = 0;
|
|
```
|
|
|
|
### Taxes
|
|
|
|
```sql
|
|
INSERT INTO proforma_taxes (
|
|
tax_id,
|
|
proforma_id,
|
|
taxable_amount_value,
|
|
taxable_amount_scale,
|
|
iva_code,
|
|
iva_percentage_value,
|
|
iva_percentage_scale,
|
|
iva_amount_value,
|
|
iva_amount_scale,
|
|
rec_code,
|
|
rec_percentage_value,
|
|
rec_percentage_scale,
|
|
rec_amount_value,
|
|
rec_amount_scale,
|
|
retention_code,
|
|
retention_percentage_value,
|
|
retention_percentage_scale,
|
|
retention_amount_value,
|
|
retention_amount_scale,
|
|
taxes_amount_value,
|
|
taxes_amount_scale,
|
|
created_at,
|
|
updated_at
|
|
)
|
|
SELECT
|
|
t.tax_id,
|
|
t.invoice_id,
|
|
t.taxable_amount_value,
|
|
t.taxable_amount_scale,
|
|
t.iva_code,
|
|
t.iva_percentage_value,
|
|
t.iva_percentage_scale,
|
|
t.iva_amount_value,
|
|
t.iva_amount_scale,
|
|
t.rec_code,
|
|
t.rec_percentage_value,
|
|
t.rec_percentage_scale,
|
|
t.rec_amount_value,
|
|
t.rec_amount_scale,
|
|
t.retention_code,
|
|
t.retention_percentage_value,
|
|
t.retention_percentage_scale,
|
|
t.retention_amount_value,
|
|
t.retention_amount_scale,
|
|
t.taxes_amount_value,
|
|
t.taxes_amount_scale,
|
|
now(),
|
|
now()
|
|
FROM customer_invoice_taxes t
|
|
JOIN customer_invoices ci ON ci.id = t.invoice_id
|
|
WHERE ci.is_proforma = 1;
|
|
|
|
INSERT INTO issued_invoice_taxes (tax_id,
|
|
issued_invoice_id,
|
|
taxable_amount_value,
|
|
taxable_amount_scale,
|
|
iva_code,
|
|
iva_percentage_value,
|
|
iva_percentage_scale,
|
|
iva_amount_value,
|
|
iva_amount_scale,
|
|
rec_code,
|
|
rec_percentage_value,
|
|
rec_percentage_scale,
|
|
rec_amount_value,
|
|
rec_amount_scale,
|
|
retention_code,
|
|
retention_percentage_value,
|
|
retention_percentage_scale,
|
|
retention_amount_value,
|
|
retention_amount_scale,
|
|
taxes_amount_value,
|
|
taxes_amount_scale,
|
|
created_at,
|
|
updated_at)
|
|
SELECT
|
|
t.tax_id,
|
|
t.invoice_id,
|
|
t.taxable_amount_value,
|
|
t.taxable_amount_scale,
|
|
t.iva_code,
|
|
t.iva_percentage_value,
|
|
t.iva_percentage_scale,
|
|
t.iva_amount_value,
|
|
t.iva_amount_scale,
|
|
t.rec_code,
|
|
t.rec_percentage_value,
|
|
t.rec_percentage_scale,
|
|
t.rec_amount_value,
|
|
t.rec_amount_scale,
|
|
t.retention_code,
|
|
t.retention_percentage_value,
|
|
t.retention_percentage_scale,
|
|
t.retention_amount_value,
|
|
t.retention_amount_scale,
|
|
t.taxes_amount_value,
|
|
t.taxes_amount_scale,
|
|
now(),
|
|
now()
|
|
FROM customer_invoice_taxes t
|
|
JOIN customer_invoices ci ON ci.id = t.invoice_id
|
|
WHERE ci.is_proforma = 0;
|
|
```
|
|
|
|
### Verifactu
|
|
|
|
```sql
|
|
UPDATE verifactu_records vr
|
|
JOIN customer_invoices ci ON ci.id = vr.invoice_id
|
|
SET vr.issued_invoice_id = vr.invoice_id
|
|
WHERE ci.is_proforma = 0;
|
|
```
|
|
|
|
## Validaciones SQL
|
|
|
|
### Conteos
|
|
|
|
```sql
|
|
SELECT COUNT(*) FROM customer_invoices WHERE is_proforma = 1;
|
|
SELECT COUNT(*) FROM proformas;
|
|
|
|
SELECT COUNT(*) FROM customer_invoices WHERE is_proforma = 0;
|
|
SELECT COUNT(*) FROM issued_invoices;
|
|
|
|
SELECT COUNT(*)
|
|
FROM customer_invoice_items i
|
|
JOIN customer_invoices ci ON ci.id = i.invoice_id
|
|
WHERE ci.is_proforma = 1;
|
|
SELECT COUNT(*) FROM proforma_items;
|
|
|
|
SELECT COUNT(*)
|
|
FROM customer_invoice_taxes t
|
|
JOIN customer_invoices ci ON ci.id = t.invoice_id
|
|
WHERE ci.is_proforma = 0;
|
|
SELECT COUNT(*) FROM issued_invoice_taxes;
|
|
```
|
|
|
|
### Duplicados fiscales
|
|
|
|
```sql
|
|
SELECT company_id, invoice_series_code, invoice_number, COUNT(*)
|
|
FROM issued_invoices
|
|
GROUP BY company_id, invoice_series_code, invoice_number
|
|
HAVING COUNT(*) > 1;
|
|
```
|
|
|
|
### Huerfanos
|
|
|
|
```sql
|
|
SELECT COUNT(*)
|
|
FROM issued_invoices ii
|
|
LEFT JOIN proformas p ON p.id = ii.source_proforma_id
|
|
WHERE ii.source_proforma_id IS NOT NULL
|
|
AND p.id IS NULL;
|
|
|
|
SELECT COUNT(*)
|
|
FROM verifactu_records vr
|
|
LEFT JOIN issued_invoices ii ON ii.id = vr.issued_invoice_id
|
|
WHERE vr.issued_invoice_id IS NOT NULL
|
|
AND ii.id IS NULL;
|
|
```
|
|
|
|
### Totales
|
|
|
|
```sql
|
|
SELECT SUM(total_amount_value) FROM customer_invoices WHERE is_proforma = 1;
|
|
SELECT SUM(total_amount_value) FROM proformas;
|
|
|
|
SELECT SUM(total_amount_value) FROM customer_invoices WHERE is_proforma = 0;
|
|
SELECT SUM(total_amount_value) FROM issued_invoices;
|
|
```
|
|
|
|
### Proformas sin lineas con impuestos materializados
|
|
|
|
```sql
|
|
SELECT
|
|
p.id,
|
|
p.proforma_reference,
|
|
COUNT(DISTINCT pi.item_id) AS item_rows,
|
|
COUNT(DISTINCT pt.tax_id) AS tax_rows
|
|
FROM proformas p
|
|
LEFT JOIN proforma_items pi
|
|
ON pi.proforma_id = p.id
|
|
LEFT JOIN proforma_taxes pt
|
|
ON pt.proforma_id = p.id
|
|
GROUP BY p.id, p.proforma_reference
|
|
HAVING COUNT(DISTINCT pi.item_id) = 0
|
|
AND COUNT(DISTINCT pt.tax_id) > 0;
|
|
```
|
|
|
|
Resultado esperado:
|
|
|
|
- `0` filas
|
|
|
|
## No incluido en fase 1A
|
|
|
|
- no cutover del backend
|
|
- no borrado de tablas legacy
|
|
- no doble escritura
|
|
- no ejecucion de migraciones en una BD real
|
|
- no integracion Verifactu externa
|
|
- no dominio de cobros, vencimientos vivos o conciliacion
|
|
|
|
## No incluido en fase 1C
|
|
|
|
- no borrado de legacy
|
|
- no backfill real
|
|
- no ejecucion de migraciones
|
|
- no doble escritura
|
|
- no integracion Verifactu funcional nueva
|
|
- no cambios en controllers, rutas o DTOs publicos
|
|
|
|
## No incluido en fase 1D
|
|
|
|
- no ejecucion en produccion
|
|
- no borrado de tablas legacy
|
|
- no eliminacion de modelos/repositorios legacy
|
|
- no integracion Verifacti funcional
|
|
- no dominio de cobros
|
|
- no doble escritura
|
|
|
|
## Resultado de validacion tecnica fase 1D
|
|
|
|
- `tsc` modulo `customer-invoices`: esperado ejecutar en esta fase
|
|
- `biome lint` sobre `src/api/infrastructure` y `docs/customer-invoices`: esperado ejecutar en esta fase
|
|
- `tsc` raiz: esperado ejecutar en esta fase
|
|
- Validacion SQL real: no ejecutada por falta de entorno de desarrollo inequivoco
|
|
- Validacion runtime real contra BD migrada: no ejecutada por la misma razon
|
|
|
|
## Siguiente fase recomendada
|
|
|
|
- confirmar `.env`/credenciales de una BD de desarrollo segura
|
|
- ejecutar solo los runbooks SQL que sigan siendo necesarios para el entorno real afectado
|
|
- levantar el backend contra esa BD y validar `create`, `update`, `get`, `list` e `issue proforma`
|
|
- validar numeracion de proformas V2 y asignacion de serie/numero de factura emitida
|
|
- revisar la ruta `VerifactuRecord -> issued_invoice_id` ya con persistencia V2 activa
|
|
|
|
## Nota document-series
|
|
|
|
`customer-invoices` ya migró funcionalmente desde `CustomerInvoiceSeriesModel` hacia `document-series`. Como nota histórica de Fase 2A:
|
|
|
|
- `issued_invoice` ya asigna numeración nueva mediante `document-series`
|
|
- `proforma` ya asigna referencia nueva mediante `document-series`
|
|
- la UI debe consumir `GET /document-series` filtrando por `document_type`
|
|
- `CustomerInvoiceSeriesModel` deja de existir como modelo runtime activo
|
|
|
|
## Fase 2B no incluida
|
|
|
|
- no ejecucion de SQL en una BD real
|
|
- no borrado de tablas/modelos/repositorios legacy
|
|
- no propagacion completa de `branchId`
|
|
- no cambios de frontend salvo compatibilidad ya existente
|
|
- no nueva infraestructura de tests automatizados para estos modulos
|