# Plan de migracion DTEngine -> api-a-conta

## Documentos hermanos

Este plan se complementa con otros documentos operativos en la misma carpeta:

| Documento | Cuando consultarlo |
|---|---|
| `ARQUITECTURA.md` | Estructura del modulo, flujo de emision, endpoints SII y storage |
| `FLUJO_PRUEBAS.md` | Setup inicial: PFX/CAF, primera emision individual |
| `SET_PRUEBAS_SII.md` | **Proceso completo de certificacion SII** desde api-a-conta-fact (emisiones + libros) |
| `TRAZABILIDAD.md` | Modelo de datos comparado vs DTEngine |
| `COMPARACION_DTENGINE.md` | Estado componente por componente vs DTEngine |
| `ANALISIS_FIRMAS.md` | 11 gaps de firma TED + XML DTE (todos cerrados en commit `694742e`) |
| `ERRORES.md` | Catalogo de errores conocidos con sintoma/causa/solucion |

## 1. Objetivo general

Migrar la logica de facturacion electronica actualmente desarrollada en `DTEngine` hacia `api-a-conta`, eliminando la dependencia de SimpleAPI y dejando un motor interno propio para facturacion real en produccion.

La certificacion se mantendra localmente en `DTEngine`, porque ese flujo ya funciona por comandos y permite generar sets, libros, simulaciones, intercambio y muestras impresas. La facturacion productiva debe quedar expuesta por API dentro de `api-a-conta`, usando como base tecnica lo aprendido en `DTEngine` y tomando `libredte-lib-core` solo como referencia.

Arquitectura objetivo a nivel de `/htdocs`:

```text
C:\xampp\htdocs
├── DTEngine                  # Certificacion local y fuente tecnica de referencia.
├── api-a-conta               # Backend productivo donde vivira la facturacion por API.
├── aconta                    # Frontend/aplicacion cliente que consumira la API.
├── lof_api                   # Otro backend existente, fuera del alcance inicial.
└── PLAN_MIGRACION_DTEngine_ApiAConta.md
```

## 2. Decision tecnica principal

No se recomienda copiar directamente los componentes de `DTEngine` a `api-a-conta`.

Motivos:

- `DTEngine` usa PHP y Laravel mas modernos que `api-a-conta`.
- `DTEngine` depende de Derafu, cuyas versiones actuales requieren PHP moderno.
- `libredte-lib-core` tambien depende fuertemente de Derafu y apunta a PHP moderno.
- LibreDTE tiene licencia AGPL, por lo que copiar codigo directamente puede tener implicancias legales para un producto privado o comercial.
- `api-a-conta` ya tiene una estructura propia de modelos, controladores, rutas, POS, empresas, CAF, certificados y documentos DTE.

La mejor estrategia es construir un motor interno compatible con `api-a-conta`, reutilizando conceptos y reglas de `DTEngine`, pero no sus dependencias modernas.

Decision para firma digital:

- No usar Derafu directamente en `api-a-conta`.
- No importar LibreDTE como dependencia directa.
- Usar `DOMDocument`, `DOMXPath`, `OpenSSL`, `robrichards/xmlseclibs` y `phpseclib/phpseclib`.
- Usar `DTEngine` como referencia operativa.
- Usar `libredte-lib-core` como referencia conceptual y fuente de fixtures, no como codigo copiado.

## 3. Alcance inicial

### Incluido

- Eliminar SimpleAPI de `api-a-conta`.
- Crear motor interno de facturacion electronica.
- Exponer emision, envio, consulta y descarga de DTE por API.
- Integrar la emision interna con el flujo actual de ventas POS.
- Manejar certificados digitales.
- Manejar CAF y folios.
- Firmar TED.
- Firmar XML DTE.
- Firmar y enviar sobre/envio al SII.
- Guardar XML, track ID, estado y logs.
- Preparar despliegue por cliente, considerando que aconta opera con backends y bases de datos separadas por cliente.

### Fuera del alcance inicial

- Migrar todo el flujo de certificacion a `api-a-conta`.
- Desplegar `DTEngine` como dependencia productiva.
- Convertir aconta a multi-tenant logico centralizado.
- Copiar codigo fuente de LibreDTE directamente.
- Resolver libros, intercambio y cesion en la primera etapa productiva.

## 4. Fase 0: Preparacion de rama y diagnostico

Repositorio principal:

```text
C:\xampp\htdocs\api-a-conta
```

Crear rama:

```bash
git checkout -b feature/facturacion-electronica-interna
```

Validaciones iniciales:

- Confirmar version real de PHP en ambiente productivo.
- Confirmar version de Laravel de `api-a-conta`.
- Ejecutar `composer install` si falta `vendor`.
- Ejecutar pruebas existentes si existen.
- Respaldar `.env`.
- Respaldar base de datos.
- Respaldar certificados y CAF existentes.

Archivos a revisar antes de modificar:

```text
api-a-conta\composer.json
api-a-conta\composer.lock
api-a-conta\routes\api.php
api-a-conta\routes\web.php
api-a-conta\config\services.php
api-a-conta\config\database.php
api-a-conta\app\Services\SimpleApiService.php
api-a-conta\app\Http\Controllers\Pos\PosVentaController.php
```

Resultado esperado:

- Rama limpia.
- Dependencias instaladas.
- Estado actual de SimpleAPI documentado.
- Estado actual de tablas DTE documentado.

## 5. Fase 1: Encapsular SimpleAPI antes de reemplazarla

Antes de eliminar SimpleAPI, crear una interfaz interna para desacoplar el controlador POS.

Crear modulo:

```text
api-a-conta\app\Services\FacturacionElectronica
```

Estructura sugerida:

```text
app\Services\FacturacionElectronica
├── Contracts
│   ├── DteEmitterInterface.php
│   ├── CertificateLoaderInterface.php
│   ├── CafRepositoryInterface.php
│   └── SiiClientInterface.php
├── DTO
│   ├── DteEmissionRequest.php
│   ├── DteEmissionResult.php
│   └── SiiSendResult.php
├── Emitters
│   ├── SimpleApiDteEmitter.php
│   └── InternalDteEmitter.php
├── Services
│   ├── DteEmissionService.php
│   ├── DteXmlBuilder.php
│   ├── TedBuilder.php
│   ├── XmlSignatureService.php
│   ├── CertificateService.php
│   ├── CafService.php
│   ├── SiiAuthService.php
│   ├── SiiDteClient.php
│   └── DteStorageService.php
└── Exceptions
    ├── DteEmissionException.php
    ├── CertificateException.php
    ├── CafException.php
    └── SiiException.php
```

Agregar configuracion:

```env
DTE_EMITTER=internal
DTE_SII_ENV=produccion
DTE_STORAGE_DISK=local
DTE_SEND_SYNC=true
```

Durante esta fase:

- `PosVentaController` deja de depender directamente de `SimpleApiService`.
- El controlador llama a `DteEmitterInterface`.
- `SimpleApiDteEmitter` queda solo como adaptador temporal.
- `InternalDteEmitter` se implementa en fases posteriores.

## 6. Fase 2: Normalizar modelos DTE existentes

En `api-a-conta` ya existen conceptos DTE. Antes de construir el motor, hay que ordenar el dominio.

Tablas esperadas:

```text
dte_documentos
dte_detalles
dte_cafs
dte_certificados
dte_envios
dte_logs
```

Revisar y corregir namespaces de modelos:

```text
App\Models\Dte\DteDocumento
App\Models\Dte\DteDetalle
App\Models\Dte\DteCaf
App\Models\Dte\DteCertificado
App\Models\Dte\DteEnvio
App\Models\Dte\DteLog
```

Problemas detectados que deben corregirse:

- Algunos modelos DTE estan dentro de carpeta `Models\Dte`, pero declaran namespace `App\Models`.
- Algunos controladores importan modelos sin el namespace correcto.
- `PosVentaController` intenta usar `\App\Models\Certificado`, pero el modelo real debe normalizarse contra `dte_certificados`.

Decision:

- `dte_certificados` sera la fuente canonica de certificados.
- `dte_cafs` sera la fuente canonica de CAF.
- `dte_documentos` sera la fuente canonica de documentos emitidos.
- `pos_certificados`, si existe, debe quedar como legado o puente temporal, no como fuente principal.

## 7. Fase 3: Manejo de certificados digitales

Crear `CertificateService`.

Responsabilidades:

- Leer certificado PFX/P12 desde almacenamiento seguro.
- Desencriptar con password usando `openssl_pkcs12_read`.
- Obtener llave privada.
- Obtener certificado publico.
- Validar vigencia.
- Obtener metadata del certificado:
  - subject
  - issuer
  - serial
  - fecha desde
  - fecha hasta
  - RUT firmante si esta disponible

