# LoteManager — Contexto del proyecto

> Documento de contexto para agentes y desarrolladores.  
> Generado: 2026-07-10. **Actualizado: 2026-07-23** (edit/delete clientes con regla de saldo pendiente).  
> Índice: [README.md](./README.md) · Ops: [OPERATIONS.md](./OPERATIONS.md)  
> Handoffs: [hardening 2026-07-10](./CHANGELOG_HARDENING_2026-07-10.md) · [edit/delete + redesign 2026-07-21](./CHANGELOG_EDIT_DELETE_REDESIGN_2026-07-21.md) · [edit/delete clientes 2026-07-23](./CHANGELOG_CLIENTS_EDIT_DELETE_2026-07-23.md)

---

## 1. Propósito del sistema

**LoteManager** es un sistema interno de administración para la venta de **lotes/terrenos** (manzana + lote). Gestiona:

- Clientes
- Ventas de lotes (con enganche y saldo pendiente)
- Cobros / pagos a cuenta de ventas
- Gastos operativos
- Reportes de cobranza, financiero, estado de cliente y vendedores

Dominio: **inmobiliario / fraccionamientos** (`block`/`lot`, `payment_day`, cartera vencida).

---

## 2. Stack tecnológico

| Capa | Tecnología | Notas |
|------|------------|--------|
| Backend | **PHP 8.1+** (Docker **PHP 8.3** / FrankenPHP) | **CodeIgniter 4.7** |
| Auth | **CodeIgniter Shield** `^1.2` | Filtro `session` + CSRF global |
| BD | **MySQL** (MySQLi) | Soft deletes |
| Frontend UI | Tema **Paces** (base) + **lote-theme.css** | Bootstrap 5.3, jQuery 3.7, shell light Summit-inspired |
| Tablas | **DataTables 2.3.3** (+ Responsive, Buttons) | Preferir `new DataTable` + `LoteApp.dtDefaults` |
| Build assets | Gulp + Sass (tema); theme producto es CSS estático | `npm run build` solo si se toca SCSS del tema |
| Servidor | FrankenPHP + Caddy | `Dockerfile`, `docker-compose.yml`, red `damp` |
| Tests | PHPUnit 10 | `HealthTest` + `SaleBalanceServiceTest` |

### Dependencias

- Composer: `codeigniter4/framework`, `codeigniter4/shield`
- npm package: `lotemanager` (antes `paces`; assets del template siguen en el repo)

### Infra Docker (resumen)

```yaml
app: lotemanager, CI_ENVIRONMENT=development
DB: damp-db (external network damp), lotemanager_db, root/root
Caddy: root public/, rewrite → index.php
```

BD host local en config: puerto **3307** (sobreescrito por env en Docker).

---

## 3. Estructura del repositorio

```
loteManager/
├── app/
│   ├── Config/           # Routes, Filters, Auth, Security, Database…
│   ├── Controllers/      # Home, ReportController, REST (Client/Sale/Payment/Expense/User)
│   ├── Models/ + Entities/
│   ├── Libraries/        # SaleBalanceService, ReportService, PaymentMethods
│   ├── Helpers/app_helper.php
│   ├── Database/
│   │   ├── Migrations/   # dominio feb-2026 + hardening jul-2026
│   │   └── Seeds/DemoSeeder.php
│   └── Views/
│       ├── app.php                 # layout app real
│       ├── components/             # ★ negocio
│       │   ├── reportes/
│       │   ├── newSale.php, newPayments.php, receipt.php, saleReceipt.php
│       │   ├── sales.php, payments.php, saleDetail.php, …
│       │   └── start.php           # dashboard (sin form venta)
│       ├── modals/                 # editSale, editPayment, editClient
│       ├── partials/               # sidenav, topbar, head-css, toast, …
│       └── [demos tema Paces]      # NO son producto
├── public/js/app-common.js         # CSRF, DT defaults, errores AJAX
├── public/js/config.js             # shell theme defaults (LOTE_V2 light)
├── public/css/lote-theme.css       # tokens + lavado visual producto
├── docs/                           # esta documentación
├── tests/unit/
├── docker-compose.yml, Dockerfile, Caddyfile
└── README.md
```

---

## 4. Arquitectura

