ConTodo ERP — Modelo Contable, PCGE 2019 y Motor de Asientos Automáticos
Módulo Contable de ConTodo ERP
Alcance. Este documento define el modelo contable núcleo de ConTodo: catálogo de cuentas basado en el Plan Contable General Empresarial (PCGE 2019), el libro mayor (general ledger) multi-tenant y multimoneda, el motor de asientos automáticos desde transacciones operativas (compras, ventas, planilla, tesorería), la gestión de cuentas por cobrar/pagar, activos fijos y depreciación, y la generación de los cuatro estados financieros bajo NIIF (Estado de Situación Financiera, Estado de Resultados, Estado de Flujos de Efectivo y Estado de Cambios en el Patrimonio). Se incluyen supuestos explícitos, riesgos, alternativas y diagramas mermaid.
1. Principios de diseño y supuestos
| # | Supuesto / Principio | Justificación |
|---|---|---|
| S1 | Partida doble inviolable: todo asiento debe cumplir Σ debe = Σ haber por moneda funcional. Validación a nivel de base de datos (constraint + trigger) y de aplicación. | La integridad contable no puede depender solo del frontend. Un asiento descuadrado nunca se persiste. |
| S2 | Moneda funcional = PEN por defecto, con soporte multimoneda. Cada journal_entry_line guarda monto en moneda origen, tipo de cambio (TC) y monto en moneda funcional. | NIC 21. Empresas importadoras textiles operan en USD; SUNAT exige libros en soles al TC SBS publicación/cierre. |
| S3 | Multi-tenant por company_id con aislamiento por Row Level Security (RLS) de PostgreSQL. El plan de cuentas se versiona: base PCGE compartida + cuentas personalizadas por empresa. | Multiempresa es requisito. RLS evita fugas de datos entre tenants a nivel de motor, no solo de ORM. |
| S4 | Inmutabilidad del asiento contabilizado: una vez posted, no se edita; se corrige con asiento de extorno o de ajuste. | Trazabilidad de auditoría (Big Four), exigencia de PLE/SIRE de SUNAT. |
| S5 | Plan de cuentas a 5 dígitos mínimo (PCGE) extensible a 7-9 dígitos para detalle analítico/sub-cuentas por empresa. | El PCGE define dígitos 1-2 (cuenta), 3 (subcuenta), 4-5 (divisionaria); el ERP permite auxiliares adicionales. |
| S6 | Centro de costo y dimensión analítica opcionales por línea (sucursal, proyecto, línea de producto). | BI y costeo por centro requieren etiquetas dimensionales sin multiplicar el catálogo. |
| S7 | Devengo (accrual) como base, no caja. El flujo de efectivo se deriva por método indirecto. | NIC 1. La caja es una vista derivada del mayor de cuentas 10. |
2. Catálogo de cuentas — PCGE 2019 (estructura por clases)
El PCGE organiza las cuentas en 9 elementos (clases). ConTodo precarga el catálogo oficial como seed del tenant y lo marca como system: true (no editable en su naturaleza, sí extensible en divisionarias).
| Clase | Elemento | Naturaleza | Saldo normal | Estado financiero |
|---|---|---|---|---|
| 1 | Activo disponible y exigible | Activo | Deudor | Situación Financiera |
| 2 | Activo realizable (existencias) | Activo | Deudor | Situación Financiera |
| 3 | Activo inmovilizado | Activo | Deudor | Situación Financiera |
| 4 | Pasivo | Pasivo | Acreedor | Situación Financiera |
| 5 | Patrimonio neto | Patrimonio | Acreedor | Situación Financiera |
| 6 | Gastos por naturaleza | Resultado | Deudor | Resultados |
| 7 | Ingresos | Resultado | Acreedor | Resultados |
| 8 | Saldos intermediarios de gestión / Determinación del resultado | Resultado | Mixto | Cierre |
| 9 | Cuentas analíticas de explotación (costos por función) | Orden/Costos | Mixto | Interno / Costeo |
2.1 Ejemplos de cuentas representativas (divisionarias)
| Código | Denominación | Clase | Tipo | Uso típico en ConTodo |
|---|---|---|---|---|
| 10 | Efectivo y equivalentes de efectivo | 1 | Activo | Cabecera tesorería |
| 104.1 | Cuentas corrientes operativas BCP MN | 1 | Activo | Conciliación bancaria |
| 105.1 | Cuentas corrientes operativas USD | 1 | Activo | Multimoneda |
| 12 | Cuentas por cobrar comerciales – terceros | 1 | Activo | Submódulo CxC |
| 121.1 | Facturas por cobrar | 1 | Activo | Venta a crédito |
| 1212 | Detracciones por cobrar | 1 | Activo | SPOT SUNAT |
| 19 | Estimación de cuentas de cobranza dudosa | 1 | Activo (regularizadora) | Provisión incobrables |
| 20 | Mercaderías | 2 | Activo | Inventario reventa |
| 21 | Productos terminados | 2 | Activo | Producción textil |
| 24 | Materias primas | 2 | Activo | Hilados, telas |
| 33 | Inmuebles, maquinaria y equipo | 3 | Activo | Activo fijo |
| 39 | Depreciación acumulada | 3 | Activo (regularizadora) | Depreciación |
| 40 | Tributos por pagar | 4 | Pasivo | IGV, Renta, ESSALUD |
| 4011 | IGV – Cuenta propia | 4 | Pasivo | Débito/crédito fiscal |
| 42 | Cuentas por pagar comerciales – terceros | 4 | Pasivo | Submódulo CxP |
| 41 | Remuneraciones y participaciones por pagar | 4 | Pasivo | Planilla |
| 45 | Obligaciones financieras | 4 | Pasivo | Préstamos/leasing |
| 50 | Capital | 5 | Patrimonio | Aportes |
| 59 | Resultados acumulados | 5 | Patrimonio | Cierre |
| 60 | Compras | 6 | Gasto | Compra mercadería |
| 62 | Gastos de personal, directores y gerentes | 6 | Gasto | Planilla |
| 63 | Gastos de servicios prestados por terceros | 6 | Gasto | Fletes, servicios |
| 68 | Valuación y deterioro de activos y provisiones | 6 | Gasto | Depreciación, deterioro |
| 69 | Costo de ventas | 6 | Gasto | COGS |
| 70 | Ventas | 7 | Ingreso | Facturación |
| 75 | Otros ingresos de gestión | 7 | Ingreso | Diferencia de cambio, etc. |
| 77 | Ingresos financieros | 7 | Ingreso | Intereses ganados |
2.2 Modelo de datos del catálogo
CREATE TABLE chart_of_accounts (
id BIGSERIAL PRIMARY KEY,
company_id BIGINT NOT NULL REFERENCES companies(id),
code VARCHAR(12) NOT NULL, -- '104.1'
name VARCHAR(200) NOT NULL,
class_id SMALLINT NOT NULL, -- 1..9
account_type VARCHAR(20) NOT NULL, -- asset|liability|equity|income|expense|order
normal_side CHAR(1) NOT NULL, -- 'D' | 'H'
is_postable BOOLEAN NOT NULL DEFAULT false, -- solo hojas reciben asientos
is_system BOOLEAN NOT NULL DEFAULT false,
parent_id BIGINT REFERENCES chart_of_accounts(id),
currency VARCHAR(3), -- NULL = multimoneda
requires_aux BOOLEAN NOT NULL DEFAULT false, -- exige auxiliar (RUC) p.ej. 12, 42
fs_caption VARCHAR(60), -- mapeo a rubro de EEFF
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (company_id, code)
);
Regla clave: solo cuentas con is_postable = true (hojas del árbol) reciben movimientos. Las cuentas cabecera (10, 12, 42) son agregadoras de reporte.
3. Libro Diario y Libro Mayor (modelo de asiento)
El núcleo es el par journal_entries (cabecera) + journal_entry_lines (detalle). Todo evento contable, sin importar su origen, produce un asiento.
CREATE TABLE journal_entries (
id BIGSERIAL PRIMARY KEY,
company_id BIGINT NOT NULL,
branch_id BIGINT, -- multisucursal
entry_number VARCHAR(20) NOT NULL, -- correlativo por libro/periodo
entry_date DATE NOT NULL,
period CHAR(6) NOT NULL, -- '202606' (YYYYMM)
book_type VARCHAR(20) NOT NULL, -- diario|compras|ventas|caja
source_type VARCHAR(30), -- Invoice|Purchase|Payroll|Manual
source_id BIGINT, -- polimórfico al doc origen
description TEXT,
currency VARCHAR(3) NOT NULL DEFAULT 'PEN',
exchange_rate NUMERIC(12,6) NOT NULL DEFAULT 1,
status VARCHAR(12) NOT NULL DEFAULT 'draft', -- draft|posted|voided
posted_at TIMESTAMPTZ,
posted_by BIGINT,
UNIQUE (company_id, book_type, period, entry_number)
);
CREATE TABLE journal_entry_lines (
id BIGSERIAL PRIMARY KEY,
journal_entry_id BIGINT NOT NULL REFERENCES journal_entries(id),
account_id BIGINT NOT NULL REFERENCES chart_of_accounts(id),
debit_fc NUMERIC(16,2) NOT NULL DEFAULT 0, -- funcional (PEN)
credit_fc NUMERIC(16,2) NOT NULL DEFAULT 0,
debit_oc NUMERIC(16,2), -- moneda origen
credit_oc NUMERIC(16,2),
cost_center_id BIGINT,
aux_party_id BIGINT, -- cliente/proveedor (RUC)
line_description TEXT
);
Constraint de cuadre (defensa en profundidad):
-- Trigger AFTER INSERT/UPDATE: por journal_entry_id, suma debe = haber en FC
CREATE FUNCTION assert_balanced() RETURNS trigger AS $$
DECLARE d NUMERIC; c NUMERIC;
BEGIN
SELECT SUM(debit_fc), SUM(credit_fc) INTO d, c
FROM journal_entry_lines WHERE journal_entry_id = NEW.journal_entry_id;
IF round(d,2) <> round(c,2) THEN
RAISE EXCEPTION 'Asiento descuadrado: debe=% haber=%', d, c;
END IF;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
4. Motor de asientos automáticos (Accounting Templates)
En lugar de cablear lógica contable en cada módulo, ConTodo usa plantillas de asiento parametrizables por tipo de transacción y empresa. Cada plantilla describe qué cuentas mover y con qué fórmula. Esto desacopla la operación de la contabilidad y permite que un contador (no un dev) ajuste mapeos.
| Plantilla | Disparador (evento) | Líneas generadas (debe / haber) |
|---|---|---|
SALE_INVOICE_CREDIT | Venta a crédito emitida | D 12 Clientes / H 70 Ventas, H 4011 IGV |
SALE_COGS | Salida de inventario por venta | D 69 Costo de ventas / H 20/21 Existencias |
PURCHASE_INVOICE | Factura de compra (mercadería) | D 60 Compras, D 4011 IGV crédito / H 42 Proveedores |
PURCHASE_TO_INV | Destino de la compra a almacén | D 20/24 Existencias / H 61 Variación de existencias |
CUSTOMER_PAYMENT | Cobro de cliente | D 10 Caja/Bancos / H 12 Clientes |
SUPPLIER_PAYMENT | Pago a proveedor | D 42 Proveedores / H 10 Caja/Bancos |
DETRACTION | Detracción retenida en cobro | D 1212 Detracciones / H 12 Clientes (parcial) |
PAYROLL_ACCRUAL | Cierre de planilla | D 62 Gastos personal / H 41 Remun., H 40 Tributos, H 41 AFP |
DEPRECIATION | Corrida mensual de activos | D 681 Depreciación / H 39 Deprec. acumulada |
FX_DIFFERENCE | Revaluación de partidas ME | D/H 67/77 Dif. de cambio / H/D 12/42/10 |
4.1 Estructura de la plantilla
# AccountingTemplate (parametrizable por company_id)
{
code: "SALE_INVOICE_CREDIT",
lines: [
{ account_resolver: "customer_ar_account", side: :debit, formula: "total_with_tax" },
{ account_resolver: "sales_revenue", side: :credit, formula: "subtotal" },
{ account_resolver: "vat_payable", side: :credit, formula: "igv_amount" }
]
}
Los account_resolver consultan reglas (por línea de producto, almacén, tipo de operación) para elegir la cuenta divisionaria correcta. El formula opera sobre el contexto del documento origen.
4.2 Ejemplo numérico — venta a crédito con IGV 18% y detracción 12% (servicio)
Venta de S/ 10,000 + IGV. Total S/ 11,800. Detracción 12% sobre total = S/ 1,416 retenida por el cliente y depositada al Banco de la Nación.
Asiento de venta (devengo):
| Cuenta | Debe | Haber |
|---|---|---|
| 12 Clientes | 11,800.00 | |
| 70 Ventas | 10,000.00 | |
| 4011 IGV por pagar | 1,800.00 |
Asiento de cobro con detracción:
| Cuenta | Debe | Haber |
|---|---|---|
| 104 Bancos (neto recibido) | 10,384.00 | |
| 1212 Detracciones por cobrar | 1,416.00 | |
| 12 Clientes | 11,800.00 |
5. Diagrama del flujo de asientos
6. Cuentas por cobrar (CxC) y por pagar (CxP)
Ambos submódulos son subledgers sincronizados con el mayor (cuentas 12 y 42). Cada documento (factura, NC, ND, anticipo) genera partidas abiertas (open items) que se aplican contra cobros/pagos.
| Característica | CxC (cuenta 12) | CxP (cuenta 42) |
|---|---|---|
| Auxiliar obligatorio | Cliente (RUC) | Proveedor (RUC) |
| Aging buckets | 0-30 / 31-60 / 61-90 / +90 | igual |
| Provisión | 19 Cobranza dudosa (NIIF 9 — pérdida esperada) | — |
| Retenciones | Detracción (1212), retención IGV | Detracción a depositar, retención 4ta cat. |
| Reportes | Estado de cuenta, antigüedad de saldos, DSO | Antigüedad, programación de pagos, DPO |
Conciliación subledger ↔ mayor: una tarea Sidekiq nocturna valida que Σ saldos abiertos CxC = saldo cuenta 12 y levanta alerta si difieren (control SOX-like).
7. Activos fijos y depreciación
CREATE TABLE fixed_assets (
id, company_id, asset_code, name, account_id, -- 33x
acquisition_date, acquisition_cost, residual_value,
useful_life_months, method, -- straight_line|declining
depreciation_account_id, -- 681
accumulated_account_id, -- 39x
status -- active|disposed|fully_depreciated
);
| Categoría | Vida útil (tributaria SUNAT) | Tasa anual máx. |
|---|---|---|
| Edificios y construcciones | 33 años | 3% |
| Maquinaria y equipo (industria textil) | 10 años | 10% |
| Vehículos de transporte | 5 años | 20% |
| Equipos de cómputo | 4 años | 25% |
| Muebles y enseres | 10 años | 10% |
La corrida mensual (DepreciationRunJob) genera un asiento DEPRECIATION por categoría/centro de costo. Se distingue depreciación contable (NIIF, vida útil real) de la tributaria (tasas SUNAT), generando diferencias temporales que alimentan el impuesto diferido (cuenta 37/49).
8. Tesorería
- Conciliación bancaria automática: importación de extractos (formato banco / Norma 43) y matching por monto+fecha+referencia contra movimientos de la cuenta 104/105.
- Posición de caja consolidada multimoneda y multiempresa con proyección de flujos (CxC + CxP programadas).
- Medios de pago Perú: Yape/Plin, transferencias interbancarias (CCI), detracciones (cuenta BN), cheques.
- Cada movimiento de tesorería confirmado dispara
CUSTOMER_PAYMENToSUPPLIER_PAYMENT.
9. Estados financieros (NIIF)
Los EEFF se construyen mapeando el balance de comprobación a rubros (fs_caption). ConTodo genera los cuatro estados obligatorios:
| Estado | Norma | Método en ConTodo |
|---|---|---|
| Estado de Situación Financiera | NIC 1 | Saldos clases 1-5 a la fecha, clasificados corriente/no corriente |
| Estado de Resultados | NIC 1 | Clases 6-7 del periodo; presentación por función (usa clase 9) o naturaleza |
| Estado de Flujos de Efectivo | NIC 7 | Método indirecto: utilidad + ajustes no monetarios ± variaciones capital de trabajo |
| Estado de Cambios en el Patrimonio | NIC 1 | Movimientos de clase 5 (capital, reservas, resultados) |
9.1 Cierre contable
Proceso mensual/anual: (1) corridas automáticas (depreciación, dif. cambio, provisiones), (2) balance de comprobación, (3) ajustes manuales, (4) asiento de destino de resultados (clase 8 → 59), (5) bloqueo del periodo (period_locked), (6) generación de PLE/SIRE.
10. Cumplimiento SUNAT
| Obligación | Cómo lo resuelve ConTodo |
|---|---|
| PLE (Programa de Libros Electrónicos) | Exportador de Libro Diario, Mayor, Registro de Compras y Ventas en formato TXT con estructura/encabezados oficiales |
| SIRE (Sistema Integrado de Registros Electrónicos) | Generación de propuestas RVIE/RCE y conciliación con comprobantes |
| PCGE 2019 | Catálogo base obligatorio, mapeo a casillas |
| Detracciones (SPOT) | Cálculo automático por tipo de bien/servicio y cuenta puente 1212 |
| Tipo de cambio SBS | Job que ingesta TC compra/venta diario para multimoneda y ajuste de cierre |
11. Riesgos y oportunidades
Riesgos
| ID | Riesgo | Impacto | Mitigación |
|---|---|---|---|
| R1 | Cambios normativos SUNAT (estructura PLE/SIRE) | Alto | Capa de exportadores versionada y desacoplada; tests de formato contra esquemas oficiales |
| R2 | Descuadre por concurrencia en correlativos de asiento | Alto | Secuencias por periodo con lock advisory de PostgreSQL; nunca correlativos en aplicación |
| R3 | Fuga de datos entre tenants en el mayor | Crítico | RLS a nivel de motor + company_id en todo índice |
| R4 | Diferencias entre depreciación contable y tributaria mal gestionadas | Medio | Modelo de impuesto diferido explícito (NIC 12), doble libro de activos |
| R5 | Rendimiento de reportes sobre millones de líneas | Medio | Tablas de saldos agregados (account_balances por periodo) actualizadas por trigger/job, no SUM en caliente |
| R6 | Errores de mapeo en plantillas por empresa | Medio | Plantillas con versión, simulación (dry-run) antes de activar, auditoría de cambios |
Oportunidades
| ID | Oportunidad | Valor |
|---|---|---|
| O1 | Asientos sugeridos por IA: clasificación automática de gastos y predicción de cuenta a partir del histórico | Reduce trabajo del contador, diferenciador vs. CONCAR/SISCONT |
| O2 | Cierre contable asistido: checklist guiado + detección de anomalías (saldos atípicos, partidas antiguas) | Acelera cierre de 5 días a horas |
| O3 | Conciliación bancaria con ML de matching difuso | Reduce trabajo manual de tesorería |
| O4 | EEFF en tiempo real (drill-down de rubro → cuenta → asiento → documento) | Ventaja BI frente a Defontana/StarSoft |
| O5 | Multi-GAAP (NIIF + tributario peruano simultáneo) con un solo set de transacciones | Habilita medianas empresas y filiales de multinacionales |
12. Alternativas de arquitectura evaluadas
| Decisión | Opción elegida | Alternativa descartada | Razón |
|---|---|---|---|
| Generación de asientos | Plantillas parametrizables en datos | Lógica contable hardcodeada por módulo | Flexibilidad por empresa, mantenibilidad, configurable por contador |
| Cuadre de partida doble | Constraint + trigger en BD | Validación solo en app | Defensa en profundidad; ningún path puede saltarse la regla |
| Saldos para reportes | Tabla agregada account_balances | SUM on-the-fly sobre el mayor | Rendimiento a escala SaaS |
| Aislamiento tenant | RLS de PostgreSQL | Filtro solo por scope de ActiveRecord | Seguridad a nivel de motor, no confiar solo en el ORM |
| Multimoneda | Monto en origen + funcional por línea | Conversión al vuelo en reportes | Cumple NIC 21 y evita re-cálculos inconsistentes |
13. Conclusión
El módulo contable de ConTodo se cimienta en tres pilares: PCGE 2019 como catálogo base extensible, un motor de asientos automáticos basado en plantillas que desacopla la operación de la contabilidad, y un libro mayor con partida doble garantizada por la base de datos. Sobre ese núcleo se construyen CxC/CxP como subledgers conciliados, activos fijos con doble tratamiento (contable/tributario), tesorería con conciliación bancaria, y los cuatro estados financieros NIIF derivados del balance de comprobación. El cumplimiento SUNAT (PLE, SIRE, detracciones, multimoneda SBS) se aísla en una capa de exportadores versionada para resistir cambios normativos. Las oportunidades de IA (asientos sugeridos, cierre asistido, conciliación con ML) y los EEFF en tiempo real con drill-down constituyen el principal diferenciador frente a CONCAR, SISCONT, Defontana y StarSoft en el mercado peruano y LATAM.