# Comparación api-a-conta-fact vs DTEngine

`DTEngine` es la **fuente de verdad** — sistema operativo que ya pasa certificación SII. Cualquier divergencia que se detecte se resuelve siguiendo el patrón de `DTEngine`.

Este documento concentra las diferencias detectadas componente por componente, con archivo:línea verificables, severidad y corrección sugerida.

> Convención: paths absolutos abreviados como `DTEngine\...` y `api-a-conta-fact\...`.

---

## 1. CertificateService vs EmisorCertificateService

| | DTEngine | api-a-conta-fact |
|---|---|---|
| Archivo | `app/Services/SII/EmisorCertificateService.php` | `app/Services/FacturacionElectronica/Services/CertificateService.php` |
| Storage del PFX | BD (`emisor_certificados.pfx_binario`) | Disco (`storage/app/dte/empresas/.../certificados/*.pfx`) |
| Password | Cifrado con `Crypt::encryptString` | **Plano** en `password_pfx` (hoy cifrado con mutador del modelo, BC con plano) |
| Carga del PFX | Delegada a `CertificateManager` + `Derafu\Certificate\Service\CertificateLoader` | `openssl_pkcs12_read()` directo |
| Extrae RUT del cert | No (toma de `rut_titular` manual) | Sí (`openssl_x509_parse()` → `subject.serialNumber`) |
| Vencimiento | Validación al cargar | Validación al cargar |

**Gaps:**

| # | Gap | Severidad | Corrección |
|---|---|---|---|
| 1.1 | Password legacy en plano | 🔴 Crítica | Mutador con `Crypt::encryptString` ya agregado al modelo (commit `c85c3e0`). Falta migrar passwords existentes si los hay |
| 1.2 | PFX en disco vs BD cifrada | 🟡 Media | Decisión consciente: mantener disco para arrancar más rápido. Migrar a BD cifrada antes de producción |

---

## 2. CafService vs CafManager

| | DTEngine | api-a-conta-fact |
|---|---|---|
| Archivo | `app/Services/SII/CafManager.php` | `app/Services/FacturacionElectronica/Services/CafService.php` |
| Parseo del CAF | Extrae nodo `<DA>` + validaciones (busca en `AUTORIZACION/CAF/DA` o `CAF/DA`) | Parseo parcial: solo extrae `<DA>` y campos clave |
| Storage XML CAF | `caf_files.xml_caf` (longtext) | `dte_cafs.caf_xml` + cache opcional `caf_path` |
| Reserva folio | `lockForUpdate()` transaccional | `lockForUpdate()` transaccional |
| Liberar folio fallido | Sí, estado `liberado_local` con metadata de release | No implementado |
| Validación vencimiento | 6 meses para tipos 33, 43, 46, 56, 61 | No implementada |
| Alerta CAF bajo (<10%) | Sí | No |

**Gaps:**

| # | Gap | Severidad | Corrección |
|---|---|---|---|
| 2.1 | No implementa `liberarFolio()` | 🔴 Crítica | Agregar método para liberar folios reservados que no se emitieron. Sin esto, un fallo deja el folio "perdido" |
| 2.2 | No valida vencimiento CAF | 🟡 Media | Agregar `esCafVencido()` en `CafService::getActivoCaf()` |
| 2.3 | No alerta cuando quedan <10% folios | 🟢 Baja | `Log::warning()` cuando `(usado_hasta - folio_desde) / (folio_hasta - folio_desde) > 0.9` |

---

## 3. DteXmlBuilder vs XmlDteBuilder

| | DTEngine | api-a-conta-fact |
|---|---|---|
| Archivo | `app/Services/SII/XmlDteBuilder.php` | `app/Services/FacturacionElectronica/Services/DteXmlBuilder.php` |
| Construcción XML | `Derafu\Xml\XmlEncoder` (array → XML) | String concatenation |
| Cálculo de totales | **Recalcula desde items** (nunca confía en input) | Lee del input sin validar |
| Tipos en código | 33, 39, 41, 52, 56, 61 + boletas explícito | Constante `TIPOS_CON_RECEPTOR_IDENTIFICADO = [33, 34, 52, 56, 61]` (falta 39/41) |
| Referencias | Espera array `Referencia` en data | Construye nodo `Referencia` desde `referencias` |
| Descuentos/recargos globales (DscRcgGlobal) | Sí | No |
| TmstFirma | Lo inserta `EnvelopeService` | Lo inserta el propio `DteXmlBuilder` |
| Encoding | ISO-8859-1 explícito en `serializarElemento()` | UTF-8 en `htmlspecialchars` (ver `ANALISIS_FIRMAS.md` Gap 1) |

