# Sprint 1 · Núcleo fiscal, Auth y Nueva factura

**ZIP:** `facturaos_sprint1.zip`
**Archivos:** 30
**Líneas de código:** ~4,200

---

## Qué resuelve este sprint

El núcleo que hace posible emitir una factura electrónica válida en Costa Rica y enviarla a Hacienda. Sin este sprint, nada más funciona.

Al terminar este sprint el sistema puede:
- Autenticar usuarios con rate limiting y auditoría
- Aislar datos entre empresas (multi-tenant)
- Calcular impuestos con exactitud fiscal CR
- Generar y firmar XML v4.4
- Enviar a Hacienda de forma asíncrona
- Descontar inventario al aceptarse
- Crear CxC automáticamente si es a crédito

---

## Archivos del sprint

### `app/Services/Fiscal/TaxCalculator.php`

**Qué hace:** Motor de cálculo tributario central. Toda la aritmética fiscal del sistema pasa por aquí — tanto en el frontend (cálculo en tiempo real mientras el usuario escribe) como en el backend (al guardar y generar el XML).

**Funcionalidades:**
- `calcularLinea(array $linea)` — calcula subtotal, IVA, exoneración y total de una línea
- `calcularComprobante(array $lineas, array $medios_pago)` — totales del comprobante agrupados por tarifa
- `calcularDevolucionIva(float $monto, array $medios, array $montos)` — devolución IVA 4% salud con soporte de pago combinado proporcional
- Maneja todas las tarifas CR: 13%, 4%, 2%, 1%, 0%, exento, no sujeto
- Redondeo con `PHP_ROUND_HALF_UP` para coincidir con la aritmética del XML de Hacienda

**Regla fiscal crítica implementada:**
La devolución del IVA 4% en servicios de salud aplica **solo con tarjeta débito (código 02) y tarjeta crédito (código 03)**. SINPE (05), transferencia (04) y efectivo (01) no aplican (Art. 33, Ley 9635).

**Conecta con:**
- `XmlBuilder.php` — usa los mismos cálculos para generar el ResumenFactura del XML
- `NuevaFactura.php` — llamado en cada keystroke para actualizar totales en tiempo real
- `EnviarFacturaHacienda.php` — valida que los montos coincidan antes del envío

---

### `tests/Unit/Fiscal/TaxCalculatorTest.php`

**Qué hace:** Suite de 20 tests que cubren todos los casos fiscales de CR.

**Tests incluidos:**
- IVA 13%, 4%, 2%, 1%, 0%
- Descuentos por línea con límite máximo
- Exoneración parcial y total
- Devolución IVA 4% con tarjeta débito ✓
- Devolución IVA 4% con tarjeta crédito ✓
- Sin devolución con efectivo ✗
- Sin devolución con SINPE ✗
- Sin devolución con transferencia ✗
- Pago combinado 50/50 tarjeta+efectivo → devolución proporcional
- Pago combinado 75/25 → devolución proporcional
- Consistencia: suma de líneas = total del comprobante
- Decimales sin redondeo raro (3 × 3333.33)

**Ejecutar:**
```bash
php artisan test tests/Unit/Fiscal/TaxCalculatorTest.php
# o con Pest:
./vendor/bin/pest tests/Unit/Fiscal/
```

---

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

**Qué hace:** Genera el número consecutivo del comprobante electrónico con lock atómico a nivel de BD. Nunca dos facturas con el mismo número, incluso si dos usuarios facturan al mismo tiempo.

**Funcionalidades:**
- `generar(int $empresa_id, string $tipo, ...)` — genera el siguiente número con `lockForUpdate()` dentro de una transacción
- `formatear(...)` — formatea el consecutivo de 20 dígitos según estándar Hacienda: `[sucursal 3][terminal 5][tipo 2][número 10]`
- `generarClave(...)` — genera la clave numérica de 50 dígitos: `[país 3][fecha 8][cédula 12][consecutivo 20][situación 1][código seguridad 8]`

**Tipos de documento soportados:**
| Código | Tipo |
|---|---|
| 01 | Factura Electrónica |
| 02 | Nota de Débito |
| 03 | Nota de Crédito |
| 04 | Tiquete Electrónico |
| 05 | Factura de Compra (FEC) |
| 99 | REP |

**Conecta con:**
- `NuevaFactura.php` — llamado dentro de la transacción al crear la factura
- `consecutivos` (tabla BD) — donde se guarda y bloquea el último número
- `EnviarFacturaHacienda.php` — la clave de 50 dígitos generada aquí se envía a Hacienda

---

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

