# Flujo de pruebas — Motor interno DTE

Documento operativo: cómo dejar los archivos necesarios y ejecutar la primera emisión real en ambiente de certificación (`maullin.sii.cl`).

Sirve como checklist reproducible para validar:
- Carga de certificado PFX
- Carga de CAF y reserva de folio
- Construcción + firma TED
- Construcción + firma XML DTE
- Envío al SII y obtención de TRACKID
- Comparación contra fixtures de `DTEngine\fuente-verdad`

---

## 1. Pre-requisitos físicos

Necesitas tres archivos físicos antes de empezar:

| Archivo | Origen | Ambiente |
|---|---|---|
| Certificado digital `.pfx` o `.p12` | E-CertChile, Acepta, Cámara de Comercio, etc. | Mismo cert sirve para certificación y producción |
| CAF (Código de Autorización de Folios) `.xml` | SII → Servicios online → Solicitud de folios | **Certificación tiene CAFs distintos a producción** |
| RUT empresa habilitado en SII | Empresa cliente | Habilitar en SII → Boleta electrónica → Inicio de operaciones |

Para la primera prueba bastará con:
- **1 certificado** del firmante autorizado (representante legal o usuario habilitado)
- **1 CAF de boleta electrónica (tipo 39)** con al menos 1 folio sin usar
- Empresa con `facturacion=1` en `par_empresas`

---

## 2. Estructura de carpetas en disco

Crear la siguiente estructura bajo `storage/app/`:

```text
storage/app/dte/
└── empresas/
    └── {par_empresa_id}/
        ├── certificados/
        │   └── certificado.pfx              ← tu PFX aquí
        ├── cafs/
        │   ├── FacturaElectronica_1_50.xml
        │   ├── BoletaElectronica_1_100.xml  ← tu CAF de prueba
        │   └── GuiaDespacho_1_30.xml
        ├── xml_generados/                   ← creado por el motor
        │   └── {tipo_dte}/{YYYY}/{MM}/
        │       └── {folio}.xml
        └── xml_enviados/                    ← creado por el motor
            └── {tipo_dte}/{YYYY}/{MM}/
                └── envio_{track_id}.xml
```

**Para empresa_id = 1, los paths absolutos son:**

```text
C:\xampp\htdocs\api-a-conta-fact\storage\app\dte\empresas\1\certificados\certificado.pfx
C:\xampp\htdocs\api-a-conta-fact\storage\app\dte\empresas\1\cafs\BoletaElectronica_1_100.xml
```

Crear las carpetas con:

```powershell
New-Item -ItemType Directory -Force `
  C:\xampp\htdocs\api-a-conta-fact\storage\app\dte\empresas\1\certificados, `
  C:\xampp\htdocs\api-a-conta-fact\storage\app\dte\empresas\1\cafs, `
  C:\xampp\htdocs\api-a-conta-fact\storage\app\dte\empresas\1\xml_generados, `
  C:\xampp\htdocs\api-a-conta-fact\storage\app\dte\empresas\1\xml_enviados
```

> Nota seguridad: estos directorios están bajo `storage/app/` que NO es público por defecto. Aún así, no commitear archivos `.pfx` ni CAFs al repo (.gitignore ya excluye `storage/`).

---

## 3. Registros mínimos en BD

Después de dejar los archivos en disco, hay que crear los registros que apuntan a ellos:

### 3.1 Empresa con facturación activa

```sql
INSERT INTO par_empresas (id, nombre, razon_social, rut, giro, direccion, comuna, ciudad, facturacion, created_at, updated_at)
VALUES (1, 'Empresa Prueba', 'EMPRESA PRUEBA SPA', '76543210-K',
        'Servicios informaticos', 'Av. Test 123', 'Santiago', 'Santiago',
        1, NOW(), NOW());
```

Confirmar: `SELECT id, rut, facturacion FROM par_empresas WHERE id = 1;`

### 3.2 Certificado digital