**Gaps:**

| # | Gap | Severidad | Corrección |
|---|---|---|---|
| 3.1 | No recalcula totales desde items | 🔴 Crítica | Refactorizar `DteXmlBuilder::build()` para recalcular `monto_neto`/`iva`/`total` desde items, ignorando input. DTEngine comentario: *"Nunca confiar en montos del ERP"* |
| 3.2 | Constante tipos sin 39 y 41 | 🟡 Media | Cambiar a `TIPOS_CON_RECEPTOR_IDENTIFICADO = [33, 34, 39, 41, 52, 56, 61]` o crear separado `TIPOS_BOLETA = [39, 41]` |
| 3.3 | Sin soporte DscRcgGlobal | 🟡 Media | Agregar nodo `DscRcgGlobal` si payload lo trae |
| 3.4 | TmstFirma en builder vs envelope | 🟢 Baja | Cosmético, ambos válidos |
| 3.5 | Encoding UTF-8 al escapar | 🔴 Crítica | Ver `ANALISIS_FIRMAS.md` Gap 1 |

---

## 4. Modelos: dte_documentos vs dte_emitidos

| Campo | DTEngine (`dte_emitidos`) | api-a-conta-fact (`dte_documentos`) | Acción |
|---|---|---|---|
| Identificador | UUID (`HasUuids`) | bigint autoincrement | Diferencia, no crítica |
| `ambiente` | `enum('certificacion','produccion')` | **Agregado** en migración `2026_05_19_000005` | ✅ Cerrado |
| `sii_response` | `array` cast (JSON) | era `text`, ahora `longtext` + `respuesta_sii_json` array cast | ✅ Cerrado en migración `2026_05_19_000005` |
| `xml_dte` (sin firmar) | Sí | Renombrado a `xml_original` (ya existía como columna, ahora en fillable) | ✅ Cerrado |
| `xml_firmado` | No separado, todo en `xml_dte` después de firmar | Sí | Más granular |
| `estado_sii` | Sí, semántico explícito | Solo `estado` genérico (mismos valores) | 🟢 Baja, valores compatibles |
| Métodos helper | `estaAceptado()`, `estaPendienteEnvio()` | **Agregados** | ✅ Cerrado |

**Resumen:** Modelo `dte_documentos` ya cubre los campos críticos después de la migración del 2026-05-19. Quedan diferencias menores (UUID vs autoincrement).

---

## 5. Orquestador: DteEmissionService vs DteService

| | DTEngine | api-a-conta-fact |
|---|---|---|
| Archivo | `app/Services/SII/DteService.php` | `app/Services/FacturacionElectronica/Services/DteEmissionService.php` |
| Envío al SII | **Async** vía `EnviarDteJob` (queue) | **Síncrono** si `DTE_SEND_SYNC=true` (default) |
| Construcción envelope | Delegada a `EnvelopeService` | Inline en `buildEnvio()` |
| Manejo errores | Rollback + retry vía Job | Rollback + marca folio fallido (sin retry) |
| Logging granular | A `dte_emitidos.sii_response` + Job logs | A `dte_logs` + `dte_envios` (ya implementado) |

**Gaps:**

| # | Gap | Severidad | Corrección |
|---|---|---|---|
| 5.1 | Envío síncrono bloquea | 🟡 Media | Implementar `EnviarDteJob` queue para no bloquear endpoint POS. Conservar `DTE_SEND_SYNC=true` solo para testing |
| 5.2 | `buildEnvio()` inline vs `EnvelopeService` dedicado | 🟡 Media | Crear `EnvelopeService` similar a DTEngine, con responsabilidades separadas |

---

## 6. Tipos DTE soportados

| Tipo | DTEengine | api-a-conta-fact | Notas |
|---|---|---|---|
| 33 — Factura electrónica | ✅ Operativo | ✅ Implementado | Validado en certificación DTEngine |
| 34 — Factura exenta | ✅ Operativo | ✅ Implementado | |
| 39 — Boleta electrónica | ✅ Operativo, usa `rahue.sii.cl` (cert) | ✅ Implementado, hosts corregidos en commit nuevo | Host era incorrecto antes |
| 41 — Boleta exenta | ✅ Operativo | ⚠️ Validado en código pero no en constante `TIPOS_CON_RECEPTOR_IDENTIFICADO` | Falta declarar formalmente |
| 52 — Guía de despacho | ✅ Operativo | ✅ Implementado | |
| 56 — Nota de débito | ✅ Operativo | ✅ Implementado | |
| 61 — Nota de crédito | ✅ Operativo | ✅ Implementado | |