**Qué hace:** Construye el XML v4.4 completo desde el modelo `FacturaVenta`. Cubre todos los tipos de comprobante.

**Secciones del XML generadas:**
- `<Clave>` — 50 dígitos
- `<CodigoActividad>` — desde la empresa
- `<NumeroConsecutivo>` — 20 dígitos
- `<FechaEmision>` — ISO 8601 con zona horaria -06:00
- `<Emisor>` — datos completos de la empresa
- `<Receptor>` — datos del cliente (omitido en tiquetes)
- `<CondicionVenta>` + `<PlazoCredito>`
- `<MedioPago>` — múltiples medios soportados
- `<DetalleServicio>` — líneas con CABYS, IVA, exoneración, medicamento
- `<ResumenFactura>` — totales por tarifa, IVA devuelto, total comprobante
- `<InformacionReferencia>` — para NC/ND/REP
- `<Normativa>` — DGT-R-48-2016
- `<Otros>` — notas del comprobante

**Tipos de comprobante:**
| Código | Tag XML | Namespace |
|---|---|---|
| 01 | FacturaElectronica | .../facturaElectronica |
| 02 | NotaDebitoElectronica | .../notaDebitoElectronica |
| 03 | NotaCreditoElectronica | .../notaCreditoElectronica |
| 04 | TiqueteElectronico | .../tiqueteElectronico |
| 05 | FacturaElectronicaCompra | .../facturaElectronicaCompra |

**Conecta con:**
- `TaxCalculator.php` — usa `calcularLinea()` para cada línea del XML
- `XmlSigner.php` — recibe el XML sin firma y lo devuelve firmado
- `XmlValidator.php` — valida el XML antes de firmarlo
- `EnviarFacturaHacienda.php` — coordina el flujo completo

---

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

**Qué hace:** Firma digitalmente el XML con el certificado `.p12` del contribuyente usando el estándar XMLDSig con SHA-256.

**Proceso de firma:**
1. Lee el `.p12` desde `storage/app/certificados/` (fuera del webroot)
2. Descifra el PIN con `decrypt()` de Laravel (AES-256)
3. Canonicaliza el XML con C14N
4. Calcula el digest SHA-256 del contenido
5. Construye el bloque `<ds:SignedInfo>`
6. Firma con la llave privada usando `openssl_sign()`
7. Extrae el certificado público en base64
8. Inserta el bloque `<ds:Signature>` en el XML
9. Limpia el certificado de memoria

**Seguridad:**
- El PIN nunca se loguea ni aparece en mensajes de error
- El certificado se descifra en memoria, nunca se escribe al disco
- El archivo `.p12` se almacena con nombre UUID aleatorio, nunca con la cédula

**Validación del certificado:**
- `validarCertificado(string $path, string $pin)` — usado en Configuración antes de guardar. Devuelve fecha de vencimiento, nombre del titular y si ya está vencido.

**Conecta con:**
- `Empresa.php` — lee `certificado_path` y `certificado_pin` (cifrado)
- `XmlBuilder.php` — recibe el XML sin firma
- `EnviarFacturaHacienda.php` — el XML firmado se envía a Hacienda

---

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

**Qué hace:** Valida el XML generado antes de firmarlo. Previene rechazos de Hacienda con mensajes de error claros en español.

**Dos modos de validación:**

**Con XSD disponible** (recomendado):
- Valida contra el XSD oficial v4.4 de Hacienda
- Los XSD se descargan de Hacienda y se colocan en `storage/app/xsd/`
- Errores traducidos del inglés técnico al español legible

**Sin XSD** (fallback):
- Validaciones básicas que cubren los errores más comunes de rechazo
- Verifica: Clave (50 dígitos), NumeroConsecutivo (20 dígitos), FechaEmision (formato ISO con -06:00), Emisor, CodigoActividad, CondicionVenta, MedioPago, líneas de detalle (CABYS 13 dígitos, cantidad > 0, precio > 0), ResumenFactura, Normativa

**Descargar XSD de Hacienda:**
```
https://www.hacienda.go.cr/ATV/ComprobanteElectronico/docs/esquemas/
→ storage/app/xsd/FacturaElectronicaV4.4.xsd
→ storage/app/xsd/NotaCreditoElectronicaV4.4.xsd
→ storage/app/xsd/TiqueteElectronicoV4.4.xsd
(etc.)
```

**Conecta con:**
- `EnviarFacturaHacienda.php` — se llama antes de firmar y enviar
- `XmlBuilder.php` — valida el XML que este genera

---

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

**Qué hace:** Capa de abstracción del API de Hacienda CR. Maneja OAuth2, envío de comprobantes y consulta de estado.