Entrada esperada:

```php
[
    'empresa_id' => 1,
    'certificado_id' => 10,
]
```

Salida esperada:

```php
[
    'private_key' => $privateKey,
    'certificate' => $certificate,
    'metadata' => [...],
]
```

Reglas:

- No guardar password del certificado en logs.
- No retornar llave privada por API.
- Registrar errores sin exponer secretos.
- Validar certificado activo antes de emitir.

## 8. Fase 4: Manejo de CAF y folios

Crear `CafService`.

Responsabilidades:

- Leer XML CAF.
- Parsear con `DOMDocument`.
- Extraer nodo `CAF`.
- Extraer `DA`.
- Extraer `RSASK`.
- Extraer rango `D` y `H`.
- Validar RUT emisor.
- Validar tipo DTE.
- Validar que el folio este dentro de rango.
- Reservar folio atomicamente.

Agregar tabla si no existe:

```text
dte_folio_usages
```

Campos minimos:

```text
id
empresa_id
dte_caf_id
tipo_dte
folio
dte_documento_id
estado
created_at
updated_at
```

Estados:

```text
reservado
emitido
fallido
anulado
```

Regla critica:

- La reserva de folio debe ocurrir dentro de transaccion de base de datos.
- No puede haber dos documentos con el mismo `tipo_dte + folio + empresa_id`.
- Si falla antes de generar XML, puede quedar como `fallido`.
- Si el XML se genera y firma, el folio queda consumido.

## 9. Fase 5: Construccion y firma del TED

Crear `TedBuilder`.

Fuente tecnica:

```text
DTEngine\app\Services\SII\TedBuilder.php
libredte-lib-core\src\Package\Billing\Component\Document
```

Implementacion recomendada:

- Construir nodo `DD`.
- Incluir datos del emisor, receptor, folio, fecha, monto y primer item.
- Insertar datos CAF.
- Canonicalizar segun espera SII.
- Firmar `DD` con llave `RSASK` del CAF.
- Usar SHA1withRSA.
- Usar `phpseclib/phpseclib:^3.0` para evitar problemas con llaves antiguas y OpenSSL moderno.

Dependencia nueva recomendada:

```bash
composer require phpseclib/phpseclib:^3.0
```

Salida esperada:

```xml
<TED version="1.0">
    <DD>...</DD>
    <FRMT algoritmo="SHA1withRSA">...</FRMT>
</TED>
```

Pruebas obligatorias:

- CAF valido.
- CAF con folio fuera de rango.
- CAF de otro tipo DTE.
- Firma TED verificable.
- Encoding estable.

## 10. Fase 6: Construccion XML DTE

Crear `DteXmlBuilder`.

Responsabilidades:

- Construir documento DTE por tipo.
- Generar nodo `Documento`.
- Asignar ID estable.
- Insertar encabezado.
- Insertar receptor.
- Insertar detalle.
- Insertar descuentos/recargos si aplica.
- Insertar referencias si aplica.
- Insertar TED.
- Insertar `TmstFirma`.

Tipos iniciales:

```text
33: Factura electronica
34: Factura exenta electronica
39: Boleta electronica
41: Boleta exenta electronica
```

Tipos segunda etapa:

```text
52: Guia de despacho
56: Nota de debito
61: Nota de credito
```

Reglas:

- No mezclar construccion XML con envio SII.
- No mezclar reserva de folio con render XML.
- No depender del controlador POS para datos tributarios.
- Centralizar el mapping de venta POS a DTE.

Crear mapper:

```text
app\Services\FacturacionElectronica\Mappers\PosVentaToDteMapper.php
```

## 11. Fase 7: Firma XML DTE y envio

Crear `XmlSignatureService`.

Responsabilidades:

- Firmar el nodo `Documento`.
- Firmar el sobre o `SetDTE`.
- Preservar canonicalizacion.
- Preservar encoding esperado por SII.
- Validar que el XML firmado tenga referencias correctas.

Estrategia:

- Intentar primero con `robrichards/xmlseclibs`, si ya esta disponible en `api-a-conta`.
- Si la canonicalizacion no coincide o SII rechaza el XML, implementar fallback manual con:
  - `DOMDocument::C14N`
  - `openssl_sign`
  - algoritmo RSA-SHA1 donde corresponda

