# TPV — Checkout, pagos mixtos y tickets > Implementado en F-186 y ampliado en F-187. Complementa `POS_API.md` y sustituye el flujo inmediato de pago único descrito en documentos de discovery antiguos. ## Configuración en administración En **Administración → TPV**, selecciona una tienda para configurar: ### Terminal - Navegación táctil por categorías. - Hasta **ocho** productos rápidos por terminal. - **Permitir descuentos por línea**. Desactívalo en terminales de autopago. La restricción se aplica también en backend, no solo ocultando el botón. ### Formas de pago Cada tienda puede activar o desactivar métodos con: - código estable (`cash`, `card`, `bizum`, `stripe`, `apple_pay`, etc.); - etiqueta visible; - tipo `cash`, `card` u `other`. Una etiqueta no integra automáticamente una pasarela. Stripe, Apple Pay, Bizum u otros métodos se registran manualmente hasta que exista un adaptador de proveedor. ### Empresa y ticket Configura: - nombre o razón social; - NIF/CIF; - dirección, teléfono y email; - cabecera y pie; - prefijo, próximo número y dígitos de relleno; - política de devolución. La asignación del siguiente número se bloquea dentro de la transacción de venta, evitando números duplicados entre terminales concurrentes. ### Cajeros En **Administración → TPV → Cajeros** puedes crear cuentas con rol cajero y consultar su estado: - **Activo**: puede iniciar sesión y abrir caja. - **Inactivo**: no puede autenticarse; se puede reactivar. - **Eliminado**: no puede autenticarse ni reactivarse. **Desactivar** revoca inmediatamente todas las sesiones del cajero. Al reactivarlo tendrá que iniciar una sesión nueva. **Eliminar** requiere confirmación y realiza una baja lógica irreversible. La fila y el UUID se conservan para que tickets, ventas, sesiones de caja, reporting y auditoría sigan indicando quién realizó la operación. No se puede desactivar ni eliminar un cajero con una sesión de caja abierta. Primero debe cerrarse y cuadrarse esa sesión. API administrativa: - `GET /pos/users`: lista personal POS y estado de lifecycle. - `POST /pos/users`: crea cajero/manager; contraseña mínima de ocho caracteres. - `PATCH /pos/users/:id/status` con `{ "active": false }` o `{ "active": true }`. - `DELETE /pos/users/:id`: baja lógica irreversible del cajero. Las operaciones de estado y eliminación solo admiten objetivos con rol `pos_cashier`, requieren administrador y generan eventos de auditoría. ## Flujo de caja 1. Añade productos de catálogo o un **Artículo libre** (nombre y precio positivo). 2. Aplica descuentos por línea si el terminal los permite. 3. Pulsa una forma de pago configurada. 4. En el modal elige: - **Paga el total**: asigna todo el importe pendiente. - **Paga una parte**: introduce un importe menor y añade después otro método. 5. En efectivo, indica lo entregado. El TPV muestra el cambio. 6. Revisa las líneas de pago, el total pagado y el importe pendiente. Se puede quitar una asignación antes de confirmar. 7. Cuando el pendiente sea cero, pulsa **Confirmar y cerrar ticket**. 8. Imprime o envía el ticket por email. La caja se limpia después de completar una de estas acciones. Una venta totalmente pagada queda en estado `COMPLETED`. Los pagos pendientes se implementan aparte en F-188. ## Contrato de venta `POST /pos/sales` ### Línea de stock ```json { "kind": "stock", "variantId": "uuid", "quantity": 2, "discountCents": 100 } ``` El backend ignora snapshots antiguos enviados por el cliente y vuelve a cargar nombre, SKU, EAN y precio. También bloquea y actualiza el stock de la tienda. ### Artículo libre ```json { "kind": "free", "name": "Servicio de asesoría", "unitPriceCents": 2500, "quantity": 1 } ``` No reserva ni descuenta inventario. La base de datos exige que `product_id` y `variant_id` sean nulos únicamente en estas líneas. ### Pago ```json { "methodCode": "cash", "amountCents": 1000, "tenderedCents": 2000 } ``` - La suma de `amountCents` debe coincidir exactamente con el total. - Solo un método de tipo efectivo acepta `tenderedCents`. - `tenderedCents` debe ser mayor o igual que el importe aplicado. - El cambio es `tenderedCents - amountCents`. - Un código inactivo o de otra tienda se rechaza. La venta, stock, pagos, líneas de reporting, saldo esperado de caja, número y ticket se confirman en una única transacción idempotente. ## Ticket La respuesta de venta y `GET /pos/sales/:id/receipt` contienen: - datos de empresa; - fecha y hora; - número de ticket; - terminal, sesión y cajero; - artículos, cantidad, precio, subtotal, descuento, IVA y total de línea; - subtotal, descuentos, IVA y total de venta; - métodos e importes pagados; - efectivo entregado y cambio; - política de devolución. ### Impresión `GET /pos/sales/:id/print` devuelve el mismo payload estructurado. El cliente usa impresión estándar del navegador, sin controlador específico de hardware. ### Email `POST /pos/sales/:id/receipt/email` ```json { "email": "cliente@example.es" } ``` Usa la configuración SMTP de **Ajustes → SMTP / Email**. El asunto y contenido se generan exclusivamente desde el ticket; el cliente no puede suministrar HTML arbitrario. ## Reporting y caja Por cada asignación se crea: - una transacción en `payments_transactions`; - una línea `payment` en `reporting_payment_lines` con tienda, terminal, sesión y método. El efectivo esperado aumenta por el importe aplicado, no por el efectivo entregado; el cambio no cuenta como ingreso ni efectivo retenido. ## Próximas ampliaciones - F-188: ventas con saldo pendiente. - F-189: cantidades negativas, devoluciones parciales/totales y ticket de devolución. - F-190: auditoría completa de actualización/refresco de reporting. - F-191: cierre de terminal y cierre diario conciliando efectivo, tarjetas, devoluciones y pendientes.