# Plan de migración — Portar DTEngine a api-a-conta-fact

> Documento operativo del plan. Decisión tomada: **Opción A — Fork Derafu PHP 8.2 + porte literal de DTEengine**.

## Contexto

Después de 6 iteraciones contra el SII de certificación con la implementación propia (commits del 22-26 mayo 2026), el motor sigue produciendo el reparo `TED-2-510 Firma Timbre Electrónico Incorrecta`. El último intento (track `0249659090`) cambió el estado a `RSC - Rechazado por Schema` por desalineación entre `DigestValue` de firma del DTE y C14N que el SII recalcula al verificar.

La causa raíz es **acumulación de diferencias sutiles** entre nuestra implementación y la que ya certificó SII (DTEengine):

- C14N inclusive arrastra namespaces heredados del envelope que no estaban presentes al firmar el DTE individual.
- Reconstrucción del CAF embebido — DOM `saveXML` vs string concatenation determinística.
- Whitespace en `<Signature>` introducido por xmlseclibs DOM appendChild.
- Orden de elementos `<KeyValue>`/`<X509Data>` dentro de `<KeyInfo>`.

Cada iteración consume folios reales (28 facturas, 21 NC, 7 ND hasta ahora). Continuar prueba y error es ineficiente y consume CAFs.

## Decisión

Reemplazar la capa de firma + serialización XML por código **portado literalmente** de DTEengine, que ya pasó certificación SII en el RUT `76148283-1` (fixtures en `DTEengine/fuente-verdad/certificacion/`).

Las dependencias `derafu/*` que DTEengine usa requieren PHP 8.5 según su `composer.json`, pero el **código real no usa features posteriores a PHP 8.1**. Forkearemos los 3 paquetes, bajaremos la restricción a `^8.2`, y los instalaremos en api-a-conta-fact.

## Alcance del cambio

### Lo que SE reemplaza

| Archivo actual | Reemplazo |
|---|---|
| `app/Services/FacturacionElectronica/Services/TedBuilder.php` | Port literal de `DTEengine/.../TedBuilder.php` |
| `app/Services/FacturacionElectronica/Services/DteXmlBuilder.php` | Port literal de `DTEengine/.../XmlDteBuilder.php` |
| `app/Services/FacturacionElectronica/Services/EnvelopeService.php` | Port literal de `DTEengine/.../EnvelopeService.php` |
| `app/Services/FacturacionElectronica/Services/XmlSignatureService.php` | Reemplazado por `Derafu\Signature\Service\SignatureService` |
| `app/Services/FacturacionElectronica/Services/CafService.php` | Port literal de `DTEengine/.../CafManager.php` |
| `app/Services/FacturacionElectronica/Services/CertificateService.php` | Port literal de `DTEengine/.../EmisorCertificateService.php` |
| `app/Services/FacturacionElectronica/Services/SiiAuthService.php` | Absorbido en `SiiTransportService.php` (port literal) |
| `app/Services/FacturacionElectronica/Services/SiiDteClient.php` | Absorbido en `SiiTransportService.php` (port literal) |
| `app/Services/FacturacionElectronica/Services/DteEmissionService.php` | Port literal de `DTEengine/.../DteService.php` |
| `app/Services/FacturacionElectronica/Services/LibroBuilder.php` | Port literal de `DTEengine/.../LibroBuilder.php` |
| `app/Services/FacturacionElectronica/Services/LibroRespuestaParser.php` | Port literal de `DTEengine/.../LibroRespuestaParser.php` |

### Lo que se MANTIENE intacto

- `app/Services/FacturacionElectronica/Contracts/*.php` (interfaces existentes)
- `app/Services/FacturacionElectronica/DTO/*.php` (DTOs existentes)
- `app/Services/FacturacionElectronica/Emitters/InternalDteEmitter.php` + `SimpleApiDteEmitter.php`
- `app/Services/FacturacionElectronica/Mappers/PosVentaToDteMapper.php`
- `app/Console/Commands/DteEmitirSetPruebas.php`
- `app/Console/Commands/DteConsultarEstado.php`
- `app/Console/Commands/DteGenerarLibro.php`
- `app/Console/Commands/DteTestLibro.php`
- `app/Console/Commands/DteSetupEmpresa.php`
- Todas las migraciones (`database/migrations/*dte*.php`)
- Modelos: `DteCaf`, `DteCertificado`, `DteDocumento`, `DteDetalle`, `DteEnvio`, `DteLog`, `DteFolioUsage`
- Tabla `par_empresas` con sus columnas (`acteco`, `ciudad`, etc.)
- JSON del set (`tests/Fixtures/SetPruebasSII/eventsud_set_basico.json`)
- Seeder `EventsudCertificacionSeeder`