**Ambientes:**
| Ambiente | API URL | Token URL |
|---|---|---|
| Producción | api.comprobanteselectronicos.go.cr | idp.../rut |
| Sandbox | api-sandbox.comprobanteselectronicos.go.cr | idp.../rut-stag |

**Métodos:**
- `enviarComprobante(...)` — envía el XML firmado en base64. Retorna: exitoso, estado (recibido/rechazado/contingencia), mensaje, http_code
- `consultarEstado(string $clave, string $cedula)` — consulta si fue aceptado o rechazado
- `claveEstaAceptada(...)` — helper para validar XMLs de compras recibidas
- `verificarConectividad()` — ping al API, usado en el dashboard para el indicador de estado

**Manejo de errores:**
- `ConnectionException` → modo contingencia (Hacienda caída)
- HTTP 400 → rechazo con mensaje traducido al español
- HTTP 404 → comprobante no encontrado
- Token vencido → renovación automática transparente

**Caché del token:**
El `access_token` se cachea y se renueva 60 segundos antes de su vencimiento. El usuario nunca ve errores de token vencido.

**Conecta con:**
- `EnviarFacturaHacienda.php` — usa `enviarComprobante()`
- `ConsultarEstadoHacienda.php` — usa `consultarEstado()`
- `config/hacienda.php` — URLs, timeouts, parámetros
- `Empresa.php` — ambiente (sandbox/produccion) por empresa

---

### `app/Jobs/EnviarFacturaHacienda.php`

**Qué hace:** Job de cola que procesa el envío de una factura a Hacienda de forma asíncrona. El usuario no espera la respuesta de Hacienda.

**Flujo:**
1. Carga `FacturaVenta` con sus relaciones
2. Genera el XML con `XmlBuilder`
3. Valida el XML con `XmlValidator`
4. Firma el XML con `XmlSigner`
5. Envía a Hacienda con `HaciendaConnector`
6. Si recibido → actualiza estado, despacha `ConsultarEstadoHacienda`
7. Si rechazado → marca como rechazado, no reintenta
8. Si contingencia → programa reintento automático

**Reintentos:**
| Intento | Espera |
|---|---|
| 1 | 30 segundos |
| 2 | 2 minutos |
| 3 | 5 minutos |
| 4 | 15 minutos |
| 5 | 30 minutos |

Si se agotan los 5 reintentos → estado `contingencia` con log crítico.

**Conecta con:**
- `ConsultarEstadoHacienda.php` — lo despacha 10 segundos después del envío
- `HaciendaCola` (modelo) — registra el estado de cada intento
- `FacturaVenta` (modelo) — actualiza `estado_hacienda`

---

### `app/Jobs/ConsultarEstadoHacienda.php`

**Qué hace:** Polling del estado de un comprobante después del envío. Hacienda puede tardar segundos o minutos en resolver.

**Flujo post-aceptación (automático):**
1. Actualiza `FacturaVenta.estado_hacienda = 'aceptado'`
2. Descuenta inventario por cada línea de producto físico
3. Crea `CuentaPorCobrar` si la factura es a crédito
4. Despacha `EnviarCorreoFactura` con PDF + XML adjunto

**Polling:** hasta 20 consultas, cada 30 segundos (~10 minutos máximo).

**Conecta con:**
- `StockService.php` — `salida()` post-aceptación
- `CuentaPorCobrar` (modelo) — creación automática
- `EnviarCorreoFactura` (Job) — despacha al aceptarse
- `HaciendaConnector.php` — `consultarEstado()`

---

### `app/Services/Inventario/StockService.php`

**Qué hace:** Gestiona todos los movimientos de inventario. Toda entrada/salida pasa por aquí.

**Métodos:**
- `entrada(...)` — registra entrada y recalcula costo promedio ponderado
- `salida(...)` — registra salida con verificación de stock disponible
- `ajuste(...)` — ajuste manual con diferencia calculada
- `trasladar(...)` — salida de origen + entrada en destino en una transacción
- `disponible(...)` — consulta stock en una bodega
- `haySuficiente(...)` — verificación antes de facturar
- `alertasStock(int $empresa_id)` — productos bajo el mínimo

**Costo promedio ponderado:**
```
nuevo_costo = (stock_actual × costo_actual + cantidad_nueva × costo_nuevo) / total
```

**Garantías:**
- Todas las operaciones son atómicas (transacciones DB)
- Cada movimiento queda registrado en `movimientos_inventario` con referencia al documento origen
- El flag `forzar = true` permite salida sin stock (con alerta), para no bloquear operaciones