```sql
INSERT INTO dte_certificados (par_empresa_id, nombre_certificado, ruta_pfx, password_pfx,
                              rut_emisor, fecha_emision, fecha_vencimiento, activo, created_at, updated_at)
VALUES (1, 'Cert prueba certificación',
        'dte/empresas/1/certificados/certificado.pfx',
        'TU_PASSWORD_AQUI',
        '11111111-1',
        '2025-01-01', '2027-01-01', 1, NOW(), NOW());
```

- `ruta_pfx` es **relativa a `storage/app/`** (no absoluta)
- `password_pfx` por ahora va plano — pendiente cifrar con `Crypt::encryptString` (ver TRAZABILIDAD.md §gaps)

### 3.3 CAF de boleta electrónica

```sql
INSERT INTO dte_cafs (par_empresa_id, tipo_dte, folio_desde, folio_hasta, fecha_autorizacion,
                     caf_xml, caf_path, activo, usado_hasta, created_at, updated_at)
VALUES (1, 39, 1, 100, '2026-05-01',
        '<contenido completo del CAF.xml>',
        'dte/empresas/1/cafs/BoletaElectronica_1_100.xml',
        1, 0, NOW(), NOW());
```

Truco para cargar el XML del CAF sin pegar 100 líneas en SQL:

```php
// php artisan tinker
$xml = file_get_contents(storage_path('app/dte/empresas/1/cafs/BoletaElectronica_1_100.xml'));
\App\Models\Dte\DteCaf::create([
    'par_empresa_id' => 1,
    'tipo_dte' => 39,
    'folio_desde' => 1,
    'folio_hasta' => 100,
    'fecha_autorizacion' => '2026-05-01',
    'caf_xml' => $xml,
    'caf_path' => 'dte/empresas/1/cafs/BoletaElectronica_1_100.xml',
    'activo' => 1,
    'usado_hasta' => 0,
]);
```

---

## 4. Configuración de entorno

Editar `.env` del workspace `api-a-conta-fact`:

```env
# Activar motor interno
DTE_EMITTER=internal
DTE_SII_ENV=certificacion
DTE_STORAGE_DISK=local
DTE_SEND_SYNC=true
DTE_FAIL_MODE=warn
```

Limpiar config cache si existe:

```bash
php artisan config:clear
```

---

## 5. Primera emisión — Boleta 39 directa (sin POS)

Endpoint: `POST /api/dte/emitir`

Payload mínimo:

```json
{
  "par_empresa_id": 1,
  "tipo_dte": 39,
  "fecha_emision": "2026-05-19",
  "receptor_rut": "66666666-6",
  "receptor_razon_social": "Cliente generico",
  "receptor_direccion": "SIN DIRECCION",
  "items": [
    {
      "nombre": "Producto de prueba",
      "cantidad": 1,
      "precio": 10000,
      "exento": false
    }
  ],
  "enviar_sii": true
}
```

Ejemplo con curl (PowerShell):

```powershell
$body = @'
{
  "par_empresa_id": 1,
  "tipo_dte": 39,
  "fecha_emision": "2026-05-19",
  "receptor_rut": "66666666-6",
  "receptor_razon_social": "Cliente generico",
  "receptor_direccion": "SIN DIRECCION",
  "items": [{"nombre":"Test","cantidad":1,"precio":10000,"exento":false}],
  "enviar_sii": true
}
'@

curl.exe -X POST http://localhost:8000/api/dte/emitir `
  -H "Content-Type: application/json" `
  -d $body
```

Respuesta esperada:

```json
{
  "ok": true,
  "dte_documento_id": 1,
  "tipo_dte": 39,
  "folio": 1,
  "estado": "enviado",
  "track_id": "1234567890",
  "xml_url": "/api/dte/1/xml"
}
```

---

## 6. Validaciones intermedias (antes de pegarle al SII)

Si la emisión falla, validar uno por uno los pasos:

### 6.1 Validar carga del certificado

```bash
php artisan tinker
```

```php
$svc = app(\App\Services\FacturacionElectronica\Services\CertificateService::class);
$cert = $svc->load(1);
// debe retornar ['private_key' => ..., 'certificate' => ..., 'metadata' => [...]]
print_r($cert['metadata']);
```

