# Operaciones — LoteManager

## Deploy / producción

### 1. Código

```bash
git pull
composer install --no-dev
# assets del tema solo si cambió SCSS/Gulp del template Paces:
# npm ci && npm run build
# El theme de producto (public/css/lote-theme.css) es estático: no requiere build.
```

### 2. Migraciones obligatorias (post-hardening 2026-07-10)

```bash
php spark migrate
# o en Docker:
docker exec -it lotemanager php spark migrate
```

Migraciones de dominio App relevantes:

| Versión | Archivo | Efecto |
|---------|---------|--------|
| 2026-02-07 … | Clients, Sales, Payments, Expenses, payment_day, responsible | Schema base |
| **2026-07-10-174500** | `BackfillNullPaymentDay` | Rellena `sales.payment_day` NULL → 1 o 15 |
| **2026-07-10-180000** | `AddBusinessIndexesAndRecalcPending` | Índices + **recalcula todos los `pending`** |

Verificar:

```bash
php spark migrate:status
```

### 3. No correr en producción

```bash
php spark db:seed DemoSeeder   # solo demos locales
```

### 4. Post-deploy checklist

- [ ] Hard refresh del navegador (CSRF meta + `/js/app-common.js` + `/js/config.js` + `/css/lote-theme.css`)
- [ ] Login Shield funciona
- [ ] Shell light: sidenav/topbar claros, primary azul (config `LOTE_V2`)
- [ ] `/ventas` lista sin error de `payment_day`
- [ ] Crear pago en `/nuevo-pago` reduce `pending` de la venta
- [ ] Editar venta en `/ventas` o detalle → guarda y recalcula pending
- [ ] Editar pago con monto > tope → error de validación
- [ ] Eliminar pago → pending de la venta sube
- [ ] Eliminar venta con pagos → soft-delete en cascada
- [ ] `/venta/{id}/recibo` imprime limpio
- [ ] `/reportes/cobranza` con mes vacío no lanza warning DataTables
- [ ] Usuario de sesión tiene grupo Shield correcto (`user` vs `admin`)
- [ ] **Auth edit/delete ventas-pagos (2026-07-21):** hoy se habilita con `inGroup('user')` (operador puede borrar ventas/pagos). Clients/expenses delete siguen con `can('*.delete')` o admin. Ver handoff 2026-07-21.

---

## Reglas de negocio (operativas)

### Saldo pendiente

```
pending = max(0, sales.amount − SUM(payments.amount WHERE not deleted))
```

- El **enganche** (`front_payment`) se inserta también como fila en `payments` al crear la venta.
- **No** restar `front_payment` aparte (doble conteo).
- Si `pending ≈ 0` → `order_status = Delivered`; si no → `Processing`.
- Servicio: `App\Libraries\SaleBalanceService`.

### Día de pago (`payment_day`)

- Al crear venta: día del mes de `order_date` entre 8 y 21 → **15**; si no → **1**.
- Filas históricas NULL: cast `?integer` + backfill migración + default en `afterFind`.

### Métodos de pago (códigos DB)

| Código | UI ES |
|--------|--------|
| Cash | Efectivo |
| Transfer | Transferencia |
| Deposit | Depósito |

Centralizado en `App\Libraries\PaymentMethods` y helper `payment_method_label()`.

### CSRF

- Filtro global activo.
- Token en meta tags del layout (`csrf_meta()`).
- jQuery envía header `X-CSRF-TOKEN` vía `public/js/app-common.js`.
- `Security::$regenerate = false` (compatible con varios AJAX en la misma página).

### Auth / permisos

- Rutas de negocio: filtro `session`.
- **Clientes / gastos delete:** `can('*.delete')` **o** grupo `admin` / `superadmin`.
- **Ventas / pagos update+delete (2026-07-21):** implementación actual exige `inGroup('user')` en controller y en flags de UI (`canEditSales`, `canEditPayments`).  
  - Grupo `user` **sí puede** editar y eliminar ventas y pagos.  
  - Admin/superadmin **sin** grupo `user` podrían no ver botones ni pasar el check.  
  - Matrix declara `payments.edit` y `sales.edit` para `user`, pero los controllers no usan `can()` todavía.
