# Punto de venta (POS) — contexto y pendientes

> Actualizado: **2026-07-22** (post-iteración 1 + ajustes UX).  
> Complementa `ANALISIS_CONTEXTO.md` y la bitácora de `PLAN_ACTUALIZACION.md`.  
> Documento vivo: registrar aquí avances de UX/backend del POS y tachar pendientes.

---

## 1. Qué es y dónde vive

Pantalla unificada tipo **caja / punto de venta** para cobrar o cotizar análisis
clínicos. Reemplazó el flujo multi-página catálogo → carrito → cotización.

```
ANTES:  /catalogo  →  carrito (sesión)  →  /cotizacion  →  POST /venta  →  ticket
AHORA:
  empleado:  /cotizacion  (POS a pantalla completa)  →  POST /venta  →  ticket
  admin:     /dashboard   (atajos + POS embebido)     →  POST /venta  →  ticket
             /cotizacion  (misma UI, menú «Venta»)
  ambos:     /catalogo  redirige a /cotizacion
```

| Rol | Dónde vende | Home |
|-----|-------------|------|
| `empleado` | Menú **Venta** → `/cotizacion` | `/cotizacion` |
| `admin` | **Inicio** `/dashboard` = cards de acceso + POS; menú **Venta** → `/cotizacion` | `/dashboard` |

- UI y JS del POS son **partials compartidos** (`_pos.php` + `_pos_scripts.php`).
- Rutas de venta/carrito/alta-cliente: filtro Shield `group:admin,empleado`.
- Error al procesar venta: admin vuelve a `/dashboard`, empleado a `/cotizacion`.
- Ticket/PDF: layout según rol; botón «Nueva venta» → `/cotizacion`.

---

## 2. Decisiones de producto (cerradas)

| Tema | Decisión |
|------|----------|
| URLs | Empleado: `/cotizacion`. Admin: POS en `/dashboard` **y** en `/cotizacion` |
| Layout UI | Bootstrap `col-lg-8` proceso + `col-lg-4` cobro (sticky en desktop) |
| Superficies | Cards de proceso, carrito, panel cobro y bloque método: **fondo blanco** (`.pos-card-surface`) |
| Tipo de precio en POS | **Eliminado**. Precio siempre columna `General` de `precios`. Sin selector ni `POST /carrito/tipo` ni `session('tipo_cliente')` en el flujo de venta |
| Descuentos / fidelidad | **Más adelante** (POS-11). Hoy queda desc. % por ítem legacy (del análisis) |
| Métodos de pago | Solo UI (Efectivo / Tarjeta / Transferencia); **no se persisten** |
| Cierre | **Proceder a venta** + **Solo cotizar** |
| Cantidad por análisis | Una línea por análisis (`md5(id)`; no N unidades) |
| Alta de paciente | Modal compartido (`_modal` + `_form_fields`); sin tags de nombre suelto |
| Backend de cobro | `VentaService::procesar()` sin bifurcar |
| Carrito | Sesión PHP (no BD) |
| Esquema BD | Sin migraciones nuevas por el POS |
| KPIs / gráficas | Fase 6 SaaS: conviven **con** el POS en `/dashboard` (hoy solo 3 atajos + POS) |

**Nota:** el CRUD de clientes y la tabla `precios` (columnas GeneralD/Especial…)
siguen en admin; solo se desacoplaron del **flujo de cobro** del POS.

---

## 3. Flujo de la pantalla

### Columna izquierda (proceso)

1. **Paciente** — Select2 `GET /cotizacion/cliente?q=`  
   Botón **+** → `#modalCliente` → `POST /clientes/nuevo` (AJAX) → queda seleccionado.
2. **Análisis** — Select2 `GET /catalogo/buscar?q=` (precio `General`)  
   Al elegir: `POST /carrito/agregar` → limpia select → re-render cards.
3. **Carrito (cards)** — nombre, precio, input desc. %, final, quitar  
   - Descuento: `POST /carrito/descuento` (debounce ~280 ms)  
   - Quitar: `POST /carrito/quitar`

### Columna derecha (cobro)

- Previa en vivo: subtotal, descuentos, total.
- Abono / cantidad a pagar (default = total).
- Métodos de pago: toggle visual (no van en el POST).
- Submit → `POST /venta` (`accion=venta|cotizar`) → ticket.

Hiddens del form: `idcliente`, `nombre`, `fnacimiento`, `sexo`, `tel`, `mail`, `monedero`.

---

## 4. Mapa de archivos

