# Sprint 4 · Dashboard, Caja, Inventario y REP automático

**ZIP:** `facturaos_sprint4.zip`
**Archivos nuevos:** 12
**Acumulado total:** 55 archivos PHP + vistas + migraciones

---

## Qué resuelve este sprint

Las operaciones del día a día: el gerente ve el estado del negocio en el dashboard, el cajero abre y cierra su turno, el bodeguero controla el stock y hace traslados, y el sistema genera automáticamente el REP cuando se paga una factura a crédito con IVA diferido.

Al terminar este sprint el sistema puede:
- Mostrar KPIs del día, gráfico de ventas 30 días y alertas críticas
- Detectar automáticamente si Hacienda está operativo
- Abrir y cerrar turnos de caja con arqueo y registro de diferencias
- Registrar retiros y depósitos durante el turno
- Ver stock por bodega con filtro de alertas de mínimo
- Hacer ajustes manuales de inventario con trazabilidad completa
- Crear y recibir traslados entre bodegas
- Generar el REP (tipo 99) automáticamente al liquidar una CxC con IVA diferido
- Generar el cierre fiscal del día vía command programado

---

## Archivos del sprint

### `app/Livewire/Dashboard/DashboardIndex.php`

**Qué hace:** Panel principal con KPIs, gráfico de barras y estado del sistema.

**Datos cargados:**

`kpisHoy()` — cacheado 5 minutos:
- Ventas del día (cantidad + monto + IVA)
- Contingencias pendientes de envío
- Facturas rechazadas hoy
- CxC vencidas (monto total)
- CxC por vencer en 7 días
- Alertas de stock bajo mínimo

`graficoVentas30Dias()` — cacheado 5 minutos:
- Array de 30 días con fecha, label, monto y cantidad
- Días sin ventas incluidos con monto = 0

`estadoHacienda()` — cacheado 2 minutos:
- Llama a `HaciendaConnector::verificarConectividad()`
- Si no hay certificado configurado → offline

**Vista del gráfico:**
Barras CSS puras (sin librería JS). Cada barra tiene `height` proporcional al máximo del período. El día de hoy se resalta con `var(--blue)`. Tooltip en `title=""` para accesibilidad.

**Alertas críticas en el header:**
- Si `certificado_vence_pronto` → banner naranja con link a Configuración
- Si `contingencias > 0` → banner rojo con link filtrado a Documentos

**Conecta con:**
- `HaciendaConnector.php` — ping de conectividad
- `FacturaVenta` (modelo) — KPIs y gráfico
- `CuentaPorCobrar` (modelo) — CxC vencidas
- `Stock` (modelo) — alertas de stock

---

### `app/Livewire/Caja/CajaIndex.php`

**Qué hace:** Módulo de caja con tres estados: sin turno / turno abierto / cierre.

**Flujo de apertura `abrirTurno()`:**
1. Valida que se seleccionó una caja
2. Verifica que no hay otro turno abierto en esa caja
3. Crea `TurnoCaja` con `monto_apertura` y `estado = 'abierto'`

**Registro de movimientos `guardarMovimiento()`:**
- Tipo: retiro (salida de efectivo) o depósito (entrada)
- Validación: monto > 0 y motivo mínimo 5 caracteres
- Crea `MovimientoCaja` con referencia al turno

**Cierre `cerrarTurno()`:**
Llama a `calcularResumen()` que consulta directamente en BD:
- Ventas por medio de pago (MySQL `JSON_CONTAINS` sobre `medio_pago`)
- Notas de crédito del período
- Movimientos del turno (retiros/depósitos)
- Calcula `efectivo_esperado = apertura + ventas_efectivo + depositos - retiros`

Guarda en `turnos_caja`: esperado, contado, diferencia, totales por medio.

**Modelo de caja:**
La diferencia entre efectivo esperado y contado puede ser positiva (sobrante) o negativa (faltante). Ambas quedan registradas en el cierre.

**Connecta con:**
- `Caja`, `TurnoCaja`, `MovimientoCaja` (modelos)
- `FacturaVenta` (BD directa para calcular resumen)

---

### `app/Livewire/Inventario/StockIndex.php`

**Qué hace:** Vista del inventario por bodega con ajustes manuales trazables.

**Selector de bodega:**
Pestañas superiores. Al cambiar bodega se recarga el query sin perder los filtros.

**KPIs por bodega:**
- Total de productos activos
- Productos bajo mínimo (con alerta visual si > 0)
- Valor total del inventario (cantidad × costo promedio)

**Ajuste manual `guardarAjuste()`:**
1. Valida cantidad ≥ 0 y motivo ≥ 5 chars
2. Calcula el valor de la diferencia: `|cantidad_nueva - stock_actual| × costo_promedio`
3. Si supera `empresa.umbral_ajuste_inv` y el usuario requiere aprobación → bloquea con mensaje
4. Si pasa → llama a `StockService::ajuste()` que registra el movimiento

**Nota sobre umbral de ajuste:**
El umbral se configura en la tabla `empresas.umbral_ajuste_inv`. Si es 0, no hay límite. Permite que el gerente configure qué ajustes necesitan su aprobación.

**Conecta con:**
- `StockService.php` — `ajuste()`
- `Stock`, `Producto`, `Bodega` (modelos)

---

### `app/Livewire/Inventario/TrasladosBodegas.php`

**Qué hace:** Solicitar y recibir traslados entre bodegas.

