Análisis: Legacy PHP vs OnnixConnect — Diferencias y Plan de Fix
Generado: 2026-03-23 Legacy:
c:/xampp/htdocs/FE SIFEN PROJECTS/sifen_phplegacyActual: OnnixConnect (app/Domains/Sifen/)
1. Diferencias Críticas (candidatas a causar 0160)
🔴 DIFF-1 — Content-Type del request HTTP
| Legacy (FUNCIONA) | OnnixConnect (0160) | |
|---|---|---|
| Header | content-type: application/xml; charset=utf-8 | Content-Type: application/soap+xml; charset=utf-8; action="rEnviDe" |
Por qué importa:
El servidor SIFEN (Java/CXF) puede rutear o parsear el request de forma distinta según el Content-Type. El legacy usa application/xml y fue el que recibió aprobaciones. Si el servidor espera application/xml y recibe application/soap+xml, podría activar un pipeline de validación diferente que rechaza el XML por razones internas no expuestas en dMsgRes.
Fix: En SifenHttpClient::post(), cambiar:
// ANTES
"Content-Type: application/soap+xml; charset=utf-8; action=\"{$soapAction}\"",
// DESPUÉS
'Content-Type: application/xml; charset=utf-8',
🔴 DIFF-2 — Endpoint URL (con vs sin .wsdl)
| Legacy (FUNCIONA) | OnnixConnect (0160) | |
|---|---|---|
| Sync | https://sifen-test.set.gov.py/de/ws/sync/recibe.wsdl | https://sifen-test.set.gov.py/de/ws/sync/recibe |
| Async | ...recibe-lote.wsdl | ...recibe-lote |
| Eventos | ...evento.wsdl | ...evento |
Por qué importa:
Aunque en estándar SOAP el .wsdl es el descriptor y no el endpoint, en SIFEN ambas rutas están activas. Es posible que el servidor tenga comportamiento diferente entre ambas (distinto servlet o distinto middleware de validación). El legacy SIEMPRE usó .wsdl y funcionó.
Fix: Actualizar config/sifen.php para agregar las URLs con .wsdl:
'endpoints' => [
'test' => [
'sync' => 'https://sifen-test.set.gov.py/de/ws/sync/recibe.wsdl',
'async' => 'https://sifen-test.set.gov.py/de/ws/async/recibe-lote.wsdl',
'evento' => 'https://sifen-test.set.gov.py/de/ws/eventos/evento.wsdl',
// consultas sin cambio (legacy no las usó)
],
🟡 DIFF-3 — XML con whitespace vs compacto
| Legacy | OnnixConnect | |
|---|---|---|
| XML generado | Indentado con whitespace (heredoc con saltos de línea) | Compacto, sin whitespace entre elementos |
<!-- Legacy (indentado) -->
<rDE xmlns="...">
<dVerFor>150</dVerFor>
<DE Id="...">
<dDVId>0</dDVId>
...
<!-- OnnixConnect (compacto) -->
<rDE xmlns="..."><dVerFor>150</dVerFor><DE Id="..."><dDVId>0</dDVId>...
Por qué importa:
EXC-C14N preserva whitespace text nodes. El DigestValue en el legacy cubre <DE> con sus whitespace internos. En OnnixConnect cubre <DE> compacto. Si SIFEN al verificar re-parsea con preserveWhiteSpace=true o agrega whitespace, el digest podría diferir.
Fix: Baja prioridad — los XMLs aceptados (0300) también son compactos. No parece ser el problema.
🟡 DIFF-4 — Firma: add509Cert con issuerSerial=false
| Legacy | OnnixConnect | |
|---|---|---|
add509Cert(...) | $objDSig->add509Cert($certs['cert'], true, false, ['issuerSerial' => false]) | $objDSig->add509Cert($pemCert, true, false, ['issuerSerial' => true]) |
Diferencia: El legacy pasa issuerSerial => false (no incluye <X509IssuerSerial> en KeyInfo), OnnixConnect usa true. Los XMLs aceptados (0300) muestran solo <X509Certificate> sin <X509IssuerSerial>, lo que coincide con issuerSerial => false.
Fix: En SifenXmlDsigSigner.php, verificar que se pasa issuerSerial => false:
$objDSig->add509Cert($pemCert, true, false, ['issuerSerial' => false]);
🟢 DIFF-5 — gOpeCom: iCondAnt / dDesCondAnt
| Legacy | OnnixConnect | |
|---|---|---|
gOpeCom | Incluye <iCondAnt> y <dDesCondAnt> | No los incluye |
Los XMLs aceptados (0300) tampoco los tienen → no es el problema.
🟢 DIFF-6 — Receptor dNumIDRec para Innominado
| Legacy | OnnixConnect | |
|---|---|---|
Innominado dNumIDRec | <dNumIDRec>00000000</dNumIDRec> (8 ceros) | <dNumIDRec>0</dNumIDRec> (1 cero) |
Innominado dNomRec | <dNomRec>Sin Nombre</dNomRec> | <dNomRec>SIN NOMBRE</dNomRec> |
El legacy usa 8 ceros y "Sin Nombre" (con mayúscula inicial). El XML aceptado 0300 usa <dNumIDRec>0</dNumIDRec> y <dNomRec>SIN NOMBRE</dNomRec>. No parece crítico.
2. Plan de Implementación — Prioridad
Paso 1 — DIFF-1: Cambiar Content-Type (30 min)
Archivo: app/Domains/Sifen/Services/Soap/SifenHttpClient.php
// Línea ~63 — REEMPLAZAR:
CURLOPT_HTTPHEADER => [
"Content-Type: application/xml; charset=utf-8", // ← igual que el legacy
'Accept: application/xml, text/xml, */*',
'Cache-Control: no-cache',
'Pragma: no-cache',
'Connection: keep-alive',
'Expect:',
],
⚠️ Eliminar completamente el
action=del Content-Type. El legacy no lo usa y funciona.
Paso 2 — DIFF-2: Probar con endpoint .wsdl (15 min)
Archivo: config/sifen.php
Cambiar temporalmente para test:
'sync' => 'https://sifen-test.set.gov.py/de/ws/sync/recibe.wsdl',
Paso 3 — DIFF-4: Verificar issuerSerial => false (15 min)
Archivo: app/Domains/Sifen/Services/Signing/SifenXmlDsigSigner.php
Buscar el add509Cert y confirmar que issuerSerial => false.
Paso 4 — Test con DTE nuevo e Innominado
Después de los cambios, enviar un nuevo DTE con:
iTipIDRec=5(Innominado) — igual que el bienformado y el 0300 aceptado- Sin
dEmailRec - Capturar request + response XML para Iris si sigue fallando
3. Resumen de la Firma — Legacy vs OnnixConnect
Ambas implementaciones usan exactamente el mismo proceso de firma:
| Aspecto | Legacy | OnnixConnect |
|---|---|---|
| Librería | robrichards/xmlseclibs | robrichards/xmlseclibs |
| Prefijo Signature | '' (vacío) | '' (vacío) ✓ |
| Método canónico | EXC_C14N | EXC_C14N ✓ |
| Referencia | getElementsByTagName('DE')[0] | nodo DE por ID ✓ |
| Transforms | enveloped + EXC_C14N | enveloped + EXC_C14N ✓ |
| Digest | SHA256 | SHA256 ✓ |
| Firma | RSA_SHA256 | RSA_SHA256 ✓ |
| Clave | $certs['pkey'] (de P12) | desde P12 ✓ |
add509Cert | issuerSerial => false | verificar ⚠️ |
appendSignature | $doc->documentElement (= <rDE>) | $doc->documentElement ✓ |
| Quitar declaración XML | str_replace('<?xml...?>\n', '', ...) | saveXML($root) sin prolog ✓ |
La firma en sí es equivalente. El problema probablemente está en el transport layer (Content-Type / endpoint).
4. Diferencia en el QR
El legacy construye el QR dentro de firmar(), después de appendSignature, usando $doc->createElement. OnnixConnect lo hace en SifenQrService como paso separado.
Ambos producen el mismo <gCamFuFD><dCarQR>...</dCarQR></gCamFuFD> al final de <rDE>, fuera del <DE> firmado. No hay diferencia funcional.
5. Acción Inmediata Recomendada
# 1. Cambiar Content-Type en SifenHttpClient.php
# 2. Enviar con: php artisan tinker
DTE_ID=<nuevo_id> php artisan tinker < scripts/tinker_send_dte_dod.php
# 3. Si sigue en 0160, probar también el endpoint .wsdl
# 4. Capturar logs en storage/app/private/sifen/logs/ para enviar a Iris