## Forks (LOCALES en disco)

Los forks están clonados localmente — no se referencian desde GitHub, se montan vía Composer con `"type": "path"`.

| Paquete | Path local | Branch a crear |
|---|---|---|
| `derafu/xml` | `C:\Users\baezs\OneDrive\Escritorio\forks-derafu\xml` | `php-8.2-compat` |
| `derafu/certificate` | `C:\Users\baezs\OneDrive\Escritorio\forks-derafu\certificate` | `php-8.2-compat` |
| `derafu/signature` | `C:\Users\baezs\OneDrive\Escritorio\forks-derafu\signature` | `php-8.2-compat` |

### Cambios a hacer en cada fork

**1. `derafu/xml/composer.json`**:
```diff
- "php": "^8.5"
+ "php": "^8.2"
```

**2. `derafu/certificate/composer.json`**:
```diff
- "php": "^8.5"
+ "php": "^8.2"
```

**3. `derafu/signature/composer.json`**:
```diff
- "php": "^8.5",
+ "php": "^8.2",
  "derafu/certificate": "dev-main",
  "derafu/xml": "dev-main"
```
(Las dependencias entre derafu se resuelven via path en api-a-conta-fact `composer.json`.)

### Branch creation

En cada fork, branch `php-8.2-compat` desde `main`. Esto deja `main` limpio siguiendo upstream.

```bash
cd <path>
git checkout -b php-8.2-compat
# editar composer.json
git add composer.json
git commit -m "chore: bajar restricción PHP de ^8.5 a ^8.2 para api-a-conta-fact compat"
```

### Composer en api-a-conta-fact

En `composer.json`:

```json
{
    "require": {
        "derafu/xml": "dev-php-8.2-compat",
        "derafu/certificate": "dev-php-8.2-compat",
        "derafu/signature": "dev-php-8.2-compat"
    },
    "repositories": [
        { "type": "path", "url": "C:/Users/baezs/OneDrive/Escritorio/forks-derafu/xml",         "options": { "symlink": false } },
        { "type": "path", "url": "C:/Users/baezs/OneDrive/Escritorio/forks-derafu/certificate", "options": { "symlink": false } },
        { "type": "path", "url": "C:/Users/baezs/OneDrive/Escritorio/forks-derafu/signature",   "options": { "symlink": false } }
    ]
}
```

`symlink: false` para que copie los archivos en `vendor/` (evita problemas en Windows con permisos OneDrive).

### Verificación realizada del código

Inspeccioné `vendor/derafu/{xml,certificate,signature}/src/*` en DTEengine. **No usan**:
- `array_find`/`array_any`/`array_all`/`array_find_key` (PHP 8.4)
- `json_validate` (PHP 8.3)
- Property hooks (PHP 8.4)
- Asymmetric visibility (PHP 8.4)
- `final` constants en interfaces (PHP 8.2 lo soporta, pero no es 8.5-only)

**Sí usan**:
- `readonly` properties (PHP 8.1+) ✓
- `declare(strict_types=1)` (PHP 7.0+) ✓
- Constructor property promotion (PHP 8.0+) ✓
- Enums (PHP 8.1+) ✓
- `match` (PHP 8.0+) ✓

Conclusión: la única adaptación necesaria es el `composer.json`.

## Archivos DTEengine a portar

### Prioridad 1 — Núcleo de firma DTE (3,389 líneas)

