# Sprint 3 · CxC, CxP, Gastos XML y XmlParser

**ZIP:** `facturaos_sprint3.zip`
**Archivos nuevos:** 10
**Acumulado total:** 58 archivos PHP + vistas + migraciones

---

## Qué resuelve este sprint

El ciclo financiero completo: qué nos deben, qué debemos, y qué compramos. Al terminar este sprint el sistema puede:

- Parsear cualquier XML v4.4 recibido de Hacienda
- Procesar automáticamente facturas de compra: proveedor, líneas, IVA acreditable, inventario, CxP
- Sincronizar XMLs desde el API de Hacienda vía command programado
- Subir XMLs manualmente con validación instantánea
- Clasificar gastos por categoría para la D-104
- Ver cuentas por cobrar con antigüedad de saldos y barras de progreso
- Registrar pagos parciales o totales de CxC con generación automática de REP
- Enviar recordatorios de cobro por correo
- Ver cuentas por pagar con alertas de vencimiento a 7 días
- Registrar pagos a proveedores

---

## Archivos del sprint

### `app/Services/Hacienda/XmlParser.php`

**Qué hace:** Parsea el XML v4.4 de cualquier comprobante recibido y extrae todos los campos en un array estructurado.

**Tipos de comprobante soportados:**
| Tag XML | Tipo detectado |
|---|---|
| FacturaElectronica | 01 |
| NotaDebitoElectronica | 02 |
| NotaCreditoElectronica | 03 |
| TiqueteElectronico | 04 |
| FacturaElectronicaCompra | 05 |

**Detección de namespace automática:**
El namespace varía según el tipo de comprobante. El parser detecta el namespace del elemento raíz del XML y lo registra dinámicamente en XPath para garantizar compatibilidad con cualquier tipo, sin depender de un namespace hardcoded.

**Cálculos por tarifa desde líneas:**
El ResumenFactura del XML no siempre desglosa por tarifa. El parser suma desde las líneas de detalle para obtener `base_gravada_4`, `iva_4`, `base_gravada_2`, `iva_2`, etc. con precisión.

**Retorno del método `parsear()`:**
```php
[
  'valido'            => bool,
  'error'             => string|null,
  'clave_hacienda'    => string,   // 50 dígitos
  'consecutivo'       => string,
  'tipo_documento'    => string,
  'fecha_emision'     => string,
  'emisor_tipo_cedula'=> string,
  'emisor_cedula'     => string,
  'emisor_nombre'     => string,
  'receptor_cedula'   => string,
  'condicion_venta'   => string,
  'plazo_credito_dias'=> int,
  'medio_pago'        => string,
  'base_gravada_13'   => float,
  'iva_13'            => float,
  // ... por tarifa
  'total_iva'         => float,
  'total_comprobante' => float,
  'lineas'            => array,
]
```

**Conecta con:**
- `ProcesarXmlCompra.php` — consume el resultado del parser
- `GastosIndex.php` — llama al parser antes de encolar para validación rápida

---

### `app/Jobs/ProcesarXmlCompra.php`

**Qué hace:** Procesa un XML de compra ya parseado y lo registra completamente en la BD.

**Flujo de procesamiento:**
```
XML string
  ↓ XmlParser::parsear()
  ↓ Verificar duplicado por clave_hacienda
  ↓ Verificar aceptación en Hacienda (opcional)
  ↓ DB::transaction() {
      ↓ Upsert Tercero (proveedor)
      ↓ Crear FacturaCompra
      ↓ Crear FacturaCompraLineas
      ↓ Entrada inventario si hay productos físicos
      ↓ Crear CuentaPorPagar si es a crédito
  }
```

**Deduplicación:**
La clave de 50 dígitos es el identificador único. Si ya existe en `facturas_compra` para la empresa, el job termina sin error (idempotente).

**Heurística de inventario:**
Productos físicos se detectan por el primer dígito del código CABYS: 0-3 = bienes, 4+ = servicios. Si el producto existe en el catálogo interno con `tipo = 'producto'`, entra al inventario automáticamente.

**Clasificación automática de gasto:**
Se clasifica por la actividad económica del emisor:
- `68xxxx` → alquiler (actividades inmobiliarias)
- `46xxxx/47xxxx` → mercancía (comercio)
- `86xxxx` → servicio (salud)
- `85xxxx` → servicio (educación)
- Resto → otro

**Conecta con:**
- `XmlParser.php` — parseo del XML
- `StockService.php` — entrada de inventario
- `HaciendaConnector.php` — verificación de aceptación
- `CuentaPorPagar` (modelo) — creación automática
- `Tercero` (modelo) — `updateOrCreate` del proveedor

---

### `app/Console/Commands/SincronizarXmlHacienda.php`

**Qué hace:** Command artisan que descarga XMLs recibidos desde el API de Hacienda y los encola para procesamiento.

**Uso:**
```bash
# Todas las empresas, último día
php artisan hacienda:sincronizar-xml

# Empresa específica
php artisan hacienda:sincronizar-xml --empresa=1

# Rango de días
php artisan hacienda:sincronizar-xml --dias=7
```

**Programación recomendada** en `routes/console.php`:
```php
Schedule::command('hacienda:sincronizar-xml')->dailyAt('06:00');
```

**Proceso por empresa:**
1. Verifica que la empresa tenga certificado configurado
2. Construye el conector con credenciales de la empresa
3. Consulta el endpoint de comprobantes recibidos para el período
4. Por cada comprobante nuevo: descarga el XML y despacha `ProcesarXmlCompra`
5. Comprobantes ya procesados son ignorados (deduplicación por clave)

**Salida del command:**
```
Sincronizando XMLs de 2 empresa(s)...
  → Clínica Vida SA (3101000001)
    ✅ 5 comprobante(s) encolados
  → Farmacia Central (3101000002)
    ✅ 0 comprobante(s) encolados

Resumen: 5 procesados, 0 errores.
```

**Conecta con:**
- `ProcesarXmlCompra.php` — despacha uno por XML
- `HaciendaConnector.php` — para obtener token y descargar XMLs
- `Empresa` (modelo) — itera empresas activas

---

### `app/Livewire/Gastos/GastosIndex.php`

**Qué hace:** Módulo de gestión de gastos con upload manual, sincronización y clasificación.

**Acciones principales:**

`subirXml()`:
1. Valida el archivo (mimes:xml, max:2MB)
2. Llama a `XmlParser` para validación instantánea
3. Verifica que no sea duplicado
4. Si pasa, despacha `ProcesarXmlCompra`
5. Feedback inmediato al usuario con el nombre del emisor

`sincronizar()`:
Ejecuta `Artisan::call('hacienda:sincronizar-xml')` de forma sincrónica para dar feedback en pantalla.

`abrirClasificacion(int $factura_id)`:
Abre el modal con la categoría actual para edición.

`guardarClasificacion()`:
Valida y guarda categoría + subcategoría. La categoría afecta los reportes D-104.

`marcarIvaNoAcreditable(int $factura_id)`:
Mueve `iva_acreditable` a `iva_no_acreditable`. Usado para activos fijos, gastos personales o compras sin relación con la actividad.

**KPIs del período:**
- Total de documentos
- Monto total
- IVA acreditable total (clave para la D-104)
- Documentos con inventario actualizado

---

### `app/Livewire/CxC/CxCIndex.php`

**Qué hace:** Módulo de cuentas por cobrar con vista de listado y antigüedad.

**Vista antigüedad:**
Muestra la distribución del saldo pendiente en 4 rangos con barras de progreso visuales:
- 0-30 días vigente → verde
- 31-60 días → naranja
- 61-90 días → rojo
- +90 días → rojo oscuro

La query usa `DATEDIFF(CURDATE(), fecha_vencimiento)` directamente en MySQL para eficiencia, sin cargar todos los registros en PHP.

**Registro de pago — `registrarPago()`:**

Validaciones:
- Monto > 0
- Monto ≤ saldo pendiente (previene sobre-pago)
- Medio de pago válido

Dentro de `DB::transaction()`:
1. Crea `PagoCxC`
2. Llama a `$cxc->aplicarPago($monto)` que actualiza `monto_pagado`, `monto_pendiente` y `estado`
3. Si la factura tiene `rep_requerido` y el pago liquida el saldo → marca `rep_generado = true` y el pago como generador de REP

**Nota:** La generación efectiva del REP (comprobante tipo 99 enviado a Hacienda) se implementa en Sprint 4 con el Job `GenerarRepAutomatico`.

**Recordatorio de cobro — `enviarRecordatorio()`:**
Redespacha `EnviarCorreoFactura` con la factura original. El cliente recibe el PDF + XML nuevamente como recordatorio.

**Connecta con:**
- `CuentaPorCobrar` (modelo) — `aplicarPago()`
- `PagoCxC` (modelo)
- `EnviarCorreoFactura` (Job) — recordatorios
- `FacturaVenta` (modelo) — acceso a `rep_requerido`

---

### `app/Livewire/CxP/CxPIndex.php`

**Qué hace:** Espejo de `CxCIndex` para cuentas por pagar a proveedores.

**Diferencias respecto a CxC:**
- Medio de pago por defecto: `04` (transferencia, lo más común con proveedores)
- KPI extra: "Vence en 7 días" — alerta crítica para no caer en mora
- No genera REP (solo aplica en ventas)
- No tiene vista de antigüedad (se añade en Sprint 5 si se requiere)

**Colores en la columna de vencimiento:**
- Días negativos (vencida): rojo
- 0-7 días: naranja con ⚠️
- Más de 7 días: gris

**Conecta con:**
- `CuentaPorPagar` (modelo) — `aplicarPago()`
- `PagoCxP` (modelo)
- `FacturaCompra` (modelo) — IVA acreditable visible en la tabla

---

## Flujo completo XML de compra

```
Usuario hace clic en "Sincronizar" o sube un XML
        ↓
GastosIndex::subirXml() / SincronizarXmlHacienda
        ↓
XmlParser::parsear($xml_string)
  ├── Válido: false → mensaje de error al usuario
  └── Válido: true → continúa
        ↓
Verificar duplicado en facturas_compra
  ├── Existe → skip silencioso
  └── No existe → ProcesarXmlCompra::dispatch()
        ↓ (async, cola)
ProcesarXmlCompra::handle()
  ├── Upsert Tercero (proveedor)
  ├── Crear FacturaCompra + FacturaCompraLineas
  ├── Si hay productos físicos + bodega → StockService::entrada()
  └── Si condicion_venta = 02 → Crear CuentaPorPagar
```

---

## Flujo de pago CxC con REP

```
Usuario registra pago en CxC
        ↓
CxCIndex::registrarPago()
  ├── Validar monto y medio
  ├── Crear PagoCxC
  ├── CuentaPorCobrar::aplicarPago($monto)
  │     ├── Actualiza monto_pagado, monto_pendiente
  │     └── Si monto_pendiente = 0 → estado = 'pagado'
  └── Si factura.rep_requerido && saldo = 0
        ├── factura.rep_generado = true
        └── [Sprint 4] GenerarRepAutomatico::dispatch()
```

---

## Qué viene en el Sprint 4

| Módulo | Archivos |
|---|---|
| Dashboard | `Livewire/Dashboard/DashboardIndex.php` + vista |
| Módulo Caja | `Livewire/Caja/CajaIndex.php` + apertura/cierre |
| Módulo Inventario | `Livewire/Inventario/StockIndex.php` + ajustes |
| Módulo Traslados | `Livewire/Inventario/TrasladosBodegas.php` |
| GenerarRepAutomatico | `Jobs/GenerarRepAutomatico.php` |
| GenerarCierreDia | `Console/Commands/GenerarCierreDia.php` |