### Patrón

- MVC CodeIgniter 4
- Páginas HTML: `Home`, `ReportController`
- API JSON: resource controllers + `ResponseTrait`
- Front: jQuery AJAX + DataTables

### Capas de dominio añadidas (2026-07)

```
Controller → Model / ReportService / SaleBalanceService → MySQL
                ↑
         PaymentMethods (catálogo)
```

### Auth

- Shield: login/logout/register
- Filtro `session` en rutas de negocio
- CSRF global (meta + header AJAX)
- Grupos: `superadmin`, `admin`, `developer`, `user`, `beta`
- Permisos de dominio en `AuthGroups` (`clients.*`, `sales.*`, `payments.*` incl. `payments.edit`, `expenses.*`, `reports.*`)
- Grupo `user`: create/edit operación + reportes; desde 2026-07-23 también `clients.delete` (único `*.delete` de la matriz)
- **Nota 2026-07-21:** `Sale`/`Payment` update+delete y flags de UI usan hoy `inGroup('user')` (no `can()`). Ver handoff edit/delete: operadores con grupo `user` pueden borrar ventas/pagos; admin-only podría quedar fuera. Unificar a `can()` está pendiente.
- Clientes/gastos delete y clientes update: `can('*.delete'/'*.edit') || admin/superadmin` (patrón preferido)
- `created_by` / `updated_by` / `deleted_by` **solo** desde sesión en servidor

---

## 5. Modelo de datos

### Relaciones

```
clients 1──* sales 1──* payments
users ── created_by / responsible
expenses
```

### `sales` (campos clave)

| Campo | Rol |
|-------|-----|
| `amount` | Precio total del lote |
| `front_payment` | Enganche (histórico; también genera payment) |
| `pending` | Saldo denormalizado, **recalculado desde pagos** |
| `block` / `lot` | Manzana / lote |
| `payment_day` | 1 o 15 (proyección de cobro) |
| `order_status` | `Processing` \| `Delivered` |

### Fórmula de pending (fuente de verdad)

```
pending = max(0, amount − SUM(payments.amount WHERE deleted_at IS NULL))
```

Implementación: `App\Libraries\SaleBalanceService`.

- Enganche se inserta como payment → **no** restar `front_payment` otra vez.
- `pending ≈ 0` → `order_status = Delivered`.

### `payment_day`

- Alta: día 8–21 del `order_date` → 15; si no → 1.
- Históricos NULL: cast `?integer` + migración backfill + default en `afterFind`.

### Métodos de pago (DB → UI)

`Cash` → Efectivo · `Transfer` → Transferencia · `Deposit` → Depósito  
→ `PaymentMethods` + `payment_method_label()`.

---

## 6. Rutas de negocio

| Ruta | Handler | Notas |
|------|---------|--------|
| `/` | Home::index | Dashboard + atajos |
| `/documentacion` | Home::documentation | Manual de usuario (FAQ) |
| `/clientes` | Home::clients | DT ajax `/client`; edit modal + delete con regla saldo |
| `/nueva-venta` | Home::newSale | Alta venta |
| `/ventas` | Home::sales | DT ajax `/sale`; edit/delete en UI |
| `/venta/{id}` | Home::saleDetail | Detalle + movimientos + Modificar/Imprimir |
| `/venta/{id}/recibo` | Home::saleReceipt | Estado de cuenta imprimible |
| `/nuevo-pago` | Home::newPayment | Cobro con saldo visible |
| `/pagos` | Home::payments | Historial; edit/delete en UI |
| `/pago/{id}/recibo` | Home::paymentReceipt | Impresión recibo pago |
| `/nuevo-gasto`, `/gastos` | Home | Gastos |
| `/reportes/*` | **ReportController** | cobranza, financiero, clientes, vendedores |
| `/sale/user/{id}` | Sale::byClient | Solo ventas con `pending > 0` |
| `POST /sale/update/{id}` | Sale::update | Edición venta (form POST + CSRF) |
| `POST /payment/update/{id}` | Payment::update | Edición pago (tope saldo) |
| `POST /client/update/{id}` | Client::update | Edición cliente (form POST + CSRF) |
| REST | `/client`, `/sale`, `/payment`, `/expense`, `/user` | JSON + session + CSRF |

