Saltar al contenido principal

Ciclo de Gasto (Compromiso → Causado → Pagado)

El Ciclo de Gasto es la state machine canónica que ACE utiliza para trazar todo movimiento de dinero hacia un proveedor desde el momento que se firma una orden de compra hasta el cierre del asiento contable.

Reemplaza la dispersión previa de cuatro estados-máquina paralelos (budget_commitments.status, vendor_invoices.status, vendor_payments.status, journal_entries.status) con una única fuente de verdad y un event log inmutable (append-only) para auditoría completa.

Las cinco etapas

                                     CANCELLED (terminal, desde cualquier no-terminal)

COMMITTED ──→ ACCRUED ──→ AUTHORIZED_TO_PAY ──→ PAID ──→ CLOSED (terminal)
(skip si flag=FALSE)
EtapaTriggerAsiento contableNorma
COMMITTED (Comprometido)Aprobación de PO o contratoNinguno — sólo control presupuestalControl interno
ACCRUED (Causado)Factura del proveedor recibida y aprobada + recepción confirmadaD: Gasto/Activo
H: Cuentas por Pagar
VEN-NIIF Sec 2.36 (devengado)
AUTHORIZED_TO_PAYTesorería aprueba el pagoNinguno — control de tesoreríaControl de tesorería
PAID (Pagado)Egreso bancario ejecutadoD: Cuentas por Pagar
H: Banco
Pagos efectivos
CLOSED (Cerrado)Asientos posteados + reconciliados con extractoNingunoCierre contable

El estado AUTHORIZED_TO_PAY es configurable

Cada ciclo tiene un flag requires_payment_authorization (default TRUE). Si está en FALSE, la transición ACCRUED → PAID es directa (apropiado para pagos de baja materialidad o flujos de petty cash). Si está en TRUE, requiere paso expreso por tesorería.

ACE es empresa mixta de hidrocarburos bajo la Ley Orgánica de Hidrocarburos 2026 (G.O. 6.978). No es ente descentralizado del sector público, por lo que ONAPRE / LOAFSP no aplican directamente. Adoptamos el ciclo de cuatro etapas como mejor práctica industrial alineada al reporting upstream PDVSA, no por imposición legal.

La obligación contable VE-NIIF Sec 2.36 (registro por devengado, no por caja) sí es vinculante — y es por eso que el estado ACCRUED es el hito explícito que dispara el asiento contable.

Trazabilidad y bitácora de eventos

Cada transición genera una entrada inmutable en budget_commitment_events:

CampoDescripción
from_statusEstado origen (NULL si es evento inicial / backfill)
to_statusEstado destino
actor_idUsuario que disparó la transición (NULL si automática)
payloadRefs a entidades (factura, pago, JE) en JSONB
amount_deltaDiferencial monetario aplicado
notesNotas humanas (razón de cancelación, observaciones)
created_atTimestamp inmutable

La tabla NO admite UPDATE ni DELETE desde la aplicación — es append-only por diseño, ideal para auditoría CGR / Contraloría si se solicita.

Hooks automáticos

Para minimizar fricción operacional, dos hooks transicionan el ciclo automáticamente desde flujos existentes (tolerantes a errores — no bloquean la operación si no encuentran el commitment):

  • VendorInvoice aprobadaCOMMITTED → ACCRUED (vía onVendorInvoiceApproved)
  • VendorPayment completado[ACCRUED|AUTHORIZED_TO_PAY] → PAID (vía onVendorPaymentCompleted)

Las transiciones manuales en UI (botones de la tarjeta de Acciones) son para:

  • Saltos no automatizables (CLOSED requiere validación manual con extracto)
  • Casos donde no hay PO vinculada (compras spot, payroll)
  • Cancelación con razón obligatoria

UI

Listado

Disponible en /budget/expenditure-cycles. Filtros por estado, fecha y búsqueda libre. Cada fila muestra documento, descripción, proveedor, proyecto, montos comprometido/pagado, estado actual con chip de color y fecha.

Lista de ciclos de gasto

Detalle

/budget/expenditure-cycles/:id muestra:

  • Resumen — datos del ciclo
  • Acciones — botones de transición disponibles según estado actual + flags
  • Progreso — Stepper visual de las 5 etapas
  • Bitácora — eventos cronológicos con actor y notas

Detalle de ciclo en estado COMMITTED

Tras transicionar a ACCRUED, el botón cambia a "Autorizar pago (tesorería)" y el evento se registra en la bitácora:

Estado tras causación

Bitácora completa multi-transición

Tras avanzar por COMMITTED → ACCRUED → AUTHORIZED_TO_PAY, la bitácora muestra los 3 eventos cronológicos:

Bitácora con 3 eventos

Cancelación con razón obligatoria

Cualquier ciclo en estado no-terminal puede cancelarse, pero el modal exige una razón de texto libre. La razón queda registrada en cancellation_reason y también en notes del evento — para trazabilidad explícita.

Modal de cancelación

Permisos

PermisoPara
budget:cycle:readVer ciclos y eventos
budget:cycle:transitionAvanzar (causar, pagar, cerrar)
budget:cycle:authorize-payAutorizar pago (rol tesorería)
budget:cycle:cancelCancelar ciclo con razón

Asignados por defecto a: Director Técnico, Gerente de Contratación, Gerente de Logística, Estimador de Costos, Gerente de Finanzas, Contador, Administrador de Sistema, y solo lectura para gerencias generales (Yacimiento, Operaciones, Planificación). Super Admin con *:* cubre todos.

Endpoints

GET  /api/budget/expenditure-cycles                — list paginado con filtros
GET /api/budget/expenditure-cycles/:id — detalle con todas las relaciones
GET /api/budget/expenditure-cycles/:id/events — sólo eventos (orden cronológico)
POST /api/budget/expenditure-cycles/:id/transition — { toStatus, payload?, notes? }
POST /api/budget/expenditure-cycles/:id/cancel — { reason }

Estados legacy (pre-sub4)

Los enum values PARTIALLY_INVOICED, FULLY_INVOICED, EXECUTED quedan en la tabla por retrocompatibilidad con datos históricos. La state machine los normaliza a COMMITTED al evaluar transiciones, así que ciclos viejos pueden continuar el flujo sin migración manual.