SIFEN — Descarga de XML y Reenvío de DTEs
Fecha: 2026-04-10 Estado: Production-ready (probado contra SIFEN TEST) Ticket Jira: FAC-72 / Sprint 4.1 — API REST Endpoints añadidos:
GET /api/sifen/dtes/{id}/xml,POST /api/sifen/dtes/{id}/reenviar
Qué resuelven
Estos dos endpoints completan el ciclo operativo del DTE en OnnixConnect:
| Endpoint | Propósito |
|---|---|
GET /dtes/{id}/xml | Descargar el XML firmado de un DTE para respaldo, auditoría, contabilidad o reimpresión |
POST /dtes/{id}/reenviar | Reencolar un DTE rechazado o con error técnico para volver a enviarlo a SIFEN |
Decisión de diseño: solo se usan métodos GET y POST (no PUT/DELETE/PATCH) por:
- Compatibilidad con firewalls y proxies corporativos.
- Simplicidad para integradores externos.
- Coherencia con el modelo SIFEN: todo es "crear" (POST) o "consultar" (GET).
1. Descargar XML firmado
Endpoint
GET /api/sifen/dtes/{id}/xml
Authorization: Bearer <token>
Permiso requerido: sifen:read (roles: admin, operador, lector).
Comportamiento
- Retorna el XML firmado (XMLDSig) del DTE como descarga binaria.
Content-Type: application/xml; charset=UTF-8Content-Disposition: attachment; filename="{CDC}.xml"(odte_{id}.xmlsi no tiene CDC)- El XML incluye la firma digital, el CDC y todos los campos del DE tal como fueron enviados a SIFEN.
Estados permitidos
El DTE debe tener un xml_signed_path no nulo. Esto se cumple en:
| Estado | ¿Tiene XML firmado? |
|---|---|
draft | NO (aún no firmado) |
signed | SI |
sent | SI |
approved | SI |
rejected | SI |
cancelled | SI |
error | Depende de cuándo falló |
Respuestas
| Código | Descripción |
|---|---|
200 | XML retornado como descarga |
401 | Token ausente o inválido |
403 | Sin permiso sifen:read |
404 | DTE no encontrado o archivo XML no existe en disco |
409 | DTE en estado draft (sin XML firmado todavía) |
Ejemplo
curl -O -J \
-H "Authorization: Bearer $TOKEN" \
http://localhost:8000/api/sifen/dtes/96/xml
# Resultado:
# Saved as: 01801260060001001000100122026041017345886385.xml
Caso de uso típico
- Auditoría/respaldo: descarga periódica de todos los XMLs aprobados para almacenamiento off-site.
- Integración contable: envío del XML al ERP del cliente.
- Reimpresión del KuDE: el KuDE se regenera del XML.
- Resolución de incidencias: cuando un cliente reporta un problema con una factura específica.
2. Reenviar un DTE
Endpoint
POST /api/sifen/dtes/{id}/reenviar
Authorization: Bearer <token>
Permiso requerido: sifen:send (roles: admin, operador).
Comportamiento
Reencola el DTE para volver a procesarlo en el pipeline normal. El job despachado depende del estado actual:
| Estado actual | Acción | Job despachado | Estado tras la acción |
|---|---|---|---|
draft | Firma + envío | BuildAndSignDteJob → SendDteJob | draft (los jobs lo avanzan) |
signed | Solo envío | SendDteJob | signed |
rejected | Resetea cod/msg, encola envío | SendDteJob | signed |
error | Igual que rejected | SendDteJob | signed |
sent | 409 — ya en proceso | — | sin cambio |
approved | 409 — no tiene sentido | — | sin cambio |
cancelled | 409 — irreversible | — | sin cambio |
Validaciones
- El DTE debe existir (
404si no). - El estado actual debe estar en la lista permitida (
409si no). - Para
rejectedyerror: el reset limpiasifen_result_codeysifen_result_msgpara que el reenvío empiece desde cero.
Respuestas
Éxito (200):
{
"message": "DTE 42 reencolado para envío.",
"dte_id": 42,
"estado_anterior": "rejected",
"estado_actual": "signed",
"job": "SendDteJob"
}
Conflicto (409):
{
"message": "El DTE 42 está en estado approved y no puede reenviarse."
}
| Código | Descripción |
|---|---|
200 | DTE reencolado correctamente |
401 | Token ausente o inválido |
403 | Sin permiso sifen:send |
404 | DTE no encontrado |
409 | DTE en estado terminal (approved/cancelled/sent) |
Ejemplo
# Reenviar un DTE rechazado
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
http://localhost:8000/api/sifen/dtes/42/reenviar
# Respuesta:
# {
# "message": "DTE 42 reencolado para envío.",
# "dte_id": 42,
# "estado_anterior": "rejected",
# "estado_actual": "signed",
# "job": "SendDteJob"
# }
Caso de uso típico
- Recuperación de rechazos: SIFEN rechaza un DTE por validación temporal (ej. RUC del receptor en proceso de actualización), después se corrige y se reenvía.
- Errores técnicos: caídas de TLS, timeouts del servidor SIFEN, errores intermitentes — se reintentan manualmente.
- DTEs estancados en
signed: si la cola de Horizon estuvo caída cuando se firmó el DTE, se puede empujar manualmente con este endpoint. - Re-firma + envío: un DTE en
draftque necesita pasar por el pipeline completo nuevamente.
Pipeline interno
POST /api/sifen/dtes/{id}/reenviar
↓ Verifica permiso sifen:send
↓ SifenDte::findOrFail($id)
↓ Validación: estado in [draft, signed, rejected, error]
↓ Si rejected/error: update estado=signed, limpia cod/msg
↓ Despacha job(s):
- draft → BuildAndSignDteJob → chain(SendDteJob)
- signed → SendDteJob
- rejected/error → SendDteJob (con estado ya reseteado)
↓ JsonResponse con detalle del reencolado
Convención GET/POST únicamente
Los endpoints de OnnixConnect siguen una convención estricta:
| Método | Uso |
|---|---|
GET | Lectura de datos (consultas, descargas, listados) |
POST | Acciones que crean o modifican estado (emitir, cancelar, reenviar, subir cert) |
No se usan PUT, PATCH ni DELETE por:
- Algunos firewalls y proxies corporativos solo permiten
GET/POST. - En SIFEN no existe el concepto de "eliminar" un DTE — solo cancelar.
- Simplifica la integración para clientes que usan SDKs HTTP básicos.
- Coherente con el patrón de SIFEN, que solo expone
WS sync/async/eventos/consultas(todos POST).
Cache
El endpoint GET /dtes/{id}/xml no usa cache (FAC-85) porque:
- Los XMLs firmados se sirven directamente desde disco (
Storage::disk('local')). - Cachear blobs binarios en Redis es contraproducente (gasta memoria sin acelerar significativamente).
- El XML firmado es inmutable: una vez firmado, no cambia.
El sistema de archivos de Linux ya cachea los reads frecuentes en memoria (page cache) sin código adicional.
Archivos del módulo
| Archivo | Cambio |
|---|---|
app/Http/Controllers/Api/SifenController.php | Métodos downloadXml() y reenviar() con anotaciones Swagger |
routes/api.php | GET /dtes/{id}/xml + POST /dtes/{id}/reenviar con middleware de permisos |
storage/api-docs/api-docs.json | Swagger regenerado |
Referencias
docs/ROADMAP_EJECUCION_OC.md— Tarea 4.1 (líneas 1463–1502)docs/guias-tecnicas/SIFEN_EVENTOS_FUNCIONAMIENTO.md— Endpoints relacionados- Sprint 4 — API REST + KUDE + Observabilidad