### 6.2 Validar parseo del CAF

```php
$caf = \App\Models\Dte\DteCaf::where('par_empresa_id', 1)
    ->where('tipo_dte', 39)->where('activo', 1)->first();
$cafSvc = app(\App\Services\FacturacionElectronica\Services\CafService::class);
$parsed = $cafSvc->parsearCaf($caf->caf_xml);
print_r($parsed);
// debe traer: rut_emisor, rango_d, rango_h, rsask, idk
```

### 6.3 Validar reserva de folio (transacción)

```php
$folio = $cafSvc->reservarFolio(1, 39);
echo "Folio reservado: $folio\n";
// debe insertar en dte_folio_usages con estado=reservado
```

### 6.4 Generar TED sin firmar XML completo

```php
$ted = app(\App\Services\FacturacionElectronica\Services\TedBuilder::class)
    ->build($parsed, [...datos del DTE...]);
echo $ted; // <TED><DD>...</DD><FRMT>...</FRMT></TED>
```

---

## 7. Comparación contra `DTEngine/fuente-verdad`

Esta es la validación clave para confirmar que el XML generado es estructuralmente equivalente al que el SII acepta.

### 7.1 Fixtures disponibles

Carpeta: `C:\xampp\htdocs\DTEngine\fuente-verdad\`

| Subcarpeta | Contenido útil para validación |
|---|---|
| `simulacion/` | XMLs simplificados por tipo (factura, guía, boleta) — bueno para empezar |
| `certificacion/` | Sets de prueba ACEPTADOS por el SII (los más confiables) |
| `muestras/` | DTEs en HTML + PDF + TED.xml — útil para ver TED aislado |
| `intercambio/` | Documentos de respuesta comercial — no aplica en fase actual |

### 7.2 Copiar fixtures como referencia

```powershell
New-Item -ItemType Directory -Force C:\xampp\htdocs\api-a-conta-fact\tests\Fixtures\DteEngine

Copy-Item C:\xampp\htdocs\DTEngine\fuente-verdad\simulacion\*.xml `
          C:\xampp\htdocs\api-a-conta-fact\tests\Fixtures\DteEngine\

Copy-Item C:\xampp\htdocs\DTEngine\fuente-verdad\certificacion\set_basico_*.xml `
          C:\xampp\htdocs\api-a-conta-fact\tests\Fixtures\DteEngine\
```

### 7.3 Comparación TED (riesgo técnico más alto)

El TED es la parte donde más fácilmente puede haber diferencias de canonicalización.

Pasos:
1. Tomar un CAF de los fixtures de DTEngine
2. Emitir un DTE con los **mismos datos** en api-a-conta-fact y en DTEngine
3. Extraer el nodo `<TED>` de cada XML resultado
4. Diff línea a línea — debe ser **idéntico** (no solo equivalente)

Si la firma `<FRMT>` no coincide:
- Comparar el contenido del nodo `<DD>` ANTES de firmar — debe ser byte-idéntico
- Verificar canonicalización: `DOMDocument::C14N(false, false)`
- Verificar encoding: ISO-8859-1 vs UTF-8
- Ver `ERRORES.md` #1 y #2

### 7.4 Comparación XML DTE completo

Para tipo 39 (boleta) y tipo 33 (factura):

```powershell
# 1. Extraer DTE generado por api-a-conta-fact
$xmlNuevo = (Invoke-WebRequest "http://localhost:8000/api/dte/1/xml").Content

# 2. Tomar fixture equivalente de DTEngine
$xmlRef = Get-Content C:\xampp\htdocs\DTEngine\fuente-verdad\simulacion\factura_dte_33_folio_52.xml -Raw