Fuente tecnica:

```text
DTEngine\app\Services\SII\EnvelopeService.php
DTEngine\app\Services\SII\SiiTransportService.php
```

Crear `SiiAuthService`.

Responsabilidades:

- Solicitar semilla.
- Firmar semilla.
- Obtener token.
- Cachear token mientras sea valido.

Crear `SiiDteClient`.

Responsabilidades:

- Enviar XML firmado.
- Guardar respuesta.
- Extraer track ID.
- Consultar estado de envio.
- Registrar errores SII.

## 12. Fase 8: API productiva en api-a-conta

Crear rutas bajo:

```text
api-a-conta\routes\api.php
```

Endpoints propuestos:

```text
POST   /api/dte/emitir
POST   /api/dte/{id}/enviar
GET    /api/dte/{id}
GET    /api/dte/{id}/xml
GET    /api/dte/{id}/estado
POST   /api/dte/{id}/consultar-sii
POST   /api/dte/{id}/reenviar
```

Payload minimo para `POST /api/dte/emitir`:

```json
{
  "empresa_id": 1,
  "tipo_dte": 33,
  "receptor": {},
  "items": [],
  "referencias": [],
  "forma_pago": "contado",
  "enviar_sii": true
}
```

Respuesta esperada:

```json
{
  "ok": true,
  "dte_documento_id": 123,
  "tipo_dte": 33,
  "folio": 1001,
  "estado": "enviado",
  "track_id": "123456789",
  "xml_url": "/api/dte/123/xml"
}
```

Estados internos:

```text
borrador
folio_reservado
xml_generado
firmado
enviado
aceptado
rechazado
fallido
```

Controladores sugeridos:

```text
app\Http\Controllers\Api\Dte\DteEmissionController.php
app\Http\Controllers\Api\Dte\DteStatusController.php
app\Http\Controllers\Api\Dte\DteXmlController.php
```

### Endpoints SII reales (referencia)

Esta API expone endpoints internos. Los endpoints externos del SII a los que apunta el motor son:

| Operacion | Certificacion | Produccion |
|---|---|---|
| Envio DTEs/Guias/Notas | `https://maullin.sii.cl/cgi_dte/UPL/DTEUpload` | `https://palena.sii.cl/cgi_dte/UPL/DTEUpload` |
| Envio Libros (IECV) | `https://maullin.sii.cl/cgi_dte/UPL/IECVUpload` | `https://palena.sii.cl/cgi_dte/UPL/IECVUpload` |
| Semilla / Token / Estado (SOAP) | `https://maullin.sii.cl/DTEWS/*.jws` | `https://palena.sii.cl/DTEWS/*.jws` |

**Importante:**
- DTEs y libros usan endpoints distintos. El `SiiDteClient` actual solo conoce `DTEUpload`. El envio de libros requiere `SiiLibroClient` aparte — ver Fase 14.
- Guias (tipo 52) van por `DTEUpload`, NO por `IECVUpload`. Solo los libros de guias (LibroGuia) usan IECVUpload.
- Token SII expira en 90 segundos reales (no 5 minutos). Ajustar cache.

## 13. Fase 9: Integracion con POS — Diseño detallado

### Archivo principal

```text
api-a-conta\app\Http\Controllers\Pos\PosVentaController.php
```

### Flujo completo POS → DTE

```
[Frontend aconta]
        │
        │  POST /api/posVentas  (carrito + receptor + tipo_doc + empresa)
        ▼
[PosVentaController::store]
        │
        ├── 1. Iniciar DB::transaction
        ├── 2. Crear PosVenta (cabecera)
        ├── 3. Procesar carrito: PosVentaDetalle + PosInventario + stock
        ├── 4. Registrar pagos: PosCajaMovimiento
        │
        ├── 5. ¿empresa->facturacion == 1?
        │       └── NO → commit + respuesta sin DTE
        │       └── SI → continuar
        │
        ├── 6. Resolver tipo DTE desde ConDocTributario
        ├── 7. Validar receptor (RUT obligatorio en facturas 33/34/52)
        │
        ├── 8. PosVentaToDteMapper::map(posData) → DteEmissionRequest
        │
        ├── 9. DteEmitterInterface::emitir(request) → DteEmissionResult
        │       │
        │       └── [DTE_EMITTER=simpleapi]  SimpleApiDteEmitter
        │       └── [DTE_EMITTER=internal]   InternalDteEmitter
        │                   │
        │                   ├── CertificateService::load
        │                   ├── CafService::reservarFolio (transaccion)
        │                   ├── TedBuilder::build
        │                   ├── DteXmlBuilder::build
        │                   ├── XmlSignatureService::firmarDocumento
        │                   ├── DteStorageService::guardarXml
        │                   └── SiiDteClient::enviar (si DTE_SEND_SYNC=true)
        │
        ├── 10. Si DteEmissionResult::ok == false:
        │        └── Decidir segun modo:
        │             - DTE_FAIL_MODE=abort  → rollback venta completa
        │             - DTE_FAIL_MODE=warn   → commit venta, DTE queda fallido
        │
        ├── 11. posVenta->update(['track_id' => $result->trackId])
        ├── 12. Relacionar pos_venta_id → dte_documento_id (campo o tabla pivot)
        └── 13. DB::commit + respuesta al frontend
```