**Conecta con:**
- `ConsultarEstadoHacienda.php` — `salida()` se llama post-aceptación
- `ProcesarXmlCompra.php` (Sprint 3) — `entrada()` cuando llega un XML de compra
- `TrasladosBodegas.php` (Sprint 4) — `trasladar()`
- `MovimientoInventario` (modelo) — registra cada movimiento

---

### `app/Models/` (todos los modelos)

**Qué hace:** 30 modelos Eloquent que mapean las 30 tablas del sistema.

**Patrón común en todos:**
- `EmpresaScope` como Global Scope → aislamiento automático multi-tenant
- `$fillable` completo
- `$casts` tipados correctamente (decimal, boolean, json, date)
- Relaciones Eloquent declaradas

**Modelos principales:**
| Modelo | Tabla | Notas clave |
|---|---|---|
| `Empresa` | `empresas` | Parámetros del sistema, personalización, SMTP |
| `Usuario` | `usuarios` | Métodos `puede()` y `requiereAprobacion()` |
| `FacturaVenta` | `facturas_venta` | Scopes: `aceptadas()`, `delDia()`, `delPeriodo()` |
| `FacturaVentaLinea` | `facturas_venta_lineas` | Método `margenBruto()` |
| `CuentaPorCobrar` | `cuentas_por_cobrar` | Método `aplicarPago()`, `rangoAntiguedad()` |
| `Tercero` | `terceros` | Clientes y proveedores en una tabla, `saldoPendiente()` |
| `Producto` | `productos` | `stockEnBodega()`, `estaBajoMinimo()` |
| `Stock` | `stock` | Unique por producto+bodega |

---

### `app/Http/Middleware/`

**`VerificarEmpresa.php`**

Ejecuta en cada request autenticado:
1. Verifica que el usuario tenga empresa activa
2. Verifica que el usuario esté activo
3. Inyecta `empresa_id` en sesión para el `EmpresaScope`
4. Alerta si el certificado digital vence en menos de 30 días

**`VerificarPermiso.php`**

Usado en rutas: `->middleware('permiso:facturacion,crear')`
- Gerencia tiene acceso total automático
- Verifica la tabla `permisos` para el perfil del usuario
- Si requiere aprobación → redirige al flujo de solicitud
- Si es request Livewire/AJAX → devuelve JSON 403
- Segunda capa de seguridad (el frontend oculta, el backend bloquea)

---

### `app/Livewire/Auth/Login.php`

**Qué hace:** Autenticación con protecciones de seguridad.

**Protecciones:**
- Rate limiting: 5 intentos por IP cada 10 minutos
- Mensaje progresivo: muestra intentos restantes
- Log de accesos fallidos en el logger
- Auditoría de login exitoso en tabla `auditoria`
- Regeneración de sesión post-login

**Conecta con:**
- `Auditoria` (modelo) — registra cada login exitoso
- `VerificarEmpresa` (middleware) — actúa en cada request post-login
- `resources/views/livewire/auth/login.blade.php` — vista

---

### `app/Livewire/Facturacion/NuevaFactura.php`

**Qué hace:** Componente Livewire completo para emitir cualquier tipo de comprobante electrónico.

**Flujo del usuario:**
1. Selecciona tipo de documento (FE, NC, Tiquete, Proforma, FEC, REP)
2. Busca el cliente por nombre/cédula con autocompletado
3. Selecciona condición de venta y medios de pago
4. Agrega líneas: busca producto por SKU/nombre/CABYS
5. Los totales se recalculan reactivamente en cada cambio
6. Alertas contextuales: devolución IVA, REP automático
7. Guarda como proforma o firma y envía a Hacienda

**Reactividad:**
Cada cambio en una línea (cantidad, precio, IVA) llama a `TaxCalculator` y actualiza los totales inmediatamente. No hay botón "calcular".

**Conversión de proforma:**
`mount(?int $proforma_id)` → precarga todos los datos de la proforma. El usuario solo confirma y envía.

**Conecta con:**
- `TaxCalculator.php` — cálculo en tiempo real
- `ConsecutivoGenerator.php` — al momento de enviar
- `EnviarFacturaHacienda.php` — se despacha post-guardado
- `HaciendaCola` (modelo) — registra el job pendiente
- `FacturaVenta` + `FacturaVentaLinea` — se crean en transacción

---

### `database/migrations/migrations.php`

**Qué hace:** 14 migraciones en un solo archivo, en el orden correcto de dependencias de FK.