---

## 7. Flujos operativos

### Alta venta

1. `/nueva-venta` → autocomplete cliente → `POST /sale`
2. Server: valida, set `created_by`, guarda, asigna `payment_day`
3. Si enganche > 0 → insert payment + `recalculate`
4. Redirect a `/venta/{id}`

### Cobro

1. `/nuevo-pago` → cliente → ventas con saldo
2. UI muestra pending; valida monto ≤ pending
3. `POST /payment` → tope server-side + transacción + recalculate
4. Redirect a recibo `/pago/{id}/recibo`

### Editar venta (2026-07-21)

1. `/ventas` o `/venta/{id}` → modal `editSale`
2. `POST /sale/update/{id}` con campos del form
3. Server: valida enganche ≤ monto, set `updated_by`, recalcula `payment_day` si cambia fecha
4. `SaleBalanceService::recalculate` gobierna `pending` y `order_status`
5. **Nota:** cambiar `front_payment` no reescribe el payment de enganche histórico

### Editar pago (2026-07-21)

1. `/pagos` → modal `editPayment` (venta/cliente readonly)
2. Tope UI/server: `pending_actual + amount_previo`
3. `POST /payment/update/{id}` → transacción + recalculate
4. No se puede reasignar `sale`

### Editar cliente (2026-07-23)

1. `/clientes` → modal `editClient` (form + botón Guardar; ya no auto-guarda por campo)
2. `POST /client/update/{id}` con name/last_name/phone/email
3. Server: `can('clients.edit') || admin/superadmin`, set `updated_by`; email puede vaciarse

### Eliminar venta / pago / cliente

- Pago: soft-delete + recalc venta.
- Venta: soft-delete en cascada de pagos de la venta + venta (transacción).
- Cliente: **bloqueado si tiene ventas con `pending > 0`** (error 400 con conteo); si no, soft-delete + `deleted_by`. Las ventas pagadas del cliente NO se cascadean (historia financiera de reportes); detalle/recibos usan `withDeleted()` para resolver el nombre.

### Reportes cobranza

- Cobros del mes (`?mes=YYYY-MM`)
- Cartera: `pending > 0`, buckets 0-30 / 31-60 / 61-90 / +90, filtro manzana
- Proyección por `payment_day`
- Export Excel/PDF/print (DataTables Buttons)

---

## 8. Frontend de negocio

- Sections CI4: `styles`, `content`, `scripts`
- Layout: jQuery, SweetAlert2, `formatter` MXN, `deleteThing` / `editThing`, `LoteApp`
- DataTables: no filas `colspan` en tbody vacío; usar `emptyTable`
- CSRF: meta en layout + header en AJAX
- Modales de edición: `Views/modals/editSale.php`, `editPayment.php`, `editClient.php`
- Theme producto: `public/css/lote-theme.css` (primary `#236dc9`, light shell)
- Layout defaults: `public/js/config.js` key `__THEME_CONFIG_LOTE_V2__`

---

## 9. Migraciones App (orden lógico)

1. Clients, Sales, Payments, payment_day, Expenses, responsible  
2. **`2026-07-10-174500` BackfillNullPaymentDay**  
3. **`2026-07-10-180000` AddBusinessIndexesAndRecalcPending** (índices + recalc all pending)

Shield/Settings: migraciones del paquete (auth tables).

---

## 10. Madurez

| Área | Estado |
|------|--------|
| CRUD clientes/ventas/pagos/gastos | Operativo (update UI ventas/pagos 2026-07-21; clientes modal+regla saldo 2026-07-23) |
| Integridad pending vs pagos | **Corregida** (servicio + migración + recalc en update/delete) |
| Validaciones server-side | Presentes en Models + tope en Payment::update |
| CSRF | Activo |
| Roles delete | Inconsistente: clients/expenses OK; sales/payments vía `inGroup('user')` |
| Recibo venta | Hecho (`/venta/{id}/recibo`) |
| UI / branding | Theme LoteManager light (lote-theme.css) |
| Tests dominio | Unitarios básicos; **sin tests** de update/delete |
| Vistas demo tema | Siguen en repo (referencia UI) |
| FK created_by → users | Pendiente |

---