### Datos que POS debe proveer al mapper

El frontend ya envia estos datos en el payload actual de `/posVentas`:

| Campo POS | Campo DTE | Notas |
|---|---|---|
| `par_empresa_id` | `empresaId` | Identifica cert y CAF |
| `con_doc_tributario_id` | `tipo_dte` | Resuelto via ConDocTributario.codigo |
| `pos_cliente_id` | `receptor.*` | Resuelto via PosCliente |
| `carrito[].articulo.nombre` | `items[].nombre` | Directo |
| `carrito[].cantidad` | `items[].cantidad` | Directo |
| `carrito[].unitario` | `items[].precio` | Precio neto unitario |
| `carrito[].total` | `items[].total` | Monto total del item |
| `carrito[].afecto_exento` | `items[].exento` | 'exento' → true |
| `fecha` | `fecha_emision` | Fecha de la venta |

### Campos que el frontend NO necesita cambiar

El frontend de `aconta` ya envia todos los datos necesarios. El mapper en `PosVentaToDteMapper` convierte el formato POS al formato DTE sin que el frontend sepa de DTE.

### Validaciones previas en PosVentaController antes de llamar al emitter

```php
// 1. Empresa tiene facturacion activa
if ((int)$empresa->facturacion !== 1) { /* saltar DTE */ }

// 2. Tipo DTE valido
$tipoDte = (int) ConDocTributario::findOrFail($request->con_doc_tributario_id)->codigo;
if (!in_array($tipoDte, [33, 34, 39, 41, 52])) {
    throw new \Exception('Tipo DTE no soportado: ' . $tipoDte);
}

// 3. RUT receptor obligatorio en facturas
if (in_array($tipoDte, [33, 34, 52]) && (!$cliente->rut || $cliente->rut === '66666666-6')) {
    throw new \Exception('Factura requiere RUT válido del receptor');
}

// 4. Al menos un item en carrito
if (empty($request->carrito)) {
    throw new \Exception('El carrito no puede estar vacío');
}
```

### Campo adicional requerido en pos_ventas

Para relacionar venta con DTE se necesita el campo `dte_documento_id` en la tabla `pos_ventas`:

```sql
ALTER TABLE pos_ventas ADD COLUMN dte_documento_id BIGINT UNSIGNED NULL;
ALTER TABLE pos_ventas ADD FOREIGN KEY (dte_documento_id) REFERENCES dte_documentos(id) ON DELETE SET NULL;
```

Migration pendiente: `2026_05_13_000002_add_dte_documento_id_to_pos_ventas.php`

### Respuesta extendida del POS al frontend

Campos nuevos que el frontend `aconta` debe poder recibir (opcionales, no rompen UI existente):

```json
{
  "success": true,
  "venta": { "...campos actuales..." },
  "dte": { "...modelo DteDocumento..." },
  "dte_documento_id": 123,
  "tipo_dte": 39,
  "folio": 1001,
  "estado_dte": "enviado",
  "track_id": "123456789"
}
```

Si empresa no tiene facturacion activa:

```json
{
  "success": true,
  "venta": { "...campos actuales..." },
  "message": "Venta guardada (empresa sin facturacion electronica)"
}
```

Si DTE fallo pero venta se guardo (DTE_FAIL_MODE=warn):

```json
{
  "success": true,
  "venta": { "...campos actuales..." },
  "dte_error": "No se pudo emitir DTE: CAF agotado",
  "estado_dte": "fallido"
}
```

