SIFEN — Eventos del emisor (Cancelación e Inutilización)
Fecha de cierre: 2026-04-10 Estado: Production-ready (ambos eventos probados contra SIFEN TEST) Tickets Jira: FAC-84 — Fase 1 (Cancelación) + Fase 2 (Inutilización)
Qué logramos
Pipeline completo de registro de eventos del emisor ante el WS sincrónico siRecepEvento de la DNIT:
| Evento | Endpoint | Código SIFEN | Protocolo test |
|---|---|---|---|
| Cancelación | POST /api/sifen/eventos/cancelacion | 0600 — Evento registrado correctamente | 1089668 |
| Inutilización | POST /api/sifen/eventos/inutilizacion | 0600 — Evento registrado correctamente | 1089669 |
Ambos con logging de auditoría en laravel.log (inicio, resultado, errores de validación, excepciones).
Cancelación de DTE
¿Qué es?
Invalida un DTE ya aprobado por SIFEN. La transacción no se concretó (error en datos del receptor, mercadería no entregada, servicio no realizado).
Reglas de negocio (Manual v150 §11.1.2, Tabla J)
| Regla | Valor |
|---|---|
dTiGDE | No se incluye — se infiere del nodo rGeVeCan |
| Plazo | 48h post-aprobación si es FE; 168h si es NCE/NDE/NRE/AFE |
| Estado requerido del DTE | approved |
| Identificador | CDC de 44 dígitos |
| Motivo | Campo libre 5–500 chars (mOtEve) |
| WS | siRecepEvento (sincrónico) |
| Efecto | DTE pasa a estado cancelled en BD |
Endpoint
POST /api/sifen/eventos/cancelacion
Authorization: Bearer <token>
Content-Type: application/json
{
"cdc": "01801260060001001000094322026040912850865440",
"motivo": "Cancelación por error en datos del receptor"
}
Respuesta exitosa:
{
"status": "approved",
"evento_id": 7,
"cod_res": "0600",
"msg_res": "Evento registrado correctamente",
"prot_aut": "1089668",
"cdc": "01801260060001001000094322026040912850865440",
"dte_id": 96,
"dte_estado": "cancelled"
}
Validaciones
- CDC debe existir en
sifen_dtescon estadoapproved. - Plazo:
now() - last_sent_at <= 48h(FE) o<= 168h(otros). - Motivo: 5–500 caracteres.
- CDC: exactamente 44 dígitos numéricos.
Pipeline interno
POST /api/sifen/eventos/cancelacion {cdc, motivo}
↓ CancelarEventoRequest (FormRequest)
↓ RegistrarCancelacionEventoAction::execute()
1. Validar DTE existe + approved + plazo OK
2. Crear SifenEvento(estado=pending)
3. SifenEventoXmlBuilder::buildCancelacion()
→ <gGroupGesEve><rGesEve><rEve Id="N"><gGroupTiEvt><rGeVeCan>
4. SifenEventoXmlDsigSigner::sign()
→ Reference URI="#N", Signature hijo de rGesEve
5. Storage::put('sifen/xml/eventos/{Y/m}/{id}.xml')
6. SifenSoapEnvelopeBuilder::buildEvento()
→ envelope SOAP con namespace default, embebido directo (sin CDATA)
7. SifenHttpClient::post(evento.wsdl, rEnviEventoDe)
8. Parsear dCodRes, dMsgRes, dProtAut
9. Update SifenEvento(estado=approved|rejected)
10. Si aprobado → SifenDte.estado = cancelled
↓ JsonResponse
Inutilización de rango de números de DE
¿Qué es?
Comunica a SIFEN que un rango de números de DE no fueron ni serán utilizados. Casos:
- Saltos de numeración por error del sistema.
- DEs rechazados por SIFEN cuyo ajuste modificó el CDC.
- Decisión de la empresa de inutilizar un número.
Reglas de negocio (Manual v150 §11.1.1, Tabla J)
| Regla | Valor |
|---|---|
dTiGDE | No se incluye — se infiere del nodo rGeVeInu |
| Plazo | 15 primeros días del mes siguiente al hecho |
| Rango máximo | 1000 números secuenciales |
| Requisito | Ningún DE del rango puede estar aprobado en SIFEN |
| Motivo | Campo libre 5–500 chars |
| WS | siRecepEvento (sincrónico) |
| Efecto | Los números quedan marcados como inutilizados en SIFEN |
Endpoint
POST /api/sifen/eventos/inutilizacion
Authorization: Bearer <token>
Content-Type: application/json
{
"timbrado": 80126006,
"establecimiento": "001",
"punto_expedicion": "001",
"numero_inicio": "0004001",
"numero_fin": "0004010",
"tipo_documento": 1,
"motivo": "Saltos en numeración por reinicio del sistema de facturación"
}
Respuesta exitosa:
{
"status": "approved",
"evento_id": 8,
"cod_res": "0600",
"msg_res": "Evento registrado correctamente",
"prot_aut": "1089669",
"timbrado": "80126006",
"establecimiento": "001",
"punto_expedicion": "001",
"rango": "0004001 – 0004010",
"tipo_documento": 1
}
Validaciones
numero_inicio ≤ numero_fin.- Rango no supera 1000 números.
- Ningún DE aprobado o cancelado en el rango (query a
sifen_dtes). - No existe inutilización ya aprobada para el mismo rango/timbrado/est/punto.
- Todos los campos numéricos zero-padded: establecimiento (3), punto (3), números (7).
tipo_documentoentre 1 y 8 (1=FE, 2=FE export, 3=FE import, 4=AFE, 5=NCE, 6=NDE, 7=NRE, 8=CRE).- Motivo: 5–500 caracteres.
Pipeline interno
POST /api/sifen/eventos/inutilizacion {timbrado, est, punto, rango, tipo_doc, motivo}
↓ InutilizarEventoRequest (FormRequest)
↓ RegistrarInutilizacionEventoAction::execute()
1. Validar rango (inicio ≤ fin, max 1000)
2. Verificar sin DEs aprobados en el rango
3. Verificar sin inutilización ya aprobada para el rango
4. Crear SifenEvento(estado=pending)
5. SifenEventoXmlBuilder::buildInutilizacion()
→ <gGroupGesEve><rGesEve><rEve Id="N"><gGroupTiEvt><rGeVeInu>
6. SifenEventoXmlDsigSigner::sign()
7. Storage::put() + SifenSoapEnvelopeBuilder::buildEvento()
8. SifenHttpClient::post(evento.wsdl, rEnviEventoDe)
9. Parsear respuesta + Update SifenEvento
↓ JsonResponse
Estructura XML del evento (validada contra SIFEN TEST)
Cancelación
<gGroupGesEve xmlns="http://ekuatia.set.gov.py/sifen/xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ekuatia.set.gov.py/sifen/xsd siRecepEvento_v150.xsd">
<rGesEve xsi:schemaLocation="http://ekuatia.set.gov.py/sifen/xsd siRecepEvento_v150.xsd">
<rEve Id="7">
<dFecFirma>2026-04-10T09:35:30</dFecFirma>
<dVerFor>150</dVerFor>
<gGroupTiEvt>
<rGeVeCan>
<Id>01801260060001001000100122026041017345886385</Id>
<mOtEve>Test completo con logging de auditoría</mOtEve>
</rGeVeCan>
</gGroupTiEvt>
</rEve>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<!-- Reference URI="#7" sobre rEve -->
</Signature>
</rGesEve>
</gGroupGesEve>
Inutilización
<gGroupGesEve xmlns="http://ekuatia.set.gov.py/sifen/xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ekuatia.set.gov.py/sifen/xsd siRecepEvento_v150.xsd">
<rGesEve xsi:schemaLocation="http://ekuatia.set.gov.py/sifen/xsd siRecepEvento_v150.xsd">
<rEve Id="8">
<dFecFirma>2026-04-10T09:35:30</dFecFirma>
<dVerFor>150</dVerFor>
<gGroupTiEvt>
<rGeVeInu>
<dNumTim>80126006</dNumTim>
<dEst>001</dEst>
<dPunExp>001</dPunExp>
<dNumIn>0004001</dNumIn>
<dNumFin>0004010</dNumFin>
<iTiDE>1</iTiDE>
<mOtEve>Test inutilización con logging</mOtEve>
</rGeVeInu>
</gGroupTiEvt>
</rEve>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<!-- Reference URI="#8" sobre rEve -->
</Signature>
</rGesEve>
</gGroupGesEve>
Envelope SOAP (igual para ambos)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header/>
<env:Body>
<rEnviEventoDe xmlns="http://ekuatia.set.gov.py/sifen/xsd">
<dId>7</dId>
<dEvReg>{XML_DEL_EVENTO_FIRMADO}</dEvReg>
</rEnviEventoDe>
</env:Body>
</env:Envelope>
SOAP Action: rEnviEventoDe. Endpoint: evento.wsdl.
Embebido DIRECTO (sin CDATA) — patrón Manual cap7 §7.2.2.2.
Endpoint de consulta
GET /api/sifen/dtes/{id} — DTE con eventos
Cuando un DTE tiene eventos asociados, el endpoint los incluye:
{
"id": 96,
"estado": "cancelled",
"cancelado": true,
"eventos": [
{
"id": 7,
"tipo": 1,
"tipo_descripcion": "Cancelación del DTE",
"estado": "approved",
"sifen_result_code": "0600",
"prot_aut": "1089668",
"motivo": "...",
"fecha_envio": "2026-04-10T09:35:30+00:00"
}
]
}
El flag cancelado: true permite al frontend renderizar watermark "CANCELADO" sobre el KuDE.
GET /api/sifen/eventos/{id} — Detalle de un evento
Retorna todos los campos del evento individual (tipo, cdc/rango, motivo, estado, cod_res, prot_aut, etc.).
Logging de auditoría (laravel.log)
Todos los eventos registran en laravel.log con prefijo [SIFEN] Evento <tipo>: para facilitar filtrado:
grep "[SIFEN] Evento" storage/logs/laravel.log
Niveles de log
| Nivel | Cuándo | Ejemplo |
|---|---|---|
INFO | Inicio del registro | [SIFEN] Evento Cancelación: iniciando registro. {evento_id:7, dte_id:96, cdc:..., motivo:...} |
INFO | Resultado de SIFEN | [SIFEN] Evento Cancelación: resultado. {estado:approved, cod_res:0600, prot_aut:1089668} |
WARNING | Validación fallida | [SIFEN] Evento Inutilización: rango ya inutilizado. {rango:0004001–0004010} |
WARNING | CDC no encontrado | [SIFEN] Evento Cancelación: CDC no encontrado. {cdc:...} |
WARNING | DTE no aprobado | [SIFEN] Evento Cancelación: DTE no aprobado. {dte_id:X, estado:Y} |
WARNING | Rango inválido/excede máx | [SIFEN] Evento Inutilización: rango excede máximo. {cantidad:1500} |
WARNING | DEs aprobados en rango | [SIFEN] Evento Inutilización: DEs aprobados en el rango. {aprobados:3} |
ERROR | Excepción inesperada | [SIFEN] Evento Cancelación: excepción. {exception:...} |
Códigos de respuesta SIFEN para eventos
| Código | Significado |
|---|---|
0600 | Evento registrado correctamente (aprobado) — ambos tipos |
0160 | XML mal formado (ver sección bugs) |
4050–4099 | Validaciones específicas de Inutilización |
0560–0579 | Mensaje de entrada del WS SiRecepEvento |
0580–0599 | Control de llamada al WS SiRecepEvento |
La Action acepta cualquier 06xx como aprobado por compatibilidad.
Bugs resueltos durante la implementación (FAC-84)
Cancelación — 4 bugs
| # | Bug | Síntoma | Fix |
|---|---|---|---|
| 1 | Builder sin wrapper gGroupGesEve | 0160 | Agregar GDE000 como raíz |
| 2 | Falta xmlns:xsi + xsi:schemaLocation | 0160 | Agregados en gGroupGesEve y rGesEve |
| 3 | Campo dTiGDE sobrante | 0160 | Removido — se infiere del nodo rGeVeCan/rGeVeInu |
| 4 | buildEvento() usaba CDATA + prefijo xsd: | 0160 | Namespace default + embebido directo (Manual cap7) |
Inutilización — Sin bugs adicionales
La Fase 2 reutilizó toda la infraestructura corregida en Fase 1 (builder, signer, envelope). Funcionó al primer intento.
Tabla sifen_eventos (BD)
id BIGSERIAL PK
sifen_emitter_id FK → sifen_emitters
sifen_dte_id FK → sifen_dtes (nullable — inutilización no tiene DTE)
tipo SMALLINT (1=cancelación, 2=inutilización)
cdc VARCHAR(44) (solo cancelación)
timbrado VARCHAR(8) (solo inutilización)
establecimiento VARCHAR(3) (solo inutilización)
punto_expedicion VARCHAR(3) (solo inutilización)
numero_inicio VARCHAR(7) (solo inutilización)
numero_fin VARCHAR(7) (solo inutilización)
tipo_documento SMALLINT (solo inutilización, iTiDE 1-8)
motivo TEXT (5-500 chars)
xml_signed_path VARCHAR(255)
estado VARCHAR(20) — pending|sent|approved|rejected
sifen_result_code VARCHAR(4)
sifen_result_msg VARCHAR(500)
prot_aut VARCHAR(20) — protocolo de autorización SIFEN
fecha_envio TIMESTAMP
created_at TIMESTAMP
updated_at TIMESTAMP
Índices: dte_id, estado, tipo, cdc.
Archivos del módulo
| Archivo | Rol |
|---|---|
app/Domains/Sifen/Enums/TipoEvento.php | Enum 1=CANCELACION, 2=INUTILIZACION |
app/Domains/Sifen/DTOs/CancelacionEventoData.php | DTO readonly (cdc + motivo) |
app/Domains/Sifen/DTOs/InutilizacionEventoData.php | DTO readonly (timbrado + rango + motivo) |
app/Domains/Sifen/Models/SifenEvento.php | Modelo Eloquent + Swagger schema |
app/Domains/Sifen/Services/Xml/SifenEventoXmlBuilder.php | Builder: buildCancelacion() + buildInutilizacion() |
app/Domains/Sifen/Services/Signing/SifenEventoXmlDsigSigner.php | Firma XMLDSig sobre rEve |
app/Domains/Sifen/Actions/RegistrarCancelacionEventoAction.php | Orquestador cancelación + logging |
app/Domains/Sifen/Actions/RegistrarInutilizacionEventoAction.php | Orquestador inutilización + logging |
app/Http/Requests/Sifen/CancelarEventoRequest.php | FormRequest validación cancelación |
app/Http/Requests/Sifen/InutilizarEventoRequest.php | FormRequest validación inutilización |
app/Http/Controllers/Api/SifenController.php | cancelarEvento() + inutilizarEvento() + showEvento() |
app/Domains/Sifen/Services/Soap/SifenSoapEnvelopeBuilder.php | buildEvento() — envelope sin CDATA |
database/migrations/2026_04_09_140000_create_sifen_eventos_table.php | Tabla sifen_eventos |
routes/api.php | POST /eventos/cancelacion + POST /eventos/inutilizacion + GET /eventos/{id} |
scripts/tinker_test_cancelacion_evento.php | Test end-to-end cancelación |
Referencias
- Manual Técnico v150 —
docs/pdf/Manual Técnico Versión 150.pdf- §7.2.2.2 — Namespace del envelope de eventos
- §9.5 — WS siRecepEvento
- §11.1.1 — Inutilización de número de DE
- §11.1.2 — Cancelación de DTE
- §11.5 — Estructura XML de los eventos (Schema 19)
- §11.5.1 — Formato eventos emisor (rGeVeCan / rGeVeInu)
docs/manuales-md/manual_v150_cap11_eventos.md— Capítulo 11 en Markdowndocs/manuales-md/manual_v150_cap12_validaciones.md— Códigos 0600, 4050–4099docs/guias-tecnicas/SIFEN_OPERATIVE_LOGIC.md§4.2 — Estructura XML cancelación (patrón validado)docs/analisis-y-planes/PLAN_FAC84_EVENTOS_CANCELACION_INUTILIZACION.md— Plan original