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_id → accounting_accounts
Centro de Costo¿Quién o qué unidad organizacional es responsable?cost_center_id → cost_centers
Área Funcional¿Cómo se reporta a PDVSA? (clasificador upstream)functional_area_id → functional_areas
Proyecto / Pozo¿Para qué iniciativa o activo se incurre?project_id → projects
Renglón Presupuestario¿Qué partida del presupuesto consume?budget_position_id → budget_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.