Costeo Fiscal y Libro de Inventarios
Sub-proyecto 5 — Cumplimiento de obligaciones fiscales venezolanas para inventarios. Implementado: 2026-05-02. Marco legal: LISLR Art. 182, Código de Comercio Art. 35, VEN-NIIF para PYMES Sección 13, BA VEN-NIF 2 v4 (2025).
Marco legal aplicable
| Norma | Exigencia | Implementación ACE |
|---|---|---|
| LISLR Art. 182 (Decreto 5.770) | Métodos permitidos: costo promedio o PEPS (FIFO). UEPS prohibido. Cambios requieren autorización SENIAT y razones técnicas | costing_method ENUM por categoría + audit log de cambios con justificación obligatoria |
| Código de Comercio Art. 35 | Libro de Inventarios anual con descripción detallada de bienes al cierre | Tabla inventory_book_snapshots (cabecera append-only) + inventory_book_snapshot_lines (detalle ítem-por-ítem) |
| VEN-NIIF para PYMES Sec 13 §17 | Costo: PEPS o promedio ponderado. Reconocer al menor entre costo y VNR | Promedio ponderado en WeightedAverageCostService con FOR UPDATE. NRV en nrvService |
| BA VEN-NIF 2 v4 (2025) | Reexpresión INPC obligatoria en Venezuela (hiperinflacionaria) | inflationAdjustmentService.applyToInventories() con desglose por ítem |
Método de costeo (LISLR Art. 182)
Cada categoría de inventario lleva un único método. El método se elige al crear la categoría
(default WEIGHTED_AVERAGE) y debe aplicarse uniformemente. Cambiarlo exige:
- Justificación técnica documentada (mínimo 50 caracteres)
- Referencia a oficio SENIAT (opcional, recomendado)
- Audit log inmutable en
inventory_costing_method_changes
Métodos disponibles
WEIGHTED_AVERAGE(Costo Promedio Ponderado) — implementado enWeightedAverageCostServiceFIFO(PEPS) — placeholder; activación futura requiere implementar cost layers
Endpoints
GET /api/inventory/costing-methods # Lista por categoría
GET /api/inventory/costing-methods/categories/:id # Método de una categoría
GET /api/inventory/costing-methods/categories/:id/history # Audit log
GET /api/inventory/costing-methods/items/:id # Método derivado del ítem
POST /api/inventory/costing-methods/categories/:id/change # Cambiar (con justificación)
UI
Ruta: /inventory/costing-methods (permiso inventory:costing:read)
Navegación: Inventario → Métodos de Costeo
Libro de Inventarios (Código de Comercio Art. 35)
Snapshot anual append-only del estado de inventario al cierre fiscal. Estados:
DRAFT (creación) ──[close]──► CLOSED (immutable) ──[reopen + razón]──► REOPENED
Snapshot
La cabecera inventory_book_snapshots lleva:
fiscal_yearUNIQUE — un snapshot por añoclosing_date— default 31-dictotal_quantity,total_cost_value— totales agregadostotal_reexpressed_value+inpc_index_used— si se aplicó reexpresióncosting_summaryJSONB — desglose por categoría (método, qty, value, itemCount)
Líneas de detalle
Cada inventory_book_snapshot_lines captura inmutable:
- Almacén + categoría + método de costeo aplicado
- Ítem (código, nombre, UOM)
- Cantidad, costo unitario, costo total
- Reexpresión (si aplica):
reexpressed_unit_cost,reexpressed_total serial_count,lot_count— trazabilidad de unidades únicas
Cierre del Libro
Al cerrar (POST /:id/close) se puede aplicar reexpresión por inflación opcional:
- Lee
INPCde diciembre del año fiscal - Lee
INPCde diciembre del año anterior (base) - Calcula factor
INPC_cierre / INPC_base - Actualiza todas las líneas con
reexpressed_unit_cost = unit_cost * factor - Calcula
total_reexpressed_value
Reapertura
Sólo permiso inventory:book:reopen puede reabrir un libro CLOSED. Requiere razón obligatoria
(>=20 caracteres) que se persiste en reopen_reason. La reapertura queda como evento
de auditoría visible.
Endpoints
GET /api/inventory/book # Listar snapshots (filtros: year, status)
POST /api/inventory/book # Crear DRAFT
GET /api/inventory/book/:id # Cabecera + totales
GET /api/inventory/book/:id/lines # Detalle paginado (warehouseId, categoryId, page, limit)
POST /api/inventory/book/:id/close # DRAFT → CLOSED (con applyReexpression)
POST /api/inventory/book/:id/reopen # CLOSED → REOPENED (con reason)
GET /api/inventory/book/:id/export.csv # Descarga CSV formato Art. 35 CCom
UI
- Lista:
/inventory/book(permisoinventory:book:read) - Detalle:
/inventory/book/:id— stepper de estado, resumen por categoría, detalle ítem-a-ítem paginado, botones Cerrar / Reabrir / Exportar CSV
Reexpresión por inflación de inventarios (BA VEN-NIF 2)
Bug-fix crítico
Antes (sub-1..4): inflationAdjustmentService.isNonMonetary() clasificaba como inventarios las
cuentas con prefijo '1.1.0'. Pero en producción ACE las cuentas 1.1.0/1.1.1/1.1.2 son CAJAS y
BANCOS (monetarias). Si se hubiera aplicado REI, habría reexpresado caja erradamente.
Sub-5: la regla cambió a '1.1.5' (cuentas inventario reales: CRUDO, MATERIALES, EQUIPOS,
ARTÍCULOS) + soporte para subtype explícito 'INVENTORY'. Por suerte la tabla inpc_indices estaba
vacía en producción → el bug nunca afectó datos.
Trazabilidad ítem-a-ítem
Antes applyAdjustment() reexpresaba el saldo agregado de la cuenta inventario sin desglose por ítem.
Sub-5 añade inflationAdjustmentService.applyToInventories(adjustmentId):
- Lee INPC del período del ajuste y INPC base (período anterior)
- Calcula factor de reexpresión
- Actualiza cada
warehouse_stockconreexpressed_value = quantity * avg_cost * factor - Sella
last_inflation_adjustment_atylast_inflation_adjustment_id
Datos INPC
Se sembraron 6 índices placeholder (2025-12 .. 2026-05) marcados con notes 'Placeholder sub5-M7'.
El operador debe actualizar mensualmente con el índice oficial publicado por el BCV en Gaceta Oficial.
Permisos sub-5
| Código | Descripción | Asignado a roles |
|---|---|---|
inventory:costing:read | Ver método por categoría | Super Admin, Director Técnico, Director Financiero, Contador, Gerente Logística, Auditor |
inventory:costing:change | Cambiar método (con justificación) | Super Admin, Director Financiero, Contador |
inventory:book:read | Consultar Libro de Inventarios | Super Admin, Director Técnico, Director Financiero, Contador, Gerente Logística, Auditor |
inventory:book:close | Cerrar Libro anual | Super Admin, Director Financiero, Contador |
inventory:book:reopen | Reabrir Libro cerrado | Super Admin, Director Financiero, Contador |
Hallazgos del audit retrospectivo
Datos pre-sub5 en producción (auditoría 2026-05-02)
- 354 stocks totales; 215 sin avg_cost o avg_cost=0
- 115 stocks con qty>0 pero sin total_value
- 1.000 movimientos; 617 sin unit_cost (datos demo)
- Total inventario valorizado: USD 330.374,73
La migración M5 ejecuta backfill conservador: para cada (warehouse_id, item_id) con qty>0 sin valor,
calcula promedio histórico desde inventory_movements con unit_cost > 0. Sólo escribe si el cálculo
da resultado > 0; stocks sin historial valorizado quedan como están (visibles para corrección manual).
Migraciones aplicadas (sub-5)
| # | Archivo | Propósito |
|---|---|---|
| M1 | 20260502120001-sub5-inventory-costing-method | Enum + cols categorías + audit log |
| M2 | 20260502120002-sub5-warehouse-stocks-reexpression | Sello reexpresión por stock |
| M3 | 20260502120003-sub5-inventory-book-tables | Snapshots + lines |
| M4 | 20260502120004-sub5-inventory-book-permissions | 5 permisos + asignaciones a roles |
| M5 | 20260502120005-sub5-backfill-stock-costs | Recalcular avg_cost desde movimientos |
| M6 | (fix de código inflationAdjustmentService.js) | Prefijo 1.1.0 → 1.1.5 |
| M7 | 20260502120006-sub5-seed-inpc-baseline | 6 INPC placeholder (BCV) |
| M8 | 20260502120007-sub5-seed-costing-baseline | Asignar WEIGHTED_AVERAGE a categorías + log baseline |
Gap dimensional sub-1 cerrado
inventoryAccountingService ahora completa budget_position_id en cada journal_entry_line
mediante _enrichLinesWithBudgetPosition() que consulta account_budget_position_links con role
DEBIT_DEFAULT o CREDIT_DEFAULT. Esto cierra el gap dimensional dejado por sub-1 sin modificar
los 9 métodos individuales del servicio.
Capturas de producción (E2E Chrome MCP en https://erp-aceog.com)
Lista de 28 categorías con método "Costo Promedio" — captura tras deploy en producción.
Cambio LISLR Art. 182 con justificación obligatoria >=50ch. Botón Confirmar deshabilitado sin razón.
Audit log inicial registrado por M8: justificación de adopción WEIGHTED_AVERAGE.
Estado inicial sin snapshots. Botón "Nuevo Snapshot Anual" para empezar.
Snapshot 2026 con 201 ítems, USD 330.374,73 valor total, resumen por 12 categorías y detalle ítem-por-ítem.
Confirmación de cierre con flag de reexpresión por inflación opcional (BA VEN-NIF 2).
Estado tras cerrar: chip "Cerrado" en verde, botón cambia a "Reabrir Libro".
Snapshot año fiscal 2026 cerrado con totales finales.