**Orden de ejecución:**
```
1. empresas
2. perfiles
3. usuarios
4. permisos
5. solicitudes_aprobacion
6. cabys + actividades_economicas + exoneraciones_catalogo
7. bodegas + terceros
8. productos + stock + movimientos_inventario + traslados + traslados_lineas
9. consecutivos
10. facturas_venta + facturas_venta_lineas
11. facturas_compra + facturas_compra_lineas
12. cuentas_por_cobrar + pagos_cxc + cuentas_por_pagar + pagos_cxp
13. cajas + turnos_caja + movimientos_caja + cierres_dia
14. hacienda_cola + auditoria
```

> **Nota:** En producción, separar en archivos individuales con `php artisan make:migration`. El archivo unificado es para distribución del sprint.

---

### `database/seeders/DatabaseSeeder.php`

**Qué hace:** Siembra los datos base del sistema.

**Datos creados:**
- 1 empresa demo (Clínica Vida SA) en ambiente sandbox
- 7 perfiles del sistema con permisos granulares pre-configurados
- 2 usuarios de prueba (admin + recepción)
- 2 bodegas (Principal + Farmacia)

**Perfiles y permisos:**
| Perfil | Acceso |
|---|---|
| Gerencia | Total sin restricciones |
| Recepción | Factura, proformas, cobros, caja |
| Laboratorio | Solo proformas |
| Cajero | Cobros y cierre de caja |
| Contabilidad | Reportes y gastos — solo lectura |
| Bodeguero | Inventario y traslados |
| Auditor | Lectura total, sin escritura |

---

### `resources/views/layouts/app.blade.php`

**Qué hace:** Layout principal del sistema con sidebar iOS y topbar.

**Características del diseño:**
- Fondo `#F2F2F7` (iOS System Background)
- Cards blancas con `border-radius: 22px`
- Sidebar con blur (`backdrop-filter: blur(20px)`)
- Indicador de estado Hacienda en tiempo real
- Alerta de certificado próximo a vencer
- Navegación filtrada por permisos (`@can`)
- Badges de alertas (CxC vencidas, contingencias)
- Cerrar sesión con form POST (seguro contra CSRF)

**Sistema de diseño incluido en el CSS:**
- Variables CSS completas (`--blue`, `--green`, `--red`, etc.)
- Clases: `.ios-table`, `.chip`, `.btn-blue`, `.btn-plain`, `.field`, `.alert`
- Tipografía Ubuntu con antialiasing
- Scrollbar personalizado

---

### `routes/web.php`

**Rutas públicas:**
- `GET /login` → `Login::class`
- `POST /logout` → cierra sesión

**Rutas protegidas** (middleware: `auth` + `verificar.empresa`):

| Ruta | Nombre | Middleware adicional |
|---|---|---|
| `/` | dashboard | — |
| `/factura/nueva` | factura.nueva | permiso:facturacion,crear |
| `/documentos` | documentos | — |
| `/gastos` | gastos | permiso:gastos,ver |
| `/cxc` | cxc | permiso:cxc,ver |
| `/cxp` | cxp | permiso:cxp,ver |
| `/cierre` | cierre | permiso:cierre,ver |
| `/inventario` | inventario | permiso:inventario,ver |
| `/traslados` | traslados | permiso:inventario,ver |
| `/reportes/ventas` | reportes.ventas | permiso:reportes,ver |
| `/reportes/gastos` | reportes.gastos | permiso:reportes,ver |
| `/perfiles` | perfiles | permiso:perfiles,ver |
| `/configuracion` | configuracion | permiso:configuracion,ver |

---

## Registrar middleware en Laravel 11

En `bootstrap/app.php`:

```php
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'verificar.empresa' => \App\Http\Middleware\VerificarEmpresa::class,
        'permiso'           => \App\Http\Middleware\VerificarPermiso::class,
    ]);
})
```

---

## Registrar el @can personalizado

En `AppServiceProvider.php`:

```php
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::define('facturacion', function ($user, $accion) {
        return $user->puede('facturacion', $accion);
    });
    // Repetir para cada módulo...
}
```

---

## Qué viene en el Sprint 2

| Módulo | Archivos |
|---|---|
| Módulo Documentos | `Livewire/Documentos/DocumentosIndex.php` + vista |
| Detalle de documento | `Livewire/Documentos/DocumentoDetalle.php` + vista |
| PDF carta | `Services/Documentos/PdfCartaService.php` + plantilla Blade |
| Ticket térmico 80mm | `Services/Documentos/TicketTermicoService.php` + plantilla Blade |
| Correo con adjunto | `Jobs/EnviarCorreoFactura.php` + plantilla HTML |
| Selector de formato | `Livewire/Facturacion/SelectorFormato.php` |
