Ir al contenido

Sesiones de Caja y Control de Turnos

El módulo de caja administra la apertura de turnos, el registro de movimientos manuales (sangrías), los arqueos provisionales (Corte X), el cierre formal de turno (Corte Z) y la generación de reportes vectoriales PDF de Corte X y Corte Z en el cliente (@react-pdf/renderer).


Las tablas principales que conforman la arquitectura del módulo en @uunit9/db son:

  • cash_sessions: Representa una sesión de caja o turno. Almacena marcas de tiempo (openedAt, closedAt), responsable de apertura/cierre (openedByUserId, closedByUserId), estado (status: 'open' | 'closed'), fondo inicial (openingAmount), montos esperados y contados (closingAmount, expectedAmount, difference), acumulados de sangrías (totalCashIn, totalCashOut) y métricas de auditoría (uncollectedOrdersCount, uncollectedAmount, totalTipsAmount, totalDiscountsAmount, totalVoidsAmount).
  • cash_transactions: Registra los movimientos manuales de dinero en efectivo (sangrías) ocurridos durante la sesión. Cada registro indica su tipo (type: 'in' | 'out'), monto (amount), motivo (reason), usuario creador (createdByUserId) y timestamp.
  • cash_session_counts: Guarda el arqueo detallado por método de pago realizado al momento del Corte Z (cashSessionId, paymentMethodId, expectedAmount, countedAmount, difference).
  • orders: Cada comanda registra su sesión de caja de apertura (opened_cash_session_id) y su sesión de cierre/cobro (closed_cash_session_id).
  • payments: Vincula las transacciones de cobro individuales con la sesión de caja activa (cashSessionId).
  • print_jobs: Cola de impresión asíncrona utilizada para comandas de cocina (KDS) y tickets de compra. (Nota: los reportes de sesión de caja se generan en cliente mediante @react-pdf/renderer).

El cálculo de efectivo esperado en caja durante la sesión o al momento del cierre se rige por la siguiente fórmula matemática:

$$\text{Esperado Efectivo} = \text{openingAmount} + \text{cashSalesTotal} + \text{totalCashIn} - \text{totalCashOut}$$

Donde:

  • $\text{openingAmount}$: Fondo inicial de caja al abrir el turno.
  • $\text{cashSalesTotal}$: Suma total de los pagos en efectivo registrados sin reembolsar durante la sesión.
  • $\text{totalCashIn}$: Suma de todas las entradas manuales de dinero en efectivo (Cash In).
  • $\text{totalCashOut}$: Suma de todos los retiros y sangrías manuales de dinero (Cash Out).

Para los métodos de pago electrónicos (Tarjetas, Transferencias, etc.), el saldo esperado corresponde al total de ventas procesadas mediante ese medio de pago específico:

$$\text{Esperado Medio Electronico} = \text{salesByMethod}[\text{type}]$$


Endpoints de la API (apps/api/src/routes/cash-sessions.ts)

Sección titulada «Endpoints de la API (apps/api/src/routes/cash-sessions.ts)»

Todas las rutas requieren rol de sucursal caja o superior.

  • GET /api/cash-sessions/current
    • Retorna la sesión de caja abierta en la sucursal activa (status = 'open') junto con sus métricas acumuladas en tiempo real, o null si no hay turno abierto.
  • GET /api/cash-sessions
    • Devuelve el listado de sesiones de caja de la sucursal con filtros por rango de fecha y paginación.
  • POST /api/cash-sessions/open
    • Body: { openingAmount: number }
    • Restricción: Solo se permite una sesión abierta simultáneamente por sucursal. Si ya existe un turno abierto, responde con HTTP 409 Conflict (session_already_open).

4. Registro de Sangrías y Movimientos Manuales

Sección titulada «4. Registro de Sangrías y Movimientos Manuales»
  • POST /api/cash-sessions/:id/transactions
    • Body: { type: "in" | "out", amount: number, reason: string }
    • Inserta un registro en cash_transactions y valida que la sesión de caja permanezca abierta. Si la sesión ya está cerrada, devuelve 409 session_already_closed.
  • GET /api/cash-sessions/:id/corte-x
    • Genera un arqueo parcial de la sesión activa en tiempo real sin modificar el estado del turno.
    • Retorna marca de agua (watermark: "CORTE X — ARQUEO PARCIAL"), métricas acumuladas, desglose por forma de pago, comandas pendientes y total de auditoría.
  • POST /api/cash-sessions/:id/close
    • Body:
      {
      "closingAmount": 1500.00,
      "closingByMethod": [
      { "paymentMethodId": "pm_cash", "amount": 1500.00 },
      { "paymentMethodId": "pm_card", "amount": 3200.00 }
      ],
      "allowOpenOrders": true,
      "notes": "Cierre de turno nocturno"
      }
    • Manejo de Conflicto de Comandas Abiertas: Si existen comandas abiertas sin cobrar (uncollectedOrdersCount > 0) y no se envía allowOpenOrders: true, la API rechaza el cierre respondiendo HTTP 409 Conflict:
      {
      "error": "open_orders_exist",
      "uncollectedOrdersCount": 3,
      "uncollectedAmount": 450.50
      }
    • Al autorizarse el cierre, actualiza el estado a closed, registra closedAt, congela las métricas de auditoría, guarda los conteos por método en cash_session_counts y calcula la diferencia final (difference = closingAmount - expectedAmount).

7. Generación de Reportes Vectoriales PDF (Cliente)

Sección titulada «7. Generación de Reportes Vectoriales PDF (Cliente)»

En la capa de frontend (apps/platform), los reportes de Corte X y Corte Z son construidos dinámicamente con @react-pdf/renderer a partir de los datos retornados por los endpoints GET /api/cash-sessions/:id/corte-x y GET /api/cash-sessions/:id. Los usuarios pueden previsualizar, imprimir nativamente o descargar los archivos PDF vectoriales en formato A4 con los componentes PDFDownloadLink y BlobProvider.