---

## 7. Endpoints SII (estado actualizado)

**Diferencia importante detectada hoy:** Boletas (39/41) usan host distinto al resto de DTEs.

| Operación | Cert (anteriormente documentado) | Cert (real, según DTEngine) |
|---|---|---|
| DTE facturas/notas/guías | maullin.sii.cl | maullin.sii.cl ✅ |
| **DTE boletas (39, 41)** | ~~maullin.sii.cl~~ ❌ | **rahue.sii.cl** ✅ |
| Libros (IECV) | maullin.sii.cl/cgi_dte/UPL/IECVUpload | maullin.sii.cl/cgi_dte/UPL/IECVUpload ✅ |

**Para producción:**

| Operación | Producción |
|---|---|
| DTE facturas/notas/guías | palena.sii.cl |
| **DTE boletas (39, 41)** | **pangal.sii.cl** |
| Libros | palena.sii.cl/cgi_dte/UPL/IECVUpload |

**Corregido en `SiiDteClient`:** método `urlEnvio(int $tipoDte)` ahora selecciona host correcto según tipo.

### Sobre `IECVUpload` para libros

- **DTEngine usa `IECVUpload`** confirmado en `SiiTransportService.php:211`.
- El `SiiLibroClient` recién creado **soporta ambos endpoints** via parámetro `$endpoint`:
  - `iecv` (default) → `/cgi_dte/UPL/IECVUpload` (oficial, idéntico a DTEngine)
  - `dte` → `/cgi_dte/UPL/DTEUpload` (experimental, para probar el pipeline sin cambiar endpoint)

Esto permite probar enviando libros al mismo endpoint que DTEs (caso de uso solicitado) sin perder la opción oficial.

---

## 8. Resumen ejecutivo — prioridades

**🔴 Antes de probar emisión real (cuando lleguen las credenciales):**

1. Gap 3.5 / `ANALISIS_FIRMAS.md` Gap 1 — Encoding ISO al escapar campos del DD
2. `ANALISIS_FIRMAS.md` Gap 3 — Embeber `<CAF>` completo en `<DD>`, no solo `<DA>`
3. `ANALISIS_FIRMAS.md` Gap 9 — Remover `DOM::saveXML` que invalida firma del DTE
4. `ANALISIS_FIRMAS.md` Gap 6 — Agregar `xsi:schemaLocation` al envelope
5. `ANALISIS_FIRMAS.md` Gap 5 — Usar `EnvioBOLETA` para 39/41

**🟡 Importantes pero no bloquean primera prueba:**

6. Gap 3.1 — Recalcular totales desde items
7. Gap 2.1 — Implementar `CafService::liberarFolio()`
8. `ANALISIS_FIRMAS.md` Gap 8 — `FchResol`/`NroResol` desde BD

**🟢 Mejoras a futuro:**

9. Gap 5.1 — Async via Job (mejora performance, no funcionalidad)
10. Gap 2.2 — Validación vencimiento CAF
11. Gap 5.2 — Refactor `EnvelopeService` separado

---

## 9. Cómo probar el envío de libros hoy

Con `SiiLibroClient` y el comando `dte:test-libro` puedes probar contra ambos endpoints:

```bash
# Envío oficial (IECVUpload)
php artisan dte:test-libro 1 \
  storage/app/dte/empresas/1/cafs/libro_ventas_2026-05.xml \
  --endpoint=iecv --tipo=VENTA

# Envío experimental (DTEUpload, mismo que facturas)
php artisan dte:test-libro 1 \
  storage/app/dte/empresas/1/cafs/libro_ventas_2026-05.xml \
  --endpoint=dte --tipo=VENTA
```

Cada intento se persiste en `libro_intentos` con la respuesta cruda del SII para diagnóstico.

---

## 10. Referencias

| Archivo | Para qué |
|---|---|
| `ANALISIS_FIRMAS.md` | 11 gaps de firma TED + XML DTE |
| `TRAZABILIDAD.md` | Modelo de datos comparado |
| `FLUJO_PRUEBAS.md` | Operativo: cómo preparar y emitir |
| `ERRORES.md` | Catálogo síntoma/causa/solución |
| `PLAN_MIGRACION.md` | Plan completo de migración |