## 11. Mejoras — estado

### Post 2026-07-10 (hardening)

| # | Item | Estado |
|---|------|--------|
| 1 | Recalcular pending + transacciones | Hecho |
| 2 | Validaciones server-side | Hecho |
| 3 | Bugs `$e` en fails | Hecho |
| 4 | ReportService | Hecho |
| 5 | ReportController (separar de Home) | Hecho |
| 6–7 | DT defaults + new DataTable | Hecho |
| 8 | Timestamps Models | Hecho |
| 9 | CSRF | Hecho |
| 10 | Permisos delete (clients/expenses) | Hecho |
| 11 | created_by desde sesión | Hecho |
| 12 | Logout + sidenav real | Hecho |
| 13 | `/nueva-venta` | Hecho |
| 14 | Saldo en nuevo pago | Hecho |
| 15 | Export cobranza | Hecho |
| 16 | Filtros cartera | Hecho |
| 17 | Recibo pago | Hecho |
| 18–19 | Sidenav + métodos ES centralizados | Hecho |
| 20–21 | Seeds + índices | Hecho |
| 22 | FK created_by | Pendiente |
| 23 | Tests unitarios | Hecho (básico) / integración pendiente |
| 24–25 | README + forcehttps prod | Hecho |
| 26 | Purgar demos tema | No (documentado) |
| 27–28 | console.log / errores AJAX | Hecho |
| — | Fix payment_day null cast | Hecho |
| — | Fix DataTables colspan cobranza | Hecho |

### Post 2026-07-21 (edit/delete + redesign)

| # | Item | Estado |
|---|------|--------|
| A | Editar venta (UI + POST update) | Hecho |
| B | Eliminar venta con cascada de pagos | Hecho |
| C | Editar pago con tope de saldo | Hecho |
| D | Eliminar pago + recalc | Hecho |
| E | Recibo / estado de cuenta venta | Hecho |
| F | Permiso `payments.edit` en matrix | Hecho |
| G | Theme `lote-theme.css` + shell light v2 | Hecho |
| H | Unificar auth edit/delete a `can()` | **Pendiente** (Client update/delete ya usa `can()`; falta Sale/Payment) |
| I | Sync front_payment ↔ payment enganche | **Pendiente** (documentado) |
| J | Tests PHPUnit update/delete | **Pendiente** |

### Post 2026-07-23 (edit/delete clientes)

| # | Item | Estado |
|---|------|--------|
| K | Editar cliente (modal + POST update) | Hecho |
| L | Eliminar cliente bloqueado por saldo pendiente | Hecho |
| M | `clients.delete` para grupo `user` | Hecho |
| N | `parseRequestInput()` compartido en BaseController | Hecho |
| O | `withDeleted()` en detalle/recibos (cliente borrado) | Hecho |

---

## 12. Glosario

| Término | Significado |
|---------|-------------|
| Manzana / block | Manzana del fraccionamiento |
| Lote / lot | Lote dentro de la manzana |
| Enganche / front_payment | Anticipo al cerrar venta |
| Pending | Saldo por cobrar |
| payment_day | Día esperado de cobro (1 o 15) |
| Cartera vencida | Ventas con pending > 0 |
| Cobranza del mes | SUM payments en YYYY-MM |

---

## 13. Notas para agentes

1. Empezar por `docs/README.md` y el changelog del área (hardening cobros **o** edit/delete/UI 2026-07-21).  
2. Priorizar `components/` + `modals/`, no demos del tema.  
3. Pending solo vía `SaleBalanceService` (también en update/delete de pagos y ventas).  
4. Casts nullable (`?integer`) si la BD puede tener NULL.  
5. DataTables: tbody sin filas colspan falsas.  
6. Mutaciones AJAX con CSRF de `app-common.js`.  
7. Updates de venta/pago/cliente: preferir `POST …/update/{id}` (no confiar en PUT form-urlencoded).  
8. Theme: no meter estilos de producto en SCSS del tema Paces si basta con `lote-theme.css`.  
9. Al tocar permisos, alinear controllers + flags de vista (`canEdit*`) y documentar en OPERATIONS.

---

*Actualizar este archivo cuando cambien dominio, saldos, auth, rutas principales o UI shell.*
