Flujo de efectivo
CashFlowManager gestiona el ciclo de vida de la sesión de caja (arqueo): apertura,
movimientos manuales, cierre con cuadratura y reasignación de pagos huérfanos. Es la
contraparte del módulo de finanzas y se apoya en CashRegisterManager
para la asociación con la caja física.
Fuente: repos/tables-sdk/src/core/managers/CashFlowManager.ts.
Modelo de datos
Sección titulada «Modelo de datos»CashFlowDocument
Sección titulada «CashFlowDocument»Es el documento PouchDB de la sesión de caja (type: 'cashflow'). Campos centrales:
| Campo | Tipo | Descripción |
|---|---|---|
opening_amount | number | Monto de apertura (caja inicial). |
closing_amount | number? | Monto al cierre (igual a declared_cash). |
opening_date / closing_date | string | Marcas ISO de apertura y cierre. |
status | 'open' | 'closed' | Estado de la sesión. |
cash_register_id | string | Caja registradora asociada. |
user_id | string | Usuario legacy (apertura o último editor). |
movements | CashMovement[] | Movimientos manuales embebidos. |
Al cerrar se persiste además un snapshot de cuadratura (declared_cash,
expected_cash, difference, total_sales, total_cash, total_card,
total_transfer, total_tips, excluded_payments_count) y los campos de apertura/cierre
(opened_by_user_id, closed_by_user_id, nombres y umbral de descuadre). Todos son
opcionales para preservar retrocompatibilidad con sesiones previas.
CashMovement
Sección titulada «CashMovement»Movimiento embebido en movements[] (no es un documento PouchDB independiente):
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador (nanoid). |
amount | number | Monto del movimiento. |
operation_type | 'income' | 'expense' | Ingreso o egreso. |
date | string | Marca ISO. |
category | CashMovementCategory? | 'sale' | 'tip' | 'paid_in' | 'paid_out'. |
subcategory | CashMovementSubcategory? | Vocabulario controlado (ver abajo). |
payment_method | string? | Método asociado (p. ej. 'cash'). |
saleId | string? | Pago de venta que originó el movimiento. |
user_id | string? | Usuario que registró. |
notes | string? | Observación libre. |
category, subcategory y user_id son opcionales por retrocompatibilidad. Las
subcategorías están acotadas por PAID_IN_SUBCATEGORIES (deposit,
change_replenishment, bank_drop_return, correction, other) y
PAID_OUT_SUBCATEGORIES (reimbursement, supplies, vendor_payment, safe_drop,
correction, other), exportadas para que la UI reutilice el mismo conjunto.
Asociación con la caja y el turno
Sección titulada «Asociación con la caja y el turno»La sesión vive sobre una caja registradora (cash_register_id). CashRegisterManager
resuelve qué caja usar:
resolveForArea(areaId): caja asignada al área (area_idsoareaIdlegacy); si no, la caja default de la compañía; como último recurso, cualquier caja activa.getDefault()/setDefault(registerId): caja default de la compañía.
Al abrir una sesión, openCashFlow setea cash_register_id en el documento y escribe
cash_flow_id en la caja (puntero a la sesión abierta); al cerrar, ese puntero se limpia.
Para la relación con la jornada operativa ver Turnos.
Ciclo de vida
Sección titulada «Ciclo de vida»-
Apertura —
openCashFlow(cashRegisterId, userId, openingAmount, opts?)crea el documento (status: 'open'), enlaza la caja, snapshotea el umbral de descuadre (max_close_difference_amount) desde la config de compañía y adopta pagos huérfanos creados tras el cierre anterior (opening_adopted_payments_count). -
Movimientos — durante la sesión abierta se registran ingresos/egresos manuales.
-
Cierre —
closeCashFlow(id, declaredCashOrParams)calcula la cuadratura, valida la política de descuadre y persiste el snapshot. La caja queda inmutable. -
Impresión —
printArqueo(cashFlowId, opts?)encola el voucher de arqueo en la cola del printer-daemon (requiere sesión cerrada; advierte si no lo está).
Movimientos (solo sesión abierta)
Sección titulada «Movimientos (solo sesión abierta)»registerMovement(cashFlowId, movement)— agrega unCashMovement(asignaid).updateMovement(cashFlowId, movementId, patch, actorUserId?)— corrige un movimiento.deleteMovement(cashFlowId, movementId, actorUserId?)— remueve el movimiento del array.
Las tres operaciones rechazan con CASHFLOW_CLOSED si la caja está cerrada y emiten
audit logs (cashflow.movement.create / .update / .delete).
Cómputo del resumen
Sección titulada «Cómputo del resumen»CashFlowManager.computeSummary(cashFlow, payments) (estático) calcula el resumen en
vivo combinando movimientos manuales y PaymentDocument enlazados:
totalCashIncome/totalExpenses— suma demovementsporoperation_type.expectedCashInRegister—opening_amount + totalCashIncome - totalExpenses.totalSalesysalesByMethod— suma dePayment.amountagrupada pormethod_code.totalTipsytipsByMethod— suma detip_amountpor método.
Solo se consideran pagos liquidados: SETTLED_PAYMENT_STATUSES = ['paid', 'completed']
(helper isSettledPayment). Los estados no liquidados (pending, processing,
failed, refunded) se excluyen.
getSummary(cashFlowId) es el atajo async: carga la sesión, obtiene sus pagos vía
BillingManager.getPaymentsByCashFlow y devuelve el CashFlowSummary.
Snapshot de cierre
Sección titulada «Snapshot de cierre»computeCloseSnapshot(cashFlow, payments, params) (estático) construye la cuadratura:
- Clasifica los pagos liquidados en
total_cash/total_card/total_transfersegúnmethod_code(acepta variantes en inglés y español:cash/efectivo,transfer/transferencia,card/credit/debit/credito/debito). expected_cash = opening_amount + income(movements) - expense(movements) + other_income - egresos.difference = declared_cash - expected_cash(0cuadra,> 0sobrante,< 0faltante).excluded_payments_count— pagos enlazados no liquidados; no suman a ningún total pero se registran en el audit log al cerrar.
Política de descuadre
Sección titulada «Política de descuadre»Si la sesión tiene max_close_difference_amount (snapshot de la config al abrir) y
|difference| lo excede, closeCashFlow exige close_difference_reason (mínimo 10
caracteres); de lo contrario rechaza con CLOSE_DIFFERENCE_REASON_REQUIRED. El cierre
con descuadre aceptado emite cashflow.close.difference_acknowledged.
Pagos huérfanos
Sección titulada «Pagos huérfanos»Pagos creados sin sesión activa (offline, cashFlowId nulo o sentinel '-1', o
apuntando a una sesión ya cerrada) se reasignan en dos puntos:
openCashFlowadopta los huérfanos de la caja al abrir.recoverOrphanPayments(cashRegisterId)reasigna, a la sesión abierta actual, pagos liquidados dentro de la ventana temporal de esa sesión; para pagos en efectivo sin movimiento de venta crea elsale(ytip) correspondiente de forma idempotente.
recoverOrphanPayments se invoca antes de cada cierre y puede engancharse al evento
online del NetworkMonitor con initRecoveryListener. Es idempotente y está
protegido contra reentrancia por caja.
Eventos de auditoría
Sección titulada «Eventos de auditoría»CashFlowManager emite entradas vía AuditLogManager:
cashflow.open, cashflow.close, cashflow.movement.create / .update / .delete,
payment.adopted, cashflow.orphan_payments.recovered,
cashflow.close.excluded_payments, cashflow.close.difference_acknowledged y
cashflow.close.pending_orders_overridden.