Skip to main content

Estado de la entrega — 7 de septiembre de 2026

Son dos entregas de backend pendientes de integración y aceptación extremo a extremo. Publicar estas ramas y su documentación no significa desplegarlas. El consumidor Difactu todavía no conecta la recuperación idempotente descrita aquí. No se ha aplicado la migración a la base compartida de desarrollo.

Duplicación identificada por operación

POST /api/invoice/duplicate/{id} admite un objeto JSON con operation_key opcional y date opcional. La clave admite de 16 a 128 caracteres ASCII: letras, números, guion y guion bajo. Es sensible a mayúsculas y pertenece a la cuenta autenticada. No es un identificador de factura ni un permiso de acceso.
  • La primera petición guarda reserva, factura borrador, líneas y vínculo al resultado en una transacción. Repetir la misma operación devuelve esa copia.
  • Cambiar el origen o la fecha explícita con la misma clave devuelve HTTP 409 operation_key_conflict, sin crear otra factura.
  • Omitir la fecha es una identidad distinta de indicarla explícitamente; no se sustituye por la fecha de hoy en la identidad de operación.
  • Una clave consumida no se libera al marcar su registro como eliminado. Si la copia deja de estar disponible, se responde HTTP 409 duplicate_operation_result_unavailable, sin recrearla.
  • Sin clave se conserva la duplicación anterior: dos peticiones pueden crear dos copias. El botón del backoffice no gana deduplicación automáticamente.
El JSON malformado, los cuerpos que no sean objeto y las fechas de calendario inválidas se rechazan antes de copiar. Los parámetros de query/formulario no suplantan los campos del JSON. El camino antiguo sin clave conserva sus alias de fecha. Los fallos inesperados mantienen un HTTP 500 sanitizado: un fallo de transporte no prueba que no hubiera commit.

Consultar sin repetir la escritura

GET /api/invoice/duplicateoperation?operation_key=... consulta la vinculación de la cuenta autenticada sin crear una factura. La operación OpenAPI es getInvoiceDuplicateOperation; se incorpora al perfil MCP interno difactu, no al perfil público difactu-chatgpt. El llamante debe conservar la misma clave para recuperar una operación incierta; crear otra clave no es un reintento.

Migración y comprobaciones

La migración 20260907_create_invoice_duplicate_operation.sql crea el registro transaccional y su unicidad por cuenta/clave. No debe eliminarse ese registro como limpieza de reintentos: conserva la identidad de operaciones consumidas. Para volver a un consumidor anterior, este puede seguir omitiendo la clave; no hay que borrar las vinculaciones ya guardadas. tests/experimental/invoice-duplicate-model-acceptance.php --run-isolated --port=43307 es opt-in. Verifica el datadir antes de DDL, crea un esquema sintético por ejecución y llama al modelo real desde procesos PHP separados. Las tablas auxiliares omiten FKs antiguas únicamente en el fixture; el ledger ejecuta la migración real con todas sus restricciones. Dos rondas completas acreditan concurrencia, acuse descartado y proceso nuevo, consulta sin escritura, rollback tras copiar líneas, aislamiento de cuentas y originales intactos. La repetición reforzada tarda 12,139 s. No prueba HTTP autenticado/MCP ni cambio de día; no sustituye las pruebas finales de la aplicación.

Región fiscal de Ceuta y Melilla

La resolución conserva ES-CE y ES-ML guardados por el alta, en lugar de degradarlos al perfil peninsular. El contrato candidato devuelve: Es un contrato de software, no una decisión sobre el tipo aplicable a una actividad. No inventa un porcentaje, no convierte pendiente en exento ni cambia los impuestos de documentos existentes o la fiscalidad de la suscripción. Con catalog_mode=empty, el alta inicial puede completarse sin sembrar tipos IPSI ni conceptos. reduced y advanced devuelven HTTP 409 tax_configuration_required, con can_continue_without_catalog: true, antes de iniciar la transacción. El rechazo conserva preferencias, catálogo y estado de onboarding anteriores; no guarda parcialmente la región solicitada. La selección y persistencia del impuesto por concepto siguen pendientes en el plan R4. Las pruebas del controlador y las respuestas HTTP simuladas del frontend no acreditan soporte fiscal completo desde la aplicación conectada.