### Variable de entorno nueva para modo de fallo DTE

```env
DTE_FAIL_MODE=warn
```

Valores:
- `abort`: si DTE falla, rollback completo (venta no se guarda)
- `warn`: si DTE falla, venta se guarda igual, DTE queda en estado `fallido`

Recomendado `warn` en produccion para no romper ventas por problemas de conectividad SII.

### Consideraciones de concurrencia

- La reserva de folio usa `DB::transaction` + `lockForUpdate` en DteCaf
- Dos ventas POS simultaneas no pueden obtener el mismo folio
- Si la transaccion DTE falla despues de reservar el folio, el folio queda marcado como `fallido` en `dte_folio_usages`
- Los folios fallidos no se reutilizan (SII no acepta folios saltados)

### Estados posibles del DTE desde perspectiva del POS

| Estado `dte_documentos.estado` | Significado para el cajero |
|---|---|
| `folio_reservado` | Procesando (raro verlo) |
| `xml_generado` | Procesando firma |
| `firmado` | Listo pero no enviado al SII todavia |
| `enviado` | Enviado al SII, esperando confirmacion |
| `aceptado` | SII acepto el documento |
| `rechazado` | SII rechazo (revisar en consola DTE) |
| `fallido` | Error tecnico, requiere reemision manual |

### Adaptaciones pendientes en el frontend aconta

1. Mostrar folio y tipo DTE en el comprobante de venta
2. Indicar al cajero si el DTE fue enviado o quedo pendiente
3. Agregar boton "Reenviar DTE" para documentos en estado `fallido` o `firmado`
4. En la vista de detalle de venta, mostrar link para descargar XML
5. En el cierre de caja, mostrar resumen de DTE emitidos vs fallidos

## 14. Fase 10: Mantener DTEngine como certificacion local

`DTEngine` queda como laboratorio local.

Ruta:

```text
C:\xampp\htdocs\DTEngine
```

Uso:

- Generar sets de certificacion.
- Generar guias.
- Generar libros.
- Generar simulaciones.
- Generar intercambio.
- Preparar muestras impresas.
- Probar envios reales de certificacion.
- Mantener XML validos como fuente de verdad.

No usar `DTEngine` en produccion.

Carpeta recomendada en `api-a-conta` para fixtures:

```text
api-a-conta\tests\Fixtures\DteEngine
```

Contenido sugerido:

```text
factura_33_valida.xml
boleta_39_valida.xml
guia_52_valida.xml
nota_credito_61_valida.xml
caf_factura.xml
caf_boleta.xml
ted_valido.xml
respuesta_sii_track_id.json
```

## 15. Fase 11: Uso de LibreDTE local

Ruta:

```text
C:\Users\baezs\OneDrive\Escritorio\libredte-lib-core
```

Uso permitido:

- Leer implementaciones para entender estructura.
- Comparar canonicalizacion.
- Revisar validaciones CAF.
- Revisar fixtures.
- Revisar comportamiento esperado.

Uso no recomendado:

- Agregarlo como dependencia Composer de `api-a-conta`.
- Copiar clases completas.
- Copiar codigo AGPL sin decision legal.

Motivos:

- Requiere PHP moderno.
- Depende de Derafu.
- Puede ser incompatible con Laravel/PHP de `api-a-conta`.
- Tiene implicancias de licencia.

## 16. Fase 12: Despliegue por cliente

Arquitectura asumida de aconta:

- Cada cliente tiene su propio backend.
- Cada cliente tiene su propia base de datos.
- Cada cliente puede tener su propio servidor.
- `api-a-conta` no parece operar como multi-tenant logico centralizado.

Implicancias:

- Las migraciones se ejecutan cliente por cliente.
- Los certificados se cargan cliente por cliente.
- Los CAF se cargan cliente por cliente.
- Los folios se controlan por base de datos de cada cliente.
- Los logs y XML quedan aislados por instalacion.
- El despliegue debe ser repetible.

Checklist por cliente:

```bash
composer install --no-dev --optimize-autoloader
php artisan migrate
php artisan config:cache
php artisan route:cache
php artisan queue:restart
```

Variables esperadas:

```env
DTE_EMITTER=internal
DTE_SII_ENV=produccion
DTE_STORAGE_DISK=local
DTE_SEND_SYNC=true
```

