27 KiB
27 KiB
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.mddocs/customer-invoices/proforma-series-contract.mddocs/customer-invoices/proforma-create-contract.mddocs/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->proformascustomer_invoices->issued_invoicescustomer_invoice_items->proforma_itemscustomer_invoice_items->issued_invoice_itemscustomer_invoice_taxes->proforma_taxescustomer_invoice_taxes->issued_invoice_taxesverifactu_records.invoice_id->verifactu_records.issued_invoice_id
Decisiones de fase 1A
proforma_referencees el identificador visible canonico de proforma.target_invoice_series_codesustituye la semantica legacy deseriesen proformas.document_series_ididentifica la serie documental usada para numerar la proforma.proforma_numberguarda el numero propio asignado a la proforma.target_invoice_series_codees opcional y solo puede apuntar a una serie deissued_invoice.- una serie
PFo cualquier otra serie dedocument_type = proformanunca debe persistirse entarget_invoice_series_code. - si
target_invoice_series_codequedaNULL, la emision de la factura usara la serie default activa deissued_invoice. issued_invoicesusainvoice_series_code,invoice_number,invoice_dateysource_proforma_id.proformasno guardaissued_invoice_id; la relacion se resuelve conissued_invoices.source_proforma_id.- Los impuestos del esquema actual son por documento, no por linea:
proforma_taxesyissued_invoice_taxes. payment_methodypayment_termenissued_invoicesson 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_termsigue 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.tsmodules/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.tsmodules/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 -> IssuedInvoiceahora 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
- lee
- No hay doble escritura: cada composition root sigue instanciando una sola familia de repositorios.
VerifactuRecordqueda preparado para la ruta V2 medianteissued_invoice_idcomo 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.sqldocs/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,TRUNCATEniDELETE - no sobrescribe filas ya migradas
- no toca datos legacy salvo la columna transicional
verifactu_records.issued_invoice_id
- crea tablas V2 con
- 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
InvoiceSeriesNumberAssignerparaIssuedInvoice - resolvers de catalogos
- servicios de documentos PDF/preview
Exclusivas de legacy
CustomerInvoiceModelcomo storage real de Proforma e IssuedInvoice- numerador de proformas sobre
customer_invoices.invoice_numberconis_proforma = true - repositorios Sequelize legacy de
customer_invoices CustomerInvoiceSeriesModelySequelizeInvoiceSeriesRepositorycomo 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 enproformas.document_series_id,proforma_numberyproforma_reference - mappers y repositorios Sequelize V2
Regla operativa actual de series en proformas
- al crear una proforma,
document-seriesse invoca condocument_type = proformapara resolver la numeracion visible de la proforma - ese resultado solo alimenta
document_series_id,proforma_numberyproforma_reference target_invoice_series_codeno se rellena automaticamente con la serie documental de proforma- si el usuario no selecciona serie futura de factura,
target_invoice_series_codese persiste enNULL - si el usuario informa
target_invoice_series_code, el backend lo conserva solo si corresponde a una serie activa deissued_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 comoseriesCode - si
target_invoice_series_codeesNULL,document-seriesresuelve la serie default activa deissued_invoice proforma_series_codees un input de create paradocument_type = proformay no vuelve a ser editable despues
Estado Fase 2B
customer-invoicesya no usacustomer_invoice_seriescomo fuente primaria para listar series activas ni para asignar numeracion nueva enissued_invoiceyproforma/catalogs/invoice-seriesfue transicional y queda eliminado antes de producciondocument-seriesqueda como API canonica para listar series activas y asignar numeracion- la DDL de
proformasy la migracion/validacion SQL quedan documentadas, no ejecutadas - la propagacion de
branchIdsigue 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_schemay 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_codeen create y update seriesse mantiene como alias legacy temporal en responses- si
seriesytarget_invoice_series_codellegan a la vez con distinto valor, el request falla por validacion - las responses de proforma prefieren
target_invoice_series_code seriesse mantiene temporalmente en responses como alias legacy con el mismo valor- el cliente interno del modulo ya emite
target_invoice_series_codeen requests nuevos
Estado Fase 2E
- create separa
proforma_series_codedetarget_invoice_series_code proforma_series_codesolo se usa para numerar la proforma- update ya no usa
seriesen el flujo nuevo ni permite cambiar la serie propia de proforma target_invoice_series_codesigue siendo el unico concepto de serie futura de factura emitida- la UI nueva de proformas ya envia
proforma_series_codeytarget_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_codequeda como configuracion fiscal inicial de la cabeceratax_configqueda persistido enproformascomo configuracion fiscal de cabeceraproforma_taxessolo representa impuestos calculados, no defaults fiscales ni placeholders sobre base0- si una proforma nace sin lineas, la response debe devolver
taxes = []y totales monetarios a0 - 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
Estado Fase 2G
DELETE /proformas/:proforma_idvuelve a estar operativo sobre persistencia V2- el borrado es logico mediante
deleted_at - el archivado es logico mediante
archived_at - solo
draftpuede borrarse rejectedsigue fuera del alcance y queda reservado para archivado futuro- la validacion de borrado comprueba tambien
linked_invoice_idyissued_invoices.source_proforma_id - la numeracion de
document-seriesno se reutiliza ni se decrementa tras un borrado
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_seriessolo como legado historico hasta ejecutar la limpieza operativa documentada
Tablas nuevas previstas
proformas
idcompany_idstatusproforma_referenceproforma_dateoperation_datetarget_invoice_series_codereferencedescriptionnoteslanguage_codecurrency_codecustomer_id- snapshot cliente
payment_method_idpayment_term_idtax_regime_code- totales monetarios actuales
created_atupdated_atdeleted_at
issued_invoices
idcompany_idstatussource_proforma_idinvoice_series_codeinvoice_numberinvoice_dateoperation_datereferencedescriptionnoteslanguage_codecurrency_codecustomer_id- snapshot cliente
payment_method_idpayment_method_namepayment_method_descriptionpayment_term_idpayment_term_namepayment_term_descriptionpayment_term_snapshot_jsontax_regime_codetax_regime_description- totales monetarios actuales
created_atupdated_atdeleted_at
Items y taxes
proforma_items.proforma_idissued_invoice_items.issued_invoice_idproforma_taxes.proforma_idissued_invoice_taxes.issued_invoice_id
Verifactu
verifactu_records.issued_invoice_idnuevoverifactu_records.invoice_idlegacy temporal durante la transicion de fase 1A
SQL orientativo de backfill
Proformas
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
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
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
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
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
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
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
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
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
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:
0filas
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
tscmodulocustomer-invoices: esperado ejecutar en esta fasebiome lintsobresrc/api/infrastructureydocs/customer-invoices: esperado ejecutar en esta fasetscraiz: 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,listeissue proforma - validar numeracion de proformas V2 y asignacion de serie/numero de factura emitida
- revisar la ruta
VerifactuRecord -> issued_invoice_idya 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_invoiceya asigna numeración nueva mediantedocument-seriesproformaya asigna referencia nueva mediantedocument-series- la UI debe consumir
GET /document-seriesfiltrando pordocument_type CustomerInvoiceSeriesModeldeja 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