Estado de la entrega — 7 de septiembre de 2026
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.
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ón20260907_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 conservaES-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.