- Matriz grupo `user`: create/edit operación + reportes; **sin** `*.delete` en matrix (inconsistente con Sale/Payment delete real).
- `created_by` / `updated_by` / `deleted_by` siempre desde sesión en servidor.

### Editar venta vs enganche

- Actualizar `front_payment` en la venta **no** modifica el registro histórico en `payments`.
- Para corregir un enganche mal capturado como cobro: **editar el pago** correspondiente.
- Tras cualquier update/delete de pago o venta: `SaleBalanceService` es la fuente de verdad de `pending`.

### Theme UI

- CSS: `public/css/lote-theme.css` (cargado en `partials/head-css.php`).
- Defaults layout: `public/js/config.js`, storage key `__THEME_CONFIG_LOTE_V2__` (invalida config dark-sidenav antigua).
- Si un usuario “no ve el rediseño”: hard refresh o limpiar sessionStorage del origen.

---

## Troubleshooting

| Síntoma | Causa probable | Acción |
|---------|----------------|--------|
| DataTables: *Requested unknown parameter '1'* | Fila `colspan` en tbody vacío | No meter filas vacías con colspan; usar `language.emptyTable` |
| *Field payment_day is not nullable* | Cast `integer` + NULL en BD | Cast `?integer` + migración backfill |
| POST 403 en AJAX | CSRF | Hard refresh; verificar meta + `app-common.js` |
| Update venta/pago no recibe datos | PUT sin body / form no llega | Usar `POST /sale/update/{id}` o `POST /payment/update/{id}` |
| Monto pago rechazado al editar | Tope = pending + monto previo | Revisar saldo real de la venta |
| Pending no baja al pagar | Código viejo sin `SaleBalanceService` | Deploy + migrar índices/recalc |
| Pending incorrecto histórico | Saldos denormalizados | `php spark migrate` (recalc en 180000) o re-ejecutar lógica |
| No puede borrar **cliente/gasto** | Rol `user` | Esperado; promover a admin o superadmin |
| No puede editar/borrar **venta/pago** | Usuario no está en grupo `user` | Añadir grupo `user` o unificar checks a `can()` (mejora pendiente) |
| Sigue viendo UI oscura / vieja | sessionStorage theme v1 | Hard refresh; key v2 debería reiniciar defaults |
| `/ventas` vacío / error JSON | API `/sale` falla | Revisar logs + cast/nulls en Model |

### Recalcular pendientes manualmente (CLI / código)

```php
(new \App\Libraries\SaleBalanceService())->recalculateAll();
// o por venta:
(new \App\Libraries\SaleBalanceService())->recalculate($saleId);
```

---

## Rutas de negocio (referencia rápida)

| Método | Ruta | Handler |
|--------|------|---------|
| GET | `/` | Home::index |
| GET | `/documentacion` | Home::documentation (manual FAQ) |
| GET | `/clientes` | Home::clients |
| GET | `/nueva-venta` | Home::newSale |
| GET | `/ventas` | Home::sales |
| GET | `/venta/{id}` | Home::saleDetail |
| GET | `/venta/{id}/recibo` | Home::saleReceipt (estado de cuenta) |
| GET | `/nuevo-pago` | Home::newPayment |
| GET | `/pagos` | Home::payments |
| GET | `/pago/{id}/recibo` | Home::paymentReceipt |
| GET | `/nuevo-gasto` | Home::newExpense |
| GET | `/gastos` | Home::expenses |
| GET | `/reportes/cobranza` | ReportController::cobranza |
| GET | `/reportes/financiero` | ReportController::financiero |
| GET | `/reportes/clientes` | ReportController::clientes |
| GET | `/reportes/vendedores` | ReportController::vendedores |
| GET | `/sale/user/{id}` | Sale::byClient (solo con pending > 0) |
| POST | `/sale/update/{id}` | Sale::update |
| POST | `/payment/update/{id}` | Payment::update |
| REST | `/client`, `/sale`, `/payment`, `/expense`, `/user` | Resource controllers |

Auth Shield: `/login`, `/logout`, `/register` (según config).

---

## Infra local

```bash
# Red Docker compartida
docker network create damp

docker compose up -d --build
# app: lotemanager, DB: damp-db / lotemanager_db
```

Ver `docker-compose.yml`, `Dockerfile`, `Caddyfile`.
