Saltar al contenido principal

Análisis: Legacy PHP vs OnnixConnect — Diferencias y Plan de Fix

Generado: 2026-03-23 Legacy: c:/xampp/htdocs/FE SIFEN PROJECTS/sifen_phplegacy Actual: OnnixConnect (app/Domains/Sifen/)


1. Diferencias Críticas (candidatas a causar 0160)

🔴 DIFF-1 — Content-Type del request HTTP

Legacy (FUNCIONA)OnnixConnect (0160)
Headercontent-type: application/xml; charset=utf-8Content-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)
Synchttps://sifen-test.set.gov.py/de/ws/sync/recibe.wsdlhttps://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

LegacyOnnixConnect
XML generadoIndentado 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

LegacyOnnixConnect
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

LegacyOnnixConnect
gOpeComIncluye <iCondAnt> y <dDesCondAnt>No los incluye

Los XMLs aceptados (0300) tampoco los tienen → no es el problema.


🟢 DIFF-6 — Receptor dNumIDRec para Innominado

LegacyOnnixConnect
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:

AspectoLegacyOnnixConnect
Libreríarobrichards/xmlseclibsrobrichards/xmlseclibs
Prefijo Signature'' (vacío)'' (vacío) ✓
Método canónicoEXC_C14NEXC_C14N
ReferenciagetElementsByTagName('DE')[0]nodo DE por ID ✓
Transformsenveloped + EXC_C14Nenveloped + EXC_C14N ✓
DigestSHA256SHA256 ✓
FirmaRSA_SHA256RSA_SHA256 ✓
Clave$certs['pkey'] (de P12)desde P12 ✓
add509CertissuerSerial => falseverificar ⚠️
appendSignature$doc->documentElement (= <rDE>)$doc->documentElement
Quitar declaración XMLstr_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