Saltar al contenido principal

Dimensiones Contables

Modelo dimensional SAP-like

A partir del Sub-proyecto 1 — Fundamento Dimensional (mayo 2026), cada línea de un asiento contable (journal_entry_lines) lleva 5 dimensiones independientes que responden 5 preguntas distintas:

DimensiónPregunta que respondeTabla / FK
Plan de Cuentas¿Qué naturaleza tiene el flujo? (activo, pasivo, gasto, ingreso)account_idaccounting_accounts
Centro de Costo¿Quién o qué unidad organizacional es responsable?cost_center_idcost_centers
Área Funcional¿Cómo se reporta a PDVSA? (clasificador upstream)functional_area_idfunctional_areas
Proyecto / Pozo¿Para qué iniciativa o activo se incurre?project_idprojects
Renglón Presupuestario¿Qué partida del presupuesto consume?budget_position_idbudget_positions

department_id se conserva como dimensión derivada (a través de cost_center.department_id) y se materializa en cada línea para queries rápidas.


Por qué 5 dimensiones independientes

Antes del Sub-proyecto 1 el sistema mezclaba tres conceptos en una única tabla cost_centers:

  1. Centro de costo (responsabilidad organizacional)
  2. Clasificación presupuestaria PDVSA (renglón)
  3. Mapeo a plan de cuentas

Esto rompe la regla básica de los ERPs serios (SAP, Oracle, Odoo): cada dimensión debe poder filtrarse, sumarse y reportarse de forma independiente. Un mismo gasto puede ser:

  • Centro de Costo CC-OPS-POZOS (responsable)
  • Área Funcional LEV (PDVSA)
  • Proyecto Pozo Sinco-3 (iniciativa)
  • Renglón ACE-1-1AL-LEV (partida presupuestaria)
  • Cuenta 6.3.01.018 (materiales)

Si están en una sola tabla, no se pueden cruzar correctamente.


Reglas de Validación en journalEntryService.create()

Al crear una línea de asiento, el backend valida según el tipo de cuenta:

Cuenta EXPENSE   → cost_center_id NOT NULL
Cuenta REVENUE → cost_center_id NOT NULL
Cuenta CAPEX → project_id NOT NULL
(CAPEX = subtipo FIXED_ASSET o INTANGIBLE_ASSET)
budget_position_id presente
→ debe existir un link en account_budget_position_links
para esa cuenta con role IN (DEBIT_DEFAULT, CREDIT_DEFAULT, ALLOWED)
functional_area_id ausente + budget_position_id presente
→ se infiere automáticamente desde bp.functional_area_id

Ejemplos válidos

// Salario imputado a centro de costo OPS, área PER, sin proyecto
{
account_id: '6.1.01.001', // Sueldos
cost_center_id: 'CC-OPS',
functional_area_id: 'PER',
budget_position_id: 'ACE-OPS-PER',
debit: 100000
}

// CAPEX de pozo nuevo: project_id obligatorio
{
account_id: '1.2.05.003', // Activo en construcción
cost_center_id: 'CC-PROD',
functional_area_id: 'INPR',
project_id: 'Pozo Sinco-3',
budget_position_id: 'ACE-CAPEX-INPR',
debit: 50000000
}

// Si solo se envía budget_position_id, fa se infiere
{
account_id: '6.3.01.018',
cost_center_id: 'CC-MTO',
budget_position_id: 'ACE-2-2MC-LEV', // bp.functional_area_id = 'LEV'
debit: 250000
// → se rellena automáticamente functional_area_id = 'LEV'
}

Ejemplos rechazados

// ✗ Cuenta EXPENSE sin cost_center
{ account_id: '6.1.01.001', debit: 100 }
// → Error: cost_center_id requerido para cuenta EXPENSE

// ✗ Cuenta CAPEX sin project_id
{ account_id: '1.2.05.003', cost_center_id: 'CC-PROD', debit: 100 }
// → Error: project_id requerido para cuenta CAPEX (FIXED_ASSET)

// ✗ budget_position sin link configurado
{
account_id: '6.1.01.001',
cost_center_id: 'CC-OPS',
budget_position_id: 'ACE-CAPEX-INPR', // CAPEX vs cuenta OPEX
debit: 100
}
// → Error: no existe link en account_budget_position_links para esa cuenta

Reemplazó al JSONB cost_centers.accounting_accounts. Permite mapear N cuentas ↔ N renglones con un rol semántico.

CampoDescripción
account_idCuenta del plan
budget_position_idRenglón presupuestario
roleDEBIT_DEFAULT / CREDIT_DEFAULT / ALLOWED
is_activePermite desactivar mapeos sin borrar

Reglas:

  • Sólo un DEBIT_DEFAULT por cuenta.
  • Sólo un CREDIT_DEFAULT por cuenta.
  • Múltiples ALLOWED por cuenta (whitelist amplia).

API: GET|POST|DELETE /api/accounting/account-position-links y consultas inversas GET /api/accounting/accounts/:id/positions y GET /api/accounting/positions/:id/accounts.


Dimensiones propagadas a tablas operativas

Las mismas 5 FKs aparecen en módulos aguas arriba del libro mayor para garantizar que cuando el outbox materializa el asiento, todas las dimensiones llegan completas:

TablaFKs agregadas
budget_linescost_center_id, functional_area_id, budget_position_id
budget_commitmentscost_center_id, functional_area_id, budget_position_id
afe_expensescost_center_id, functional_area_id, budget_position_id
journal_entry_lineslas 5 + department_id derivado

Cada servicio (budgetLineService, purchaseOrderService, materialRequisitionService, payrollService, inventoryAccountingService, …) propaga las dimensiones desde su entidad origen al asiento generado.


Antipatterns evitados

AntipatternSíntomaSolución actual
Mezclar 3 conceptos en cost_centersReportes que cruzan responsabilidad con PDVSA producen totales infladosTabla cost_centers (responsabilidad), functional_areas (PDVSA) y budget_positions (renglón) separadas
Mapear cuentas dentro del CC en JSONBImposible hacer JOIN o índice; no se puede consultar "todas las cuentas que tocan el renglón X"Tabla relacional account_budget_position_links con índices
Referencia circular Department ↔ CostCenterBug clase: el ORM no podía cargar ambos ladoscost_centers.department_id (FK NOT NULL) — departments ya no apunta de vuelta
Columna string cost_center (DEPRECATED) coexistiendo con FK cost_center_id (UUID)Doble fuente de verdadM9 eliminó las columnas string; sólo queda la FK UUID
Inferir presupuesto desde cost_centers.budget_jan/feb/...Mensual hardcoded; no soporta moneda dual ni reexpresión NIIFPresupuesto vive en budget_lines.amount_* con período abierto

Permisos relacionados

accounting:functional-areas:read | :write
accounting:cost-centers:read | :write (nuevo CC, distinto de budget:positions)
accounting:mappings:read | :write (account_budget_position_links)
budget:positions:read | :write (renombre del cost-centers viejo)

accounting:cost-centers:* controla los 22 CCs reales (responsabilidad). budget:positions:* controla los 149 renglones presupuestarios.


Documentación relacionada

Referencia técnica completa: backend_erp/docs/superpowers/specs/2026-05-01-sub1-fundamento-dimensional-design.md.