| Archivo origen | Destino api-a-conta-fact | Adaptaciones |
|---|---|---|
| `DTEengine/app/Services/SII/CertificateManager.php` (31 ln) | `app/Services/FacturacionElectronica/Services/CertificateManager.php` | Mantener igual (solo wrapping de Derafu) |
| `DTEengine/app/Services/SII/EmisorCertificateService.php` (58 ln) | Reemplaza `CertificateService.php` | Modelo `Emisor` → `ParEmpresa`, columna `cert_pfx` → `dte_certificados.ruta_pfx` |
| `DTEengine/app/Services/SII/CafManager.php` (285 ln) | Reemplaza `CafService.php` | Modelo `CafFile` → `DteCaf`, `CafFolioUsage` → `DteFolioUsage`, `Emisor` → `ParEmpresa` |
| `DTEengine/app/Services/SII/TedBuilder.php` (168 ln) | Reemplaza `TedBuilder.php` | Recibe `DteCaf` en vez de `CafFile` |
| `DTEengine/app/Services/SII/XmlDteBuilder.php` (194 ln) | Reemplaza `DteXmlBuilder.php` | Mantener interface |
| `DTEengine/app/Services/SII/EnvelopeService.php` (292 ln) | Reemplaza `EnvelopeService.php` | Recibe `ParEmpresa` en vez de `Emisor` |
| `DTEengine/app/Services/SII/SiiTransportService.php` (672 ln) | Reemplaza `SiiAuthService.php` + `SiiDteClient.php` | Recibe `ParEmpresa` en vez de `Emisor` |
| `DTEengine/app/Services/SII/DteService.php` (125 ln) | Reemplaza `DteEmissionService.php` | Mantener firma de `emitir(DteEmissionRequest)` y `emitirBatch()` |
| `DTEengine/app/Services/SII/DataMapper.php` (197 ln) | Nuevo: `app/Services/FacturacionElectronica/Services/DataMapper.php` | Mapeo de arrays para `XmlEncoder` |

### Prioridad 2 — Libros (para fase libros del set certificación)

| Archivo origen | Destino |
|---|---|
| `DTEengine/.../LibroBuilder.php` (1376 ln) | Reemplaza `LibroBuilder.php` |
| `DTEengine/.../LibroRespuestaParser.php` (71 ln) | Reemplaza `LibroRespuestaParser.php` |

### Fuera de scope inicial

| Archivo origen | Motivo |
|---|---|
| `CertificationSetService.php` (639 ln) | Set de pruebas — ya tenemos comando propio `dte:emitir-set-pruebas` |
| `DteCertificationCaseService.php` (623 ln) | Casos certificación — usamos JSON del set |
| `DteCertificationCaseCatalog.php` (207 ln) | Catálogo — no aplica |
| `LibroCompraSetPruebaFactory.php` (187 ln) | Solo certificación |
| `ReferenceDocumentService.php` (230 ln) | Solo para certificación interna DTEengine |
| `SimulationPreparationService.php` (470 ln) | Pre-certificación, no productivo |
| `IntercambioPreparationService.php` (551 ln) | Módulo intercambio (post-certificación) |

## Adaptaciones Laravel 13 → Laravel 9

DTEengine usa Laravel 13. Cambios al portar:

| L13 (DTEengine) | L9 (api-a-conta-fact) | Notas |
|---|---|---|
| `Cast::using()` con `Attribute::make()` | Igual disponible en L9 | Sin cambio |
| `Eloquent\Casts\AsEnum` | `'cast' => Enum::class` también funciona | Compatible |
| `Schema::hasColumn()` | Igual | Sin cambio |
| `Storage::disk()` | Igual | Sin cambio |
| `Http::asMultipart()` | Igual | Sin cambio |
| `Cache::remember()` | Igual | Sin cambio |
| `now()->format()` | Igual | Sin cambio |
| UUIDs con `HasUuids` trait | L9 no lo tiene nativo | Usamos id autoincrement (nuestros modelos no son UUID) |
| `array_is_list()` | PHP 8.1+ | Disponible en 8.2 ✓ |

**Mayor diferencia**: DTEengine usa el trait `HasUuids` para modelos. Nuestros modelos (`DteCaf`, `DteDocumento`, etc.) usan auto-increment. **No portamos modelos**, solo adaptamos los services para usar nuestros modelos.

## Adaptación de modelos

Diferencias entre modelos DTEengine y api-a-conta-fact:

| DTEengine | api-a-conta-fact | Mapeo de columnas relevantes |
|---|---|---|
| `Emisor` (UUID) | `ParEmpresa` (id autoincrement) | `rut` ≈, `razon_social` ≈, `giro` ≈, `direccion` ≈, `acteco` ≈, `ciudad` ≈, `fecha_resolucion`/`nro_resolucion` → config(`dte.fch_resol`/`nro_resol`) |
| `CafFile` (UUID) | `DteCaf` (id autoincrement) | `emisor_id` → `par_empresa_id`, `xml_caf` → `caf_xml`, `tipo_dte` ≈, `folio_desde`/`folio_hasta` ≈, `ambiente` → derived from `config('dte.sii_env')` |
| `CafFolioUsage` (UUID) | `DteFolioUsage` (id autoincrement) | `emisor_id` → `par_empresa_id`, `caf_file_id` → `dte_caf_id`, `estado` ≈ (nuestros estados: reservado/emitido/enviado/aceptado_sii/rechazado_sii/fallido/anulado/liberado) |
| `DteEmitido` (UUID) | `DteDocumento` (id autoincrement) | `emisor_id` → `par_empresa_id`, `xml_dte` → `xml_firmado`, `track_id` ≈, `estado_sii` → `estado`, `sii_response` → `respuesta_sii` |
| `EmisorCertificado` (UUID) | `DteCertificado` (id autoincrement) | `pfx_binario` (BLOB) → `ruta_pfx` (path en disco), `password_pfx` ≈ |

**Importante**: DTEengine guarda el PFX en BD (BLOB cifrado). Nosotros lo guardamos en disco (`storage/app/dte/empresas/{id}/certificados/`). El port debe adaptarse a leer del disco.

## Plan paso a paso

### Etapa 0 — Preparación (forks locales)

Los forks están clonados en `C:\Users\baezs\OneDrive\Escritorio\forks-derafu\{xml,certificate,signature}`.

**Tareas (Claude las ejecuta):**

1. En cada fork: `git checkout -b php-8.2-compat`
2. En cada `composer.json`: cambiar `"php": "^8.5"` por `"php": "^8.2"`
3. Commit + push (opcional, también pueden quedar como branches locales)
4. Verificar que cada fork sigue cargando: `cd <fork>; composer validate`

### Etapa 1 — Configurar Composer (path repositories)

**Tarea:**

1. Editar `c:/xampp/htdocs/api-a-conta-fact/composer.json` para agregar:

```json
{
    "require": {
        "derafu/xml": "dev-php-8.2-compat",
        "derafu/certificate": "dev-php-8.2-compat",
        "derafu/signature": "dev-php-8.2-compat"
    },
    "repositories": [
        { "type": "path", "url": "C:/Users/baezs/OneDrive/Escritorio/forks-derafu/xml",         "options": { "symlink": false } },
        { "type": "path", "url": "C:/Users/baezs/OneDrive/Escritorio/forks-derafu/certificate", "options": { "symlink": false } },
        { "type": "path", "url": "C:/Users/baezs/OneDrive/Escritorio/forks-derafu/signature",   "options": { "symlink": false } }
    ]
}
```

2. Ejecutar `composer update derafu/xml derafu/certificate derafu/signature -W` para instalar.

3. Verificar que los namespaces cargan: `php -r "require 'vendor/autoload.php'; new Derafu\Xml\XmlDocument();"`

**Checkpoint:** `composer show derafu/*` lista los 3 paquetes; no hay errores de PHP en la carga.

### Etapa 2 — Portar capa de firma + XML (núcleo)

**Mi tarea, en orden:**

1. **Portar `CertificateManager.php`** (31 ln) — wrapper Derafu, sin lógica.

2. **Portar `EmisorCertificateService.php` → `CertificateService.php`**.
   Adaptaciones:
   - `Emisor` → `ParEmpresa`
   - Leer PFX desde disco (`storage/app/dte/.../certificado.pfx`) en vez de BD
   - Password desencriptado desde `dte_certificados.password_pfx`

3. **Portar `CafManager.php` → `CafService.php`**.
   Adaptaciones:
   - `CafFile` → `DteCaf` (queries `where par_empresa_id` en vez de `emisor_id`)
   - `CafFolioUsage` → `DteFolioUsage`
   - Estados: mapear nuestros estados a los de DTEengine (`reservado` ≈, `emitido` ≈, `aceptado` → `aceptado_sii`, etc.)

4. **Portar `TedBuilder.php`** (168 ln) — usa Derafu para parsear CAF a array y construir DD compacto con `serializarElemento`.

5. **Portar `XmlDteBuilder.php` → `DteXmlBuilder.php`** (194 ln) — usa `Derafu\Xml\Service\XmlEncoder` para construir XML desde array.

6. **Portar `EnvelopeService.php`** (292 ln) — firma DTE individual + construye envelope. Usa `Derafu\Signature\Service\SignatureService`.

7. **Portar `DataMapper.php`** (197 ln) — mapeo data POS → estructura SII compatible con XmlEncoder.

**Checkpoint:** generar 1 DTE de prueba (factura 33 con 1 ítem simple) y verificar:
- Firma TED valida con RSAPK del CAF
- DigestValue del Documento coincide con C14N recalculado
- SignatureValue valida
- Estructura idéntica a fixture DTEengine

### Etapa 3 — Portar transporte SII

1. **Portar `SiiTransportService.php`** (672 ln) → reemplaza `SiiAuthService.php` + `SiiDteClient.php`.
   - Token (CrSeed + GetTokenFromSeed)
   - Envío DTE
   - Consultar estado por trackid
   - Envío Libros (IECVUpload)

**Checkpoint:** llamar `obtenerToken()` y verificar que devuelve token válido.

### Etapa 4 — Portar orquestador

1. **Portar `DteService.php` → `DteEmissionService.php`** (125 ln).
   - Mantener firma de `emitir(DteEmissionRequest): DteEmissionResult`
   - Mantener `emitirBatch(array): DteBatchEmissionResult`
   - Adaptar para usar nuevos services portados

### Etapa 5 — Primera prueba real (usuario confirma)

**Yo aviso ANTES de emitir. Tú confirmas. Yo emito.**

1. Reset BD (solo `dte_documentos`, `dte_envios`, `dte_logs`; mantener `dte_folio_usages`).
2. Marcar folios consumidos del set anterior como `aceptado_sii`.
3. Actualizar JSON del set con próximos folios (33→29+, 61→22+, 56→8+).
4. **Emisión de 1 sola factura simple** (no el set completo): tipo 33, folio nuevo, 1 ítem afecto sin descuento, receptor real, referencia al SET.

**Resultado esperado:** EPR + aceptado (no RPR con reparos, ni RSC, ni RCH).

### Etapa 6 — Set básico completo

Si Etapa 5 pasa: emitir set básico completo (8 DTEs) con cambios mínimos en `eventsud_set_basico.json` (folios y fechas actualizadas).

### Etapa 7 — Libros

1. Portar `LibroBuilder.php` y `LibroRespuestaParser.php`.
2. Generar libros de Ventas/Compras/Guías del período 2026-05.
3. Enviar a IECVUpload.

### Etapa 8 — Sets restantes (boletas, guías, exenta)

1. Set boletas (5 boletas tipo 39, host `rahue.sii.cl`).
2. Set guías (3 guías tipo 52, con `IndTraslado`/`TipoDespacho`).
3. Set factura exenta (8 casos tipo 34/61/56).

## Checkpoints y validaciones

| Checkpoint | Cómo validar |
|---|---|
| Composer instala derafu/* | `composer show derafu/xml derafu/certificate derafu/signature` |
| Carga de namespaces | `new Derafu\Xml\XmlDocument()` en tinker |
| Cert load | `app(CertificateService::class)->load(1)` retorna llave/cert/metadata |
| CAF parseo | `app(CafService::class)->parsearCaf(DteCaf::first())` retorna array con RSASK |
| TED firma local | `openssl_verify` con RSAPK del CAF retorna 1 |
| Documento DigestValue | C14N recalculado coincide con `<DigestValue>` |
| SetDTE SignatureValue | xmlseclibs `validateReference` + `verify` retornan true |
| Token SII | Cookie TOKEN obtenido con respuesta `<TOKEN>` válido |
| TrackID SII | Respuesta XML `<STATUS>0</STATUS><TRACKID>...</TRACKID>` |
| Estado envío SII | `EPR` (envío procesado) |
| Estado DTE individual | `OK` o `aceptado` (NO `RPR` con reparos TED-2-510) |

## Rollback plan

Si al final de Etapa 4 (antes de la primera emisión real) la implementación local no valida firmas correctamente:

1. `git stash` los cambios en `app/Services/FacturacionElectronica/`.
2. `composer remove derafu/xml derafu/certificate derafu/signature`.
3. Volver al estado del último commit.
4. Considerar opciones B (vendor-in-tree) o C (upgrade Laravel).

Si la primera emisión (Etapa 5) sale con reparos pero el SII acepta:
- Continuar con set completo y resto del flujo.
- Reparos `TED-2-510` se evalúan si bloquean certificación o no.

## Estado actual de folios (importante)

Folios consumidos en el SII de EVENTSUD (registrados por envíos RPR previos):

| Tipo | Folios consumidos | Próximo libre |
|---|---|---|
| 33 (factura) | 1-4, 5-8, 9-12, 17-20, 21-24, 25-28 | **29** |
| 56 (ND) | 1, 2, 3, 4, 5, 6, 7 | **8** |
| 61 (NC) | 1-3, 4-6, 13-15, 16-18, 19-21 | **22** |

CAFs disponibles: 100 folios cada uno (rango 1-100). Tenemos margen pero no es infinito.

## Decisiones confirmadas por el usuario

| Pregunta | Respuesta |
|---|---|
| Branch en forks | `php-8.2-compat` |
| Aprobaciones SII | Solo intervenir si hay problema repetitivo y dar diagnóstico |
| Rama git | `feature/facturacion-electronica-interna` (la actual, sin crear branch nueva) |
| Modelos | Adaptar services a modelos existentes (`ParEmpresa`, `DteCaf`, etc.) — no portar los modelos DTEengine |
| Forks | Locales en `C:\Users\baezs\OneDrive\Escritorio\forks-derafu\` — montados via `composer.json` con `"type": "path"` |
| Sesiones | Una sola sesión Claude Code secuencial |

## Reglas de operación para Claude Code

1. **NUNCA emitir un DTE al SII sin avisar primero**. Antes de cualquier `dte:emitir-set-pruebas` o llamada a `SiiTransportService::enviar`, mostrar al usuario lo que va a hacer y esperar luz verde.

2. **Las verificaciones LOCALES (sin red) sí son libres**:
   - Generar XMLs en disco
   - Verificar firmas locales con RSAPK del CAF
   - Comparar bytes contra fixtures `DTEengine/fuente-verdad/`
   - Tests unitarios

3. **Si hay error TED-2-510 (o cualquier reparo) repetitivo en 2 emisiones consecutivas**: PAUSA. Pedir al usuario el correo del SII y dar diagnóstico ANTES de re-emitir.

4. **Folios**: respetar la lógica de `CafService::confirmarAceptadoSii` para folios consumidos. Los folios anteriores a 29/33, 8/56, 22/61 están ya en el SII.

5. **Cert vigente**: `Mia2010Z`, vence 2029-05-25, RUT firmante `16658678-K`, archivo `certificado.pfx`.

6. **Rama git**: trabajar siempre en `feature/facturacion-electronica-interna`. Hacer commits granulares por etapa.

7. **Si surge ambigüedad** sobre cómo adaptar algo de DTEengine a Laravel 9 o a nuestros modelos: PREGUNTAR al usuario, no decidir unilateralmente.

## Tiempo estimado

| Etapa | Estimación |
|---|---|
| 0. Forks (tú) | 10 min |
| 1. Composer (yo) | 15 min |
| 2. Capa firma + XML (yo) | 1-2 h |
| 3. Transporte SII (yo) | 30-60 min |
| 4. Orquestador (yo) | 30 min |
| 5. Primera emisión + diagnóstico | 15-30 min |
| 6. Set básico completo | 30 min |
| 7. Libros | 1 h |
| 8. Sets restantes | 1-2 h |
| **Total** | **5-8 horas de trabajo** |

## Criterio de éxito

✅ **Set básico SII**: 8 DTEs en estado `OK` o `aceptado` (sin reparos TED-2-510).
✅ **Set factura exenta**: 8 DTEs aceptados.
✅ **Set guías**: 3 DTEs aceptados.
✅ **Set boletas**: 5 DTEs aceptados en `rahue.sii.cl`.
✅ **Libros ventas/compras/guías**: aceptados en IECVUpload (estado `LOK`).
✅ **Correo SII**: confirmación de habilitación RUT `78073481-7` para producción.

---

**Próximo paso del usuario**: forkear los 3 paquetes y dar las URLs. Yo arranco desde ahí.