**Flujo de creación:**
1. Seleccionar bodega origen y destino (no pueden ser iguales)
2. Buscar productos: solo muestra los que tienen stock > 0 en la bodega origen
3. Para cada línea, se muestra el stock disponible en origen
4. Validación: cantidad solicitada ≤ stock disponible
5. Crea `Traslado` + `TrasladoLineas` en `DB::transaction()`

**Recepción `recibirTraslado()`:**
1. Verifica que el traslado esté en estado `solicitado`
2. Para cada línea llama a `StockService::trasladar()`:
   - Salida de bodega origen
   - Entrada en bodega destino
   - Ambas en una transacción
3. Actualiza el traslado a `recibido` con usuario y timestamp

**Wire:confirm:**
La confirmación de recepción usa `wire:confirm` de Livewire 3 para mostrar un diálogo nativo del navegador antes de proceder.

**Connecta con:**
- `StockService.php` — `trasladar()`
- `Traslado`, `TrasladoLinea` (modelos)
- `Stock` (verificación de disponibilidad)

---

### `app/Models/Caja.php`

**Qué contiene:** 5 modelos en un archivo (convenio del proyecto):

| Modelo | Tabla | Notas |
|---|---|---|
| `Caja` | `cajas` | `turnoActivo()` helper |
| `TurnoCaja` | `turnos_caja` | `estaAbierto()`, `duracion()` |
| `MovimientoCaja` | `movimientos_caja` | Retiros y depósitos |
| `Traslado` | `traslados` | `estaPendiente()` |
| `TrasladoLinea` | `traslados_lineas` | Líneas de producto |

---

### `app/Jobs/GenerarRepAutomatico.php`

**Qué hace:** Genera el REP (comprobante tipo 99) cuando se liquida una factura a crédito con IVA diferido.

**Cuándo se genera:**
- La CxC queda en estado `pagado` (saldo = 0)
- La `FacturaVenta` tiene `rep_requerido = true`
- La `FacturaVenta` tiene `rep_generado = false`

**Proceso:**
1. Genera nuevo consecutivo tipo `99`
2. Crea `FacturaVenta` con `tipo_documento = '99'`
3. Copia todas las líneas de la FE original al REP
4. Establece `referencia_clave` apuntando a la FE original
5. Marca la FE original como `rep_generado = true`
6. Encola el REP en `hacienda_cola`
7. Despacha `EnviarFacturaHacienda` para el REP

**Base legal:** Art. 27, Ley IVA N° 9635. El IVA de facturas a crédito se declara al cobrar, no al emitir.

**Conecta con:**
- `ConsecutivoGenerator.php` — consecutivo tipo 99
- `EnviarFacturaHacienda.php` — envío a Hacienda
- `FacturaVenta`, `PagoCxC`, `HaciendaCola` (modelos)

---

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

**Qué hace:** Snapshot fiscal del día. Consolida toda la actividad fiscal para facilitar la D-104.

**Uso:**
```bash
# Todas las empresas, hoy
php artisan facturaos:cierre-dia

# Empresa específica
php artisan facturaos:cierre-dia --empresa=1

# Fecha específica
php artisan facturaos:cierre-dia --fecha=2026-06-01

# Sobrescribir si ya existe
php artisan facturaos:cierre-dia --forzar
```

**Programación recomendada** en `routes/console.php`:
```php
Schedule::command('facturaos:cierre-dia')->dailyAt('23:55');
```

**Datos que consolida en `cierres_dia`:**
- Total de FE aceptadas, rechazadas, NC
- Ventas brutas, descuentos, venta neta
- IVA cobrado desglosado por tarifa (13%, 4%, 4% con/sin devolución, 2%, 1%)
- IVA acreditable de compras del día
- IVA neto a pagar (cobrado − acreditable)
- Medios de pago: efectivo, tarjeta, SINPE, transferencia

**Nota sobre medios de pago:**
La columna `medio_pago` es JSON. El command parsea el array para sumar por medio. Si una factura tiene múltiples medios, se distribuye proporcionalmente.

---

## Flujo REP automático completo

```
Usuario registra pago en CxCIndex
        ↓
PagoCxC creado
CuentaPorCobrar::aplicarPago($monto)
  → monto_pendiente = 0 → estado = 'pagado'
        ↓
CxCIndex verifica: rep_requerido && !rep_generado
        ↓ (sí)
factura.rep_generado = true (optimista)
pago.genera_rep = true
[Sprint 4] GenerarRepAutomatico::dispatch($factura_id, $pago_id)
        ↓ (async, cola)
GenerarRepAutomatico::handle()
  ├── Generar consecutivo tipo 99
  ├── Crear FacturaVenta tipo 99 (REP)
  │   referenciando la FE original
  ├── Copiar líneas de la FE
  ├── Encolar en hacienda_cola
  └── EnviarFacturaHacienda::dispatch($rep_id)
```

---

## Qué viene en el Sprint 5

| Módulo | Archivos |
|---|---|
| Módulo Reportes Ventas | `Livewire/Reportes/ReporteVentas.php` + `Services/Reportes/ReporteVentasService.php` |
| Módulo Reportes Gastos | `Livewire/Reportes/ReporteGastos.php` + `Services/Reportes/ReporteGastosService.php` |
| Módulo Configuración | `Livewire/Configuracion/DatosEmpresa.php` + personalización + SMTP |
| Módulo Perfiles | `Livewire/Perfiles/PerfilesIndex.php` + editor de permisos granular |
| Cierre de día UI | `Livewire/Caja/CierreDia.php` |
