Caja
Caja
Prefijo: /api/caja
Requiere Auth: Sí
Permiso requerido: caja, *
Módulo de caja para gestión del dinero en efectivo y electrónico. El flujo típico es: apertura de caja → operaciones (ventas, gastos) → cierre con resumen.
GET /api/caja/
Descripción: Lista los registros de caja del tenant con paginación y filtros opcionales por rango de fechas y usuario/cajero. Ideal para el panel de administración que muestra el historial de cierres de caja con filtros por día y por cajero.
Requiere Auth: Sí
Permiso requerido: caja, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
skip | int | ❌ | Registros a saltar (default 0) |
limit | int | ❌ | Máximo de registros a retornar (default 100) |
desde | date | ❌ | Fecha inicio del filtro inclusiva (YYYY-MM-DD). Filtra por Caja.fecha >= desde |
hasta | date | ❌ | Fecha fin del filtro inclusiva (YYYY-MM-DD). Filtra por Caja.fecha <= hasta |
usuario | string | ❌ | Filtra por nombre exacto del usuario/cajero que registró el cierre |
Comportamiento:
- Sin filtros: retorna los 100 registros más recientes del tenant ordenados por fecha descendente.
- Con
desdeyhasta: retorna solo los cierres dentro del rango (inclusive). Útil para reportes diarios/semanales. - Con
usuario: retorna solo los cierres de ese cajero específico. Combinable con rango de fechas. - Los filtros se combinan con AND lógico.
Respuesta exitosa: 200 OK
Headers de respuesta:
| Header | Tipo | Descripción |
|---|---|---|
X-Total-Count | integer | Total de cierres de caja que cumplen los filtros (desde, hasta, usuario). No considera skip/limit. |
Response:
[ { "id": 1, "base": 500000, "cierre": 850000, "credito": 100000, "efectivo": 700000, "gastos": 50000, "total": 750000, "transferencia": 50000, "fecha": "2025-01-15", "hora": "18:00:00", "usuario": "Juan Pérez", "id_jornada": 1, "tenant_id": "uuid-tenant" }]cURL:
# Sin filtroscurl "http://localhost/api/caja/?skip=0&limit=10" -H "Authorization: Bearer <token>"
# Solo cierres del 15 al 20 de enerocurl "http://localhost/api/caja/?desde=2025-01-15&hasta=2025-01-20" -H "Authorization: Bearer <token>"
# Solo cierres de un cajero específicocurl "http://localhost/api/caja/?usuario=Juan%20Pérez&desde=2025-01-15" -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.get("http://localhost/api/caja/", params={"desde": "2025-01-15", "hasta": "2025-01-20", "usuario": "Juan Pérez"}, headers={"Authorization": f"Bearer :token"})caja = resp.json()total = int(resp.headers.get("X-Total-Count", "0"))total_pages = (total + 10 - 1) // 10 # ceil divisionJavaScript:
const params = new URLSearchParams({desde: '2025-01-15', hasta: '2025-01-20', usuario: 'Juan Pérez'});const resp = await fetch(`http://localhost/api/caja/?$:params`, { headers: {'Authorization': `Bearer $:token`}});const caja = await resp.json();const totalCount = parseInt(resp.headers.get('X-Total-Count') || '0', 10);const totalPages = Math.ceil(totalCount / 10);GET /api/caja/count [NUEVO]
Descripción: Cuenta el total de registros de caja del tenant con los mismos filtros opcionales que GET /api/caja/. Diseñado para paginación del frontend: primero se llama /count para saber cuántos registros hay, y luego se llama /api/caja/ con skip y limit para la página actual.
Requiere Auth: Sí
Permiso requerido: caja, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
desde | date | ❌ | Fecha inicio del filtro inclusiva (YYYY-MM-DD) |
hasta | date | ❌ | Fecha fin del filtro inclusiva (YYYY-MM-DD) |
usuario | string | ❌ | Filtra por nombre exacto del usuario/cajero |
Caso de uso típico:
- El admin abre la vista “Cierres de Caja” en NeoxAdminPanel.
- Frontend llama
GET /api/caja/count?desde=2025-01-15&hasta=2025-01-20para saber que hay 45 cierres en ese rango. - Frontend renderiza paginator con 5 páginas (10 por página).
- Para cada página, llama
GET /api/caja/?desde=2025-01-15&hasta=2025-01-20&skip=0&limit=10.
Respuesta exitosa: 200 OK
Response:
{ "total": 45}cURL:
# Contar todos los cierres del tenantcurl "http://localhost/api/caja/count" -H "Authorization: Bearer <token>"
# Contar cierres de un rango específicocurl "http://localhost/api/caja/count?desde=2025-01-15&hasta=2025-01-20" -H "Authorization: Bearer <token>"
# Contar cierres de un cajero en un rangocurl "http://localhost/api/caja/count?usuario=Juan%20Pérez&desde=2025-01-15&hasta=2025-01-20" -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.get("http://localhost/api/caja/count", params={"desde": "2025-01-15", "hasta": "2025-01-20"}, headers={"Authorization": f"Bearer :token"})total = resp.json()["total"] # 45JavaScript:
const resp = await fetch('http://localhost/api/caja/count?desde=2025-01-15&hasta=2025-01-20', { headers: {'Authorization': `Bearer $:token`}});const { total } = await resp.json(); // 45GET /api/caja/resumen-cierre
Descripción: Obtiene el resumen de cierre de jornada para un usuario
Requiere Auth: Sí
Permiso requerido: caja, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
usuario | string | ✅ | Nombre del usuario |
Respuesta exitosa: 200 OK
Response:
{ "ventas_canal": { "local": 450000, "electronico": 150000 }, "ventas_medio": { "efectivo": 400000, "transferencia": 100000, "credito": 100000 }, "creditos_jornada": { "monto_total": 200000, "abono_inicial": 0 }, "abonos": { "efectivo": 50000, "digital": 30000, "total": 80000 }, "gastos": 50000, "ingresos": 0, "retiros": 0, "base_inicial": 500000}cURL:
curl "http://localhost/api/caja/resumen-cierre?usuario=Juan%20P%C3%A9rez" -H "Authorization: Bearer <token>"GET /api/caja/base-inicial
Descripción: Obtiene la base inicial de la jornada activa del usuario
Requiere Auth: Sí
Permiso requerido: caja, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
usuario | string | ✅ | Nombre del usuario |
Respuesta exitosa: 200 OK
Response:
500000cURL:
curl "http://localhost/api/caja/base-inicial?usuario=Juan%20P%C3%A9rez" -H "Authorization: Bearer <token>"GET /api/caja/gastos
Descripción: Obtiene el total de gastos en un rango de fechas
Requiere Auth: Sí
Permiso requerido: caja, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
desde | date | ✅ | Fecha inicial |
hasta | date | ✅ | Fecha final |
Respuesta exitosa: 200 OK
Response:
{ "total": 150000, "fecha": "2025-01-01"}cURL:
curl "http://localhost/api/caja/gastos?desde=2025-01-01&hasta=2025-01-15" -H "Authorization: Bearer <token>"GET /api/caja/ultimo
Descripción: Obtiene el último ID de caja registrado
Requiere Auth: Sí
Permiso requerido: caja, leer
Respuesta exitosa: 200 OK
Response:
42cURL:
curl http://localhost/api/caja/ultimo -H "Authorization: Bearer <token>"POST /api/caja/
Descripción: Registra el CIERRE de caja con todos los totales del día (base, cierre, efectivo, crédito, gastos, transferencia). Diferencia clave vs /apertura: este endpoint cierra la jornada contabilizando ventas, gastos y calculando el cuadratura. Usar al final del día cuando el cajero hace arqueo.
Requiere Auth: Sí
Permiso requerido: caja, crear
Request:
{ "base": 500000, "cierre": 1200000, "efectivo": 800000, "credito": 100000, "gastos": 50000, "transferencia": 50000, "total": 1150000, "usuario": "Juan Pérez", "id_jornada": 5}Campos:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
base | int | ✅ | Base inicial (debe coincidir con apertura) |
cierre | int | ✅ | Total en caja al cerrar (efectivo + cheques) |
efectivo | int | ✅ | Efectivo contado en arqueo |
credito | int | ✅ | Ventas a crédito del día |
gastos | int | ✅ | Egresos/gastos del día |
transferencia | int | ✅ | Transferencias recibidas |
total | int | ✅ | efectivo + credito + transferencia - gastos (debe cuadrar con cierre) |
usuario | string | ✅ | Cajero que cierra |
id_jornada | int | ✅ | Jornada a cerrar |
Comportamiento / Casos de uso:
- Cierre diario: cajero cuenta efectivo, ingresa totales, sistema valida cuadratura
- Si
total != cierre→ advertencia pero permite guardar (para ajustes manuales) - Crea registro en
caja+ actualizajornada_caja.estado = CERRADA
Respuesta exitosa: 201 Created
Response:
{ "id": 10, "base": 500000, "cierre": 1200000, "credito": 100000, "efectivo": 800000, "gastos": 50000, "total": 1150000, "transferencia": 50000, "fecha": "2025-01-15", "hora": "18:00:00", "usuario": "Juan Pérez", "id_jornada": 5, "tenant_id": "uuid-tenant"}Errores:
| Código | Causa | Solución |
|---|---|---|
| 400 | Jornada no activa o datos inválidos | Verificar id_jornada y usuario |
| 403 | Sin permiso caja, crear | Solicitar a admin |
| 409 | Jornada ya cerrada | No se puede cerrar dos veces |
cURL:
curl -X POST http://localhost/api/caja/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"base":500000,"cierre":1200000,"efectivo":800000,"credito":100000,"gastos":50000,"transferencia":50000,"total":1150000,"usuario":"Juan Pérez","id_jornada":5}'Python:
import httpxresp = httpx.post("http://localhost/api/caja/", json={"base": 500000, "cierre": 1200000, "efectivo": 800000, "credito": 100000, "gastos": 50000, "transferencia": 50000, "total": 1150000, "usuario": "Juan Pérez", "id_jornada": 5}, headers={"Authorization": f"Bearer :token"})cierre = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/caja/', { method: 'POST', headers: {'Content-Type': 'application/json', 'Authorization': `Bearer $:token`}, body: JSON.stringify({base: 500000, cierre: 1200000, efectivo: 800000, credito: 100000, gastos: 50000, transferencia: 50000, total: 1150000, usuario: 'Juan Pérez', id_jornada: 5})});const cierre = await resp.json();POST /api/caja/apertura
Descripción: Registra la apertura de caja para un usuario
Requiere Auth: Sí
Permiso requerido: caja, crear
Request:
{ "base": 500000, "usuario": "Juan Pérez"}Respuesta exitosa: 201 Created
Response:
{ "id_caja": 10, "id_jornada": 5, "base": 500000, "mensaje": "Apertura registrada exitosamente"}cURL:
curl -X POST http://localhost/api/caja/apertura \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"base":500000,"usuario":"Juan Pérez"}'GET /api/caja/:caja_id
Descripción: Obtiene el detalle completo de un cierre de caja por ID. Incluye todos los totales (base, cierre, efectivo, crédito, gastos, transferencia) y metadatos (fecha, hora, usuario, jornada). Para auditoría, reimpresión de cierre o corrección de errores.
Requiere Auth: Sí
Permiso requerido: caja, leer
Parámetros path:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
caja_id | int | ✅ | ID del registro de caja (cierre) |
Comportamiento / Casos de uso:
- Admin revisa cierre específico por discrepancia en arqueo
- Reimprimir comprobante de cierre
- Verificar que un cierre tiene los datos correctos antes de anular
Respuesta exitosa: 200 OK
Response:
{ "id": 1, "base": 500000, "cierre": 850000, "credito": 100000, "efectivo": 700000, "gastos": 50000, "total": 750000, "transferencia": 50000, "fecha": "2025-01-15", "hora": "18:00:00", "usuario": "Juan Pérez", "id_jornada": 1, "tenant_id": "uuid-tenant"}Errores:
| Código | Causa | Solución |
|---|---|---|
| 404 | Cierre no encontrado | Verificar caja_id |
| 403 | Sin permiso caja, leer | Solicitar a admin |
cURL:
curl http://localhost/api/caja/1 -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.get("http://localhost/api/caja/1", headers={"Authorization": f"Bearer :token"})caja = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/caja/1', { headers: {'Authorization': `Bearer $:token`}});const caja = await resp.json();PUT /api/caja/:caja_id
Descripción: Actualiza campos de un cierre de caja ya registrado. Solo admin para corregir errores de digitación en arqueo. No cambia el estado de la jornada (para reabrir usar jornadas).
Requiere Auth: Sí
Permiso requerido: caja, actualizar
Parámetros path:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
caja_id | int | ✅ | ID del registro de caja |
Request (campos opcionales, solo los enviados):
{ "cierre": 900000, "efectivo": 750000, "total": 850000, "gastos": 60000}Campos actualizables:
| Campo | Tipo | Descripción |
|---|---|---|
cierre | int | Total en caja al cerrar |
efectivo | int | Efectivo contado |
credito | int | Ventas a crédito |
gastos | int | Egresos del día |
transferencia | int | Transferencias |
total | int | Total calculado |
Comportamiento / Casos de uso:
- Corrección de error de digitación: cajero escribió mal el efectivo
- Ajuste post-cierre: se detecta diferencia en conciliación bancaria
- NO recalcula automáticamente — el admin debe asegurar que
total = efectivo + credito + transferencia - gastos
Respuesta exitosa: 200 OK — retorna CajaResponse actualizado
Response:
{ "id": 1, "base": 500000, "cierre": 900000, "credito": 100000, "efectivo": 750000, "gastos": 60000, "total": 850000, "transferencia": 50000, "fecha": "2025-01-15", "hora": "18:00:00", "usuario": "Juan Pérez", "id_jornada": 1, "tenant_id": "uuid-tenant"}Errores:
| Código | Causa | Solución |
|---|---|---|
| 404 | Cierre no encontrado | Verificar caja_id |
| 403 | Sin permiso caja, actualizar | Solo admin |
| 400 | Datos inválidos | Validar tipos numéricos |
cURL:
curl -X PUT http://localhost/api/caja/1 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"cierre":900000,"efectivo":750000,"total":850000,"gastos":60000}'Python:
import httpxresp = httpx.put("http://localhost/api/caja/1", json={"cierre": 900000, "efectivo": 750000, "total": 850000, "gastos": 60000}, headers={"Authorization": f"Bearer :token"})caja = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/caja/1', { method: 'PUT', headers: {'Content-Type': 'application/json', 'Authorization': `Bearer $:token`}, body: JSON.stringify({cierre: 900000, efectivo: 750000, total: 850000, gastos: 60000})});const caja = await resp.json();DELETE /api/caja/:caja_id
Descripción: Elimina un registro de cierre de caja (hard delete). Solo para casos excepcionales: registro de prueba, cierre duplicado por error de sistema. NO usar para anular cierre real — eso requiere reabrir jornada y nuevo cierre.
Requiere Auth: Sí
Permiso requerido: caja, eliminar
Parámetros path:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
caja_id | int | ✅ | ID del registro de caja |
Comportamiento / Casos de uso:
- Cierre de prueba creado en desarrollo/testing
- Doble cierre por bug de frontend (verificar que no haya movimientos posteriores)
- ADVERTENCIA: Elimina el registro permanentemente. No hay soft delete en caja.
Respuesta exitosa: 204 No Content
Errores:
| Código | Causa | Solución |
|---|---|---|
| 404 | Cierre no encontrado | Verificar caja_id |
| 403 | Sin permiso caja, eliminar | Solo superadmin |
| 409 | Tiene dependencias (movimientos) | No se puede eliminar |
cURL:
curl -X DELETE http://localhost/api/caja/1 -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.delete("http://localhost/api/caja/1", headers={"Authorization": f"Bearer :token"})assert resp.status_code == 204JavaScript:
const resp = await fetch('http://localhost/api/caja/1', { method: 'DELETE', headers: {'Authorization': `Bearer $:token`}});// 204 = éxito sin body