# Trazabilidad — modelo de datos vs DTEngine

Comparación entre el modelo de trazabilidad de `DTEngine` (sistema operativo de referencia) y el actual de `api-a-conta-fact`, gaps identificados y plan para cerrarlos.

---

## 1. Tablas equivalentes

| Concepto | DTEngine | api-a-conta-fact | Estado |
|---|---|---|---|
| Documento DTE emitido | `dte_emitidos` | `dte_documentos` | ✅ Equivalente |
| Detalle por ítem | (embebido en XML) | `dte_detalles` | ✅ Más estructurado |
| Folios consumidos | `caf_folio_usages` | `dte_folio_usages` | ✅ Equivalente |
| Certificados | `emisor_certificados` (cifrado en BD) | `dte_certificados` (path en disco) | ⚠️ Diferente estrategia |
| CAFs | `caf_files` (BD + cache disk) | `dte_cafs` (BD + cache disk) | ✅ Equivalente |
| Intentos de envío histórico | (campos en `dte_emitidos`) | `dte_envios` (vacía) | ❌ Stub sin columnas |
| Log de eventos | (`dte_emitidos.sii_response` json) | `dte_logs` (vacía) | ❌ Stub sin columnas |
| Libros (Ventas/Compras/Guías) | `libro_intentos` | NO existe | ❌ Falta completo |
| Intentos de certificación | `dte_intentos` | NO existe | ⚠️ No aplica (cert se mantiene en DTEngine) |

---

## 2. Gap detallado: `dte_envios` y `dte_logs` están vacías

Ambas migraciones (`2025_12_23_171848_create_dte_envios_table.php` y `2025_12_23_171904_create_dte_logs_table.php`) crearon tablas con solo `id + timestamps`. Los modelos `DteEnvio` y `DteLog` existen pero no se usan en el flujo actual porque no tienen columnas.

### 2.1 Diseño propuesto para `dte_envios`

Equivale a guardar cada **intento** de envío al SII (un DTE puede tener múltiples intentos si falla y se reintenta).

```php
Schema::create('dte_envios', function (Blueprint $table) {
    $table->id();
    $table->foreignId('dte_documento_id')->constrained('dte_documentos')->cascadeOnDelete();
    $table->string('track_id', 50)->nullable()->index();
    $table->enum('resultado', ['exito', 'error_http', 'sin_trackid', 'rechazado_estructura'])->index();
    $table->string('endpoint', 100);              // 'DTEUpload' | 'IECVUpload' | ...
    $table->string('ambiente', 20);               // 'certificacion' | 'produccion'
    $table->unsignedSmallInteger('http_status')->nullable();
    $table->longText('xml_enviado')->nullable();
    $table->longText('respuesta_raw')->nullable();
    $table->json('respuesta_parseada')->nullable();
    $table->string('error_mensaje', 500)->nullable();
    $table->timestamp('enviado_en');
    $table->timestamps();

    $table->index(['dte_documento_id', 'resultado']);
});
```

### 2.2 Diseño propuesto para `dte_logs`

Para eventos del ciclo de vida del DTE (más granular que `dte_documentos.estado`).

```php
Schema::create('dte_logs', function (Blueprint $table) {
    $table->id();
    $table->foreignId('dte_documento_id')->nullable()->constrained()->nullOnDelete();
    $table->unsignedBigInteger('par_empresa_id')->nullable()->index();
    $table->string('evento', 50)->index();        // 'folio_reservado' | 'xml_generado' | 'firmado' | 'enviado' | 'aceptado' | etc.
    $table->enum('nivel', ['info', 'warning', 'error'])->default('info');
    $table->string('mensaje', 500)->nullable();
    $table->json('contexto')->nullable();
    $table->timestamps();

    $table->index(['par_empresa_id', 'evento', 'created_at']);
});
```

### 2.3 Migraciones pendientes

Hay que reemplazar las migraciones stub o agregar `alter` que añadan las columnas. Plan recomendado:

1. Crear migración nueva `2026_05_19_000001_complete_dte_envios_table.php` con `Schema::table()` (ya que la tabla existe pero está vacía)
2. Idem para `dte_logs`
3. Refactorizar `DteEmissionService` para escribir en ambas
4. Refactorizar `SiiDteClient` para escribir en `dte_envios` cada intento (no solo el último)

---

## 3. Gap mayor: módulo de libros no existe

DTEngine tiene `libro_intentos` con tipos `VENTA | COMPRA | GUIA`. En `api-a-conta-fact` esto no existe todavía.

### 3.1 Diseño preliminar `libro_intentos`

```php
Schema::create('libro_intentos', function (Blueprint $table) {
    $table->id();
    $table->foreignId('par_empresa_id')->constrained();
    $table->enum('tipo_libro', ['VENTA', 'COMPRA', 'GUIA'])->index();
    $table->string('periodo', 7);                 // 'YYYY-MM'
    $table->enum('operacion', ['TOTAL', 'PARCIAL', 'AJUSTE']);
    $table->string('track_id', 50)->nullable()->index();
    $table->enum('estado', ['pendiente', 'firmado', 'enviado', 'aceptado', 'rechazado', 'completado']);
    $table->longText('xml_libro')->nullable();
    $table->string('xml_path', 500)->nullable();
    $table->char('xml_sha256', 64)->nullable();
    $table->string('identificador_envio', 100)->nullable();
    $table->longText('respuesta_raw')->nullable();
    $table->json('respuesta_resumen')->nullable();
    $table->timestamps();

    $table->index(['par_empresa_id', 'tipo_libro', 'periodo']);
});
```

### 3.2 Servicios necesarios

- `LibroXmlBuilder.php` — construye `<LibroCompraVenta>` o `<LibroGuia>`
- `LibroSignatureService.php` — firma con cert de la empresa
- `SiiLibroClient.php` — POST a `IECVUpload` (endpoint distinto al de DTE)
- Endpoint `POST /api/libros/enviar` con payload `{ par_empresa_id, tipo_libro, periodo, operacion }`

### 3.3 Importante: endpoint SII separado

Libros se envían a `IECVUpload`, **no** a `DTEUpload`. Esto se confirmó investigando DTEngine:

| Endpoint cert | Endpoint prod | Para qué |
|---|---|---|
| `https://maullin.sii.cl/cgi_dte/UPL/DTEUpload` | `https://palena.sii.cl/cgi_dte/UPL/DTEUpload` | Facturas, boletas, guías, notas |
| `https://maullin.sii.cl/cgi_dte/UPL/IECVUpload` | `https://palena.sii.cl/cgi_dte/UPL/IECVUpload` | Libro de Ventas, Compras, Guías |

El `SiiDteClient` actual solo conoce `DTEUpload`. Para libros hay que crear `SiiLibroClient` o agregar método con URL distinta.

---

## 4. Estrategia de almacenamiento — comparación

| Recurso | DTEngine | api-a-conta-fact actual | Decisión |
|---|---|---|---|
| PFX | BD cifrado con `Crypt::encryptString` en `emisor_certificados.pfx_cifrado` | Path al archivo en disco en `dte_certificados.ruta_pfx`, password plano en `password_pfx` | Mantener path en disco (más simple). **TODO: cifrar password_pfx** |
| CAF XML | BD `caf_files.xml_content` + cache disk `storage/cafs/` | BD `dte_cafs.caf_xml` + opcional `dte_cafs.caf_path` | OK actual |
| XML DTE emitido | BD `dte_emitidos.xml_dte` (longtext) | BD `dte_documentos.xml_original` + `xml_firmado` (longtext) | OK actual, más granular |
| XML envío al SII | (no persistido aparte) | Pendiente: `dte_envios.xml_enviado` cuando se complete la tabla | Mejorar al completar `dte_envios` |
| Respuesta SII raw | BD `dte_emitidos.sii_response` (json) | BD `dte_documentos.respuesta_sii` (text) | Cambiar tipo a `json` o `longtext` |

### 4.1 TODO: cifrar `password_pfx`

Hoy se guarda en plano. Cambio sugerido:

```php
// Al guardar
$cert->password_pfx = Crypt::encryptString($passwordPlano);

// Al leer en CertificateService
$password = Crypt::decryptString($cert->password_pfx);
```

Requiere migración de datos existentes (si ya hay PFX cargado con password plano).

---

## 5. Estados del ciclo de vida del DTE

Estado actual en `dte_documentos.estado`:
```
pendiente | timbrado | firmado | enviado | aceptado | rechazado | observado
```

Estado del flujo conceptual (más granular, usado por el motor):
```
1. folio_reservado   ← entra a dte_folio_usages
2. xml_generado      ← XML construido sin firmar
3. firmado           ← Documento + SetDTE firmados
4. enviado           ← SII recibió, hay TRACKID
5. aceptado          ← SII validó (EPR)
6. aceptado_reparos  ← SII aceptó con observaciones (EPS)
7. rechazado         ← SII rechazó (RCT, RFR, SOK, FOK)
8. fallido           ← Error técnico (sin llegar al SII)
```

Recomendación: expandir el enum de `dte_documentos.estado` para cubrir todos los casos:

```sql
ALTER TABLE dte_documentos MODIFY estado ENUM(
    'pendiente', 'folio_reservado', 'xml_generado', 'firmado',
    'enviado', 'aceptado', 'aceptado_reparos', 'rechazado',
    'observado', 'fallido'
) NOT NULL DEFAULT 'pendiente';
```

---

## 6. Plan de cierre de gaps (orden recomendado)

1. **Completar `dte_envios`** — primer gap a cerrar para tener trazabilidad real de envíos al SII
2. **Completar `dte_logs`** — útil para debugging del flujo
3. **Cifrar `password_pfx`** — preventivo, antes de cargar certs reales
4. **Expandir enum `dte_documentos.estado`** — para reflejar el ciclo completo
5. **Crear `libro_intentos`** — habilita módulo de libros (Fase 14 del PLAN)
6. **Implementar `LibroXmlBuilder` + `SiiLibroClient`** — envío de libros mensuales

Cada uno es una migración + cambios mínimos en el `DteEmissionService`. Se puede hacer en sesiones independientes sin bloquear las pruebas de DTE individual (que pueden empezar con las tablas como están hoy).