Respaldos obligatorios antes de activar:

- Base de datos.
- CAF.
- Certificados.
- XML emitidos anteriores.
- Configuracion `.env`.

## 17. Fase 14: Modulo de Libros (Ventas, Compras, Guias)

Esta fase se introduce con base en el modelo de `DTEngine` (tabla `libro_intentos`).

### Alcance

- Envio de Libro de Ventas mensual.
- Envio de Libro de Compras mensual.
- Envio de Libro de Guias de Despacho mensual.
- Reenvio de libros con tipo de operacion: TOTAL, PARCIAL, AJUSTE.
- Trazabilidad completa de intentos en tabla `libro_intentos`.

### Componentes a crear

```text
app\Services\FacturacionElectronica\
├── Services\
│   ├── LibroXmlBuilder.php         Construye <LibroCompraVenta> y <LibroGuia>
│   ├── LibroSignatureService.php   Firma del libro con cert de la empresa
│   └── SiiLibroClient.php          POST a IECVUpload (URL distinta a DTEUpload)
└── Mappers\
    └── PeriodoToLibroMapper.php    Agrupa DTEs del periodo en estructura de libro
```

### Tabla `libro_intentos`

Diseño detallado en `TRAZABILIDAD.md` §3.1. Campos clave:

```text
par_empresa_id      Empresa emisora
tipo_libro          VENTA | COMPRA | GUIA
periodo             YYYY-MM
operacion           TOTAL | PARCIAL | AJUSTE
track_id            Identificador SII (no es el mismo que dte_documentos.track_id)
estado              pendiente | firmado | enviado | aceptado | rechazado | completado
xml_libro           XML completo firmado
respuesta_raw       Respuesta cruda del SII (IECVUpload)
identificador_envio Hash que el SII devuelve para confirmar
```

### Endpoint nuevo

```text
POST /api/libros/enviar
```

Payload:

```json
{
  "par_empresa_id": 1,
  "tipo_libro": "VENTA",
  "periodo": "2026-05",
  "operacion": "TOTAL"
}
```

### URLs SII para libros

```text
Certificacion: https://maullin.sii.cl/cgi_dte/UPL/IECVUpload
Produccion:    https://palena.sii.cl/cgi_dte/UPL/IECVUpload
```

Multipart con campo `xml_envio` similar a DTEUpload, pero el XML interior es `<LibroCompraVenta>` o `<LibroGuia>` (no `<EnvioDTE>`).

### Fixtures de referencia