# 3. Comparar nodo por nodo (estructura, no firma — la firma será distinta)
```

Diferencias esperadas (NO son problema):
- Folios distintos
- Fechas distintas
- IDs únicos generados
- Hash y firmas distintas

Diferencias que SÍ son problema:
- Estructura de nodos (orden, anidamiento)
- Atributos faltantes o extra
- Encoding distinto (UTF-8 vs ISO-8859-1)
- Canonicalización diferente

---

## 8. Verificar estado en SII

Después de emitir, esperar 5-10 minutos y consultar:

```powershell
curl.exe http://localhost:8000/api/dte/1/consultar-sii
```

Estados SII posibles:
- `EPR`: Envío Procesado (OK)
- `EPS`: Envío Procesado pero con esquemas válidos
- `RCT`: Rechazo total
- `RFR`: Rechazo por falta de RUT
- `SOK`: Schema OK pero documento rechazado
- `FOK`: Firma OK pero documento rechazado

Si el track_id no aparece o aparece `RCT`:
- Ver `respuesta_sii` en `dte_documentos`
- Revisar `storage/logs/laravel.log` para detalle
- Comparar XML enviado vs fixture de DTEngine

---

## 9. Endpoints SII reales (referencia)

| Operación | Certificación | Producción |
|---|---|---|
| DTE facturas/notas/guías (33, 34, 52, 56, 61) | `https://maullin.sii.cl/cgi_dte/UPL/DTEUpload` | `https://palena.sii.cl/cgi_dte/UPL/DTEUpload` |
| **DTE boletas (39, 41)** | `https://rahue.sii.cl/cgi_dte/UPL/DTEUpload` | `https://pangal.sii.cl/cgi_dte/UPL/DTEUpload` |
| Envío Libros (IECV) | `https://maullin.sii.cl/cgi_dte/UPL/IECVUpload` | `https://palena.sii.cl/cgi_dte/UPL/IECVUpload` |
| Semilla (CrSeed) | `https://maullin.sii.cl/DTEWS/CrSeed.jws` | `https://palena.sii.cl/DTEWS/CrSeed.jws` |
| Token | `https://maullin.sii.cl/DTEWS/GetTokenFromSeed.jws` | `https://palena.sii.cl/DTEWS/GetTokenFromSeed.jws` |
| Consulta estado | `https://maullin.sii.cl/DTEWS/QueryEstUp.jws` | `https://palena.sii.cl/DTEWS/QueryEstUp.jws` |

**Importante:**
- **Boletas usan host distinto** (`rahue.sii.cl` en cert, `pangal.sii.cl` en prod). `SiiDteClient::urlEnvio($tipoDte)` selecciona correcto automáticamente.
- **Libros se pueden enviar por IECVUpload (oficial) o DTEUpload (experimental)**. `SiiLibroClient` soporta ambos via parámetro `endpoint`. Comando: `php artisan dte:test-libro {id} {xml} --endpoint=iecv|dte`.
- Token SII expira en **90 segundos**. `SiiAuthService` cachea por 80s (margen).

---

## 10. Troubleshooting rápido

| Síntoma | Causa probable | Dónde mirar |
|---|---|---|
| `CertificateException: PFX no se puede desencriptar` | Password o archivo PFX corrupto | `ERRORES.md` #3 |
| `CafException: CAF agotado` | `dte_cafs.usado_hasta >= folio_hasta` | Cargar nuevo CAF |
| `SII no devolvió TOKEN` | Semilla expirada (>2 min) o cert vencido | `ERRORES.md` #5 |
| `SII no devolvió TRACKID` | XML rechazado sin track | `ERRORES.md` #6 + ver `respuesta_sii` |
| `RCT` (rechazo total) en consulta | Estructura XML inválida | Comparar contra `fuente-verdad/` |
| openssl_sign retorna false | Llave RSASK antigua incompatible | `ERRORES.md` #1 |

---

## 11. Próximas validaciones (después de la primera emisión exitosa)

1. Emitir Factura 33 con receptor real (RUT distinto a 66666666-6)
2. Emitir Boleta exenta 41
3. Emitir Nota de crédito 61 con referencia a factura previa
4. Emitir Guía de despacho 52
5. Emitir Libro de Ventas mensual (cuando exista LibroDteService)
6. Stress test concurrencia: 10 boletas simultáneas → ningún folio duplicado