| Pieza | Ruta |
|-------|------|
| Partial POS UI | `app/Views/empleado/cotizacion/_pos.php` |
| Partial POS JS | `app/Views/empleado/cotizacion/_pos_scripts.php` |
| Vista ruta `/cotizacion` | `app/Views/empleado/cotizacion/index.php` |
| Dashboard admin + POS | `app/Controllers/Admin/Dashboard.php` + `app/Views/admin/dashboard.php` |
| Controlador POS | `app/Controllers/Empleado/Cotizacion.php` |
| Carrito AJAX | `app/Controllers/Empleado/Carrito.php` (`PRECIO_TIPO = General`) |
| Búsqueda análisis | `app/Controllers/Empleado/Catalogo.php` (`buscar` + redirect index) |
| Cierre / ticket | `app/Controllers/Empleado/Venta.php` |
| Negocio | `app/Services/VentaService.php` (**no bifurcar**) |
| Modal cliente | `app/Views/admin/clientes/_modal.php` + `_form_fields.php` |
| Alta cliente AJAX | `Admin\Cliente::nuevo` — `POST /clientes/nuevo` (admin+empleado) |
| Rutas | `app/Config/Routes.php` |
| Layouts / menú | `layouts/admin.php`, `layouts/empleado.php`, `partials/navmenu.php` |
| Estilos | `public/css/app.css` (bloque «Punto de venta») |
| JS global | `public/js/app.js` (`LB.select2Remoto`, `LB.postForm`, CSRF) |

### Endpoints del POS

| Método | Ruta | Uso |
|--------|------|-----|
| GET | `/dashboard` | Admin: atajos + POS embebido |
| GET | `/cotizacion` | POS a pantalla completa (ambos roles) |
| GET | `/cotizacion/cliente?q=` | Select2 pacientes |
| GET | `/catalogo/buscar?q=` | Select2 análisis + precio `General` |
| GET | `/catalogo` | Redirect → `/cotizacion` |
| GET | `/carrito` | JSON items + totales |
| POST | `/carrito/agregar` | Agregar análisis |
| POST | `/carrito/quitar` | Quitar |
| POST | `/carrito/descuento` | `id` + `descuento` (0–100) |
| POST | `/clientes/nuevo` | Alta rápida (admin y empleado) |
| POST | `/venta` | Cobrar o cotizar |
| GET | `/venta/ticket/{id}` | Ticket |
| GET | `/venta/pdf/{id}` | PDF ticket |

**Eliminado del POS:** `POST /carrito/tipo` (tipo de precio / sesión).

Payload típico del carrito:

```json
{
  "ok": true,
  "csrfHash": "…",
  "items": [{ "id", "nombre", "precio", "descuento", "puntos" }],
  "total": 2,
  "subtotal": 1000.0,
  "descuentoTotal": 50.0,
  "totalPagar": 950.0
}
```

---

## 5. Estado actual

### Hecho

- [x] Pantalla única 8+4 (partials reutilizables)
- [x] POS en `/cotizacion` (empleado home + menú admin «Venta»)
- [x] POS embebido en `/dashboard` del admin (debajo de cards de acceso)
- [x] Select2 paciente + modal alta compartido
- [x] Select2 análisis; precio fijo `General`
- [x] Cards carrito: desc. % legacy + quitar; previa en vivo
- [x] Venta + Solo cotizar vía `VentaService`
- [x] Métodos de pago visuales (no BD)
- [x] Fondo blanco en cards de proceso / carrito / cobro / método
- [x] Sin selector ni backend de tipo de precio en el POS
- [x] `POST /clientes/nuevo` para empleado
- [x] Layout dual admin/empleado en POS y ticket
- [x] Redirect de error de venta según rol
- [x] Documentación y bitácora actualizadas

### Limitaciones conocidas

- Al editar descuento se re-renderizan todas las cards (se pierde focus).
- Sin alta express por tags en Select2 (solo modal o cliente existente).
- Admin sin `idsucursal` en sesión: falla al cobrar (flash); sin selector de sucursal.
- Vista tabular `empleado/catalogo/index.php` muerta (solo redirect).
- Layout admin sin badge de carrito en el menú (contador en la UI del POS).
- KPIs reales / Chart.js aún no están (Fase 6); en dashboard conviven atajos + POS.

### Corregido 2026-07-27 — Precio $0 en carrito con análisis “con precio”

El form de análisis al editar no escribía `precios.General` (solo `analisis.precio`).
El POS solo lee General → $0. Ahora editar/alta sincronizan; hay fallback y migración
de datos. Ver bitácora en `PLAN_ACTUALIZACION.md`.

---

## 6. Pendientes (backlog)

Prioridad: **P0** bloquea uso real · **P1** muy útil · **P2** mejora · **P3** futuro / SaaS.

### P0 — Operación / robustez

| ID | Pendiente | Notas |
|----|-----------|--------|
| POS-01 | **Usuario sin sucursal al vender** | Forzar `idsucursal` en admins que venden o selector en el POS. |
| POS-02 | **Smoke E2E / tests** | Empleado + admin: paciente, modal, carrito, cotizar, venta, ticket. Tests carrito si hay tiempo. |
| POS-03 | **Validación abono en cliente** | Clamp si abono > total; toasts ya cubren carrito vacío / sin paciente. |

### P1 — Producto / UX

| ID | Pendiente | Notas |
|----|-----------|--------|
| POS-10 | ~~**Persistir método de pago**~~ | **Hecho 2026-07-27:** `ventas.metodo_pago/recibido/cambio` + `abonar.metodo_pago`; ticket y PDF. Corte de caja pendiente. |
| POS-11 | **Descuentos y tarjetas de fidelidad** | Sustituir desc. % por ítem legacy; monedero/fidelidad. *Acordado: más adelante.* |
| POS-12 | **Focus al editar descuento** | No re-montar el input; solo montos y previa. |
| POS-13 | **Feedback ítem ya en carrito** | Toast o resaltar card. |
| POS-14 | **Vaciar carrito** | Botón + confirmación. |
| POS-15 | **Monedero / puntos en previa** | Mostrar umbral `maximos` antes de cerrar. |
| POS-16 | **Pulido visual / responsive** | Densidad, móvil, Select2 en input-group, a11y modal. |
| POS-17 | **KPIs reales en `/dashboard`** | Gráficas / métricas **además** del POS (no reemplazarlo). Fase 6 SaaS. |

### P2 — Capacidades nuevas

| ID | Pendiente | Notas |
|----|-----------|--------|
| POS-20 | **Cantidad N por línea** | N muestras en `VentaService`. |
| POS-21 | **Descuento global** | % o monto; convivir con puntos. |
| POS-22 | **Cotización PDF sin guardar** | Preview sin crear venta. |
| POS-23 | **Reabrir cotización** | Cargar cotización al carrito → venta. |
| POS-24 | **Alta express paciente** | Nombre mínimo / tags Select2. |

### P3 — Deuda / plan SaaS

| ID | Pendiente | Notas | Relación |
|----|-----------|--------|----------|
| POS-30 | Carrito en BD (`carrito_item`) | Persistencia entre sesiones | Fase 4 |
| POS-32 | Multi-tenant | No chocar con `tenant_id` futuro | Fases 0–3 |
| POS-33 | Borrar `empleado/catalogo/index.php` | Vista muerta | Limpieza |
| POS-34 | Renombrar `/cotizacion` → `/venta` o `/pos` | DX / URL | Cosmético |

---

## 7. Checklist de aceptación

- [x] POS 8+4 en `/cotizacion`
- [x] POS embebido en `/dashboard` (admin) bajo cards de acceso
- [x] Partials compartidos UI + JS
- [x] Paciente: buscar / crear modal / seleccionar
- [x] Análisis: buscar / agregar; sin duplicar
- [x] Descuento % y quitar actualizan previa
- [x] Precio siempre `General` (sin tipo de precio en POS)
- [x] Venta y cotizar → `VentaService` → ticket
- [x] Métodos de pago visuales sin persistir
- [x] Cards con fondo blanco distinguible
- [x] Catálogo redirige a cotización
- [ ] Smoke E2E exhaustivo (POS-02)
- [ ] Sucursal garantizada al vender (POS-01)

---

## 8. Cómo seguir en la siguiente sesión

1. Leer este archivo + bitácora final de `PLAN_ACTUALIZACION.md`.
2. Priorizar con el usuario (sugerido: **POS-01**, **POS-11** cuando toque fidelidad, **POS-17** KPIs, o feedback UX).
3. No bifurcar `VentaService` salvo necesidad real.
4. Al cerrar: actualizar §5–§6 de este doc y bitácora de `PLAN_ACTUALIZACION.md`.

### Comandos útiles (Docker)

```bash
docker compose up -d
# Empleado: test_empleado / Prueba2026emp  →  https://labsaas.test/cotizacion
# Admin:    test_admin    / Prueba2026admin →  https://labsaas.test/dashboard

docker exec labsaas php -l app/Controllers/Empleado/Carrito.php
docker exec labsaas vendor/bin/phpunit
```

---

## 9. Historial breve

| Fecha | Qué |
|-------|-----|
| 2026-07-21 | Contexto: POS planeado; preguntas en `ANALISIS_CONTEXTO.md` |
| 2026-07-22 | **Iteración 1**: POS unificado en `/cotizacion`; doc creado con backlog |
| 2026-07-22 | Cards blancas; eliminado tipo de precio del POS (precio `General`) |
| 2026-07-22 | Admin: POS en `/dashboard` (atajos + caja); partials `_pos` / `_pos_scripts` |
| 2026-07-22 | Documento de contexto unificado al estado actual (este archivo) |