`DTEngine\fuente-verdad\certificacion\` contiene libros aceptados por el SII para validar estructura:

- `libro_compras_set_4805431_*.xml`
- `libro_ventas_set_4805430_*.xml`
- `libro_guias_set_4805433_*.xml`

Usarlos como fuente de verdad para validar el XML generado por `LibroXmlBuilder` antes de enviar al SII real.

### Pre-requisito: cerrar gaps de trazabilidad

Antes de implementar libros, completar las tablas stub:

1. Migrar `dte_envios` con columnas reales (ver `TRAZABILIDAD.md` §2.1)
2. Migrar `dte_logs` con columnas reales (ver `TRAZABILIDAD.md` §2.2)
3. Cifrar `dte_certificados.password_pfx` (ver `ERRORES.md` #16)

Estos pre-requisitos NO bloquean las pruebas de DTE individual (que se pueden ejecutar hoy con la tabla `dte_documentos` actual), pero SI son necesarios antes de operar libros en produccion.

---

## 18. Fase 13: Eliminacion definitiva de SimpleAPI

Condiciones previas:

- Emision interna funcionando.
- Firma TED validada.
- Firma XML validada.
- Envio SII probado.
- Consulta de estado funcionando.
- POS emitiendo correctamente.
- Logs suficientes para soporte.
- Al menos un cliente probado en ambiente controlado.

Eliminar:

```text
app\Services\SimpleApiService.php
config services.simpleapi
SIMPLEAPI_URL
SIMPLEAPI_KEY
inyecciones de SimpleApiService
llamadas directas a generarDte()
```

Luego:

- Buscar `SimpleApi` en todo el repo.
- Buscar `SIMPLEAPI`.
- Buscar `simpleapi`.
- Confirmar que no queden referencias.

## 19. Plan de pruebas

### Unitarias

- Parsear certificado PFX.
- Detectar certificado vencido.
- Parsear CAF.
- Validar rango CAF.
- Reservar folio.
- Evitar doble folio.
- Construir TED.
- Firmar TED.
- Construir XML DTE.
- Firmar XML DTE.
- Firmar envio.

### Integracion

- Emitir factura 33.
- Emitir factura exenta 34.
- Emitir boleta 39.
- Emitir boleta exenta 41.
- Emitir venta POS con facturacion activa.
- Emitir venta POS con facturacion inactiva.
- Reintentar envio fallido.
- Consultar estado SII.
- Descargar XML emitido.

### Regresion

- Confirmar que POS sigue creando ventas.
- Confirmar que ventas sin facturacion no intentan emitir DTE.
- Confirmar que errores SII no rompen la venta si se define modo pendiente.
- Confirmar que no se exponen passwords ni llaves privadas.

### Comparacion con DTEngine

- Comparar XML generado en `api-a-conta` contra fixtures de `DTEngine`.
- Comparar estructura de TED.
- Comparar uso de CAF.
- Comparar firma.
- Comparar envio.

## 20. Orden recomendado de implementacion

1. Crear rama en `api-a-conta`.
2. Instalar dependencias faltantes.
3. Corregir namespaces/modelos DTE.
4. Crear interfaz `DteEmitterInterface`.
5. Encapsular SimpleAPI como adaptador temporal.
6. Crear estructura `FacturacionElectronica`.
7. Implementar `CertificateService`.
8. Implementar `CafService`.
9. Implementar reserva de folios.
10. Implementar `TedBuilder`.
11. Implementar `DteXmlBuilder`.
12. Implementar `XmlSignatureService`.
13. Implementar `SiiAuthService`.
14. Implementar `SiiDteClient`.
15. Crear endpoints `/api/dte`.
16. Integrar con `PosVentaController`.
17. Agregar pruebas.
18. Probar contra fixtures de `DTEngine`.
19. Activar `DTE_EMITTER=internal`.
20. Eliminar SimpleAPI.

## 21. Riesgos principales

### Canonicalizacion XML

Es el riesgo mas alto. SII puede rechazar XML si cambia whitespace, encoding o canonicalizacion.

Mitigacion:

- Usar fixtures validos de DTEngine.
- Probar con xmlseclibs.
- Tener fallback manual con `DOMDocument::C14N` y `openssl_sign`.

### Compatibilidad de firma TED

CAF puede traer llaves antiguas que fallen con OpenSSL moderno.

Mitigacion:

- Usar `phpseclib/phpseclib:^3.0`.
- Replicar enfoque usado en `DTEngine`.

### Folios duplicados

Puede ocurrir con ventas concurrentes.

Mitigacion:

- Reserva transaccional.
- Indice unico por empresa, tipo DTE y folio.
- Tabla `dte_folio_usages`.

### Diferencias entre clientes

Cada cliente puede tener su propia base, servidor y configuracion.

Mitigacion:

- Checklist de despliegue.
- Migraciones idempotentes.
- Validaciones de certificado/CAF por cliente.

### Licencia LibreDTE

Copiar codigo puede traer obligaciones AGPL.

Mitigacion:

- Usar LibreDTE solo como referencia.
- No copiar clases completas.
- Documentar cualquier fragmento si se decide reutilizar.

## 22. Criterios de aceptacion

La migracion se considera lista cuando:

- `api-a-conta` emite DTE sin SimpleAPI.
- POS puede emitir documento tributario real.
- XML firmado queda guardado.
- TED se genera correctamente.
- Sobre/envio se firma correctamente.
- SII entrega track ID.
- Se puede consultar estado.
- Se puede descargar XML desde API.
- No quedan referencias activas a SimpleAPI.
- El flujo funciona por cliente con su propia base y certificado.

## 23. Supuestos

- `api-a-conta` se mantiene como backend productivo principal.
- `DTEngine` se mantiene local para certificacion y pruebas.
- La primera salida productiva prioriza facturas y boletas.
- Guias, notas, libros e intercambio se abordan por fases.
- No se hara upgrade mayor de Laravel como parte de esta migracion.
- No se migrara a arquitectura multi-tenant logica centralizada.
- Cada cliente seguira operando con su backend/base separados.
- Se agregara `phpseclib/phpseclib:^3.0`.
- Se evitara copiar codigo fuente de LibreDTE por licencia y compatibilidad.

