# Plan: Módulo Contabilidad

> Estado: ✅ Implementado (Fases 1–7 completas) | Pendiente: validaciones en datos reales  
> Última actualización: 2026-04-19  
> Arquitectura: NestJS + Prisma + PostgreSQL (mismo backend `smartfactvoice-backend`)

---

## Resumen de decisiones

| Decisión | Valor |
|---|---|
| Ubicación | Módulo `contabilidad` en `smartfactvoice-backend/src/contabilidad` |
| Plan de cuentas | Pre-cargado (NIIF adaptado Paraguay, ~100 cuentas), personalizable por empresa |
| Monedas | Multi-moneda — asientos en moneda original + conversión a PYG (moneda funcional) |
| Períodos | Configurable: generación automática (12 meses) o manual. Cierre configurable: total o con período de ajuste de N días solo para admin |
| Integración ventas/compras | Automática al confirmar factura — asiento generado sin intervención del contador |
| Integración cobros/pagos | Automática al registrar cobro o pago |
| Anulaciones | Política de reversión: nunca se borra un asiento confirmado, se genera asiento espejo |
| Centros de costo | Sí — tabla propia, asignable por línea de asiento |
| Reportes v1 | Libro Diario, Libro Mayor, Balance de Comprobación, Estado de Resultados, Balance General |
| Permisos | Sistema existente (`@RequirePermission`) con permisos nuevos bajo módulo `CONTABILIDAD` |

---

## Permisos nuevos

```
CONTABILIDAD_PLAN_CUENTAS   — ver/crear/editar cuentas contables
CONTABILIDAD_ASIENTOS        — crear/confirmar/revertir asientos manuales
CONTABILIDAD_PERIODOS        — abrir/cerrar/bloquear períodos
CONTABILIDAD_REPORTES        — emitir libros y balances
CONTABILIDAD_CONFIG          — configurar parámetros contables (tipo de cambio, etc.)
```

---

## Modelo de datos — Tablas nuevas

### `cont_plan_cuentas`
```
id                  uuid PK
empresa_id          uuid FK empresas (null = cuenta maestra del sistema)
codigo              varchar(20)  -- ej: "1.1.1.01"
descripcion         varchar(200)
tipo                enum: ACTIVO | PASIVO | PATRIMONIO | INGRESO | COSTO | GASTO
naturaleza          enum: DEUDORA | ACREEDORA
nivel               int  -- profundidad en el árbol (1=grupo, 4=cuenta operativa)
cuenta_padre_id     uuid FK self (nullable)
acepta_movimientos  boolean  -- solo las hojas del árbol aceptan asientos
acepta_cc           boolean  -- si exige centro de costo obligatorio
moneda_fija         varchar(3) nullable  -- si la cuenta opera en moneda extranjera fija
active              boolean default true
is_sistema          boolean  -- cuentas del seed que no se pueden eliminar
created_at          timestamp
updated_at          timestamp
```

### `cont_ejercicios`
```
id                  uuid PK
empresa_id          uuid FK
año                 int
fecha_inicio        date
fecha_fin           date
estado              enum: ABIERTO | CERRADO | BLOQUEADO
created_at          timestamp
```

### `cont_periodos`
```
id                  uuid PK
ejercicio_id        uuid FK
empresa_id          uuid FK
numero              int  -- 1..12
mes                 int
año                 int
fecha_inicio        date
fecha_fin           date
estado              enum: ABIERTO | CERRADO | AJUSTE | BLOQUEADO
-- AJUSTE = período de gracia post-cierre, solo admin puede asentar
dias_ajuste         int nullable  -- configurado al cerrar
fecha_cierre        timestamp nullable
created_at          timestamp
```

### `cont_centros_costo`
```
id                  uuid PK
empresa_id          uuid FK
codigo              varchar(20)
descripcion         varchar(200)
activo              boolean default true
created_at          timestamp
```

### `cont_tipo_cambio`
```
id                  uuid PK
empresa_id          uuid FK
moneda              varchar(3)  -- USD, BRL, etc.
fecha               date
tasa                decimal(18,6)  -- unidades de PYG por 1 unidad de moneda
created_at          timestamp
-- unique: empresa_id + moneda + fecha
```

### `cont_documentos`
```
id                  uuid PK
empresa_id          uuid FK
tipo                enum: FACTURA_VENTA | FACTURA_COMPRA | COBRO | PAGO | NOTA_CREDITO | NOTA_DEBITO | AJUSTE | APERTURA | CIERRE
origen_tipo         varchar(50) nullable  -- 'facturas' | 'compra_cab' | 'recibos_cobro' | etc.
origen_id           uuid nullable  -- ID del registro origen
numero_documento    varchar(50)  -- número contable interno
estado              enum: BORRADOR | CONFIRMADO | REVERTIDO
asiento_id          uuid nullable FK cont_asientos
reversion_de_id     uuid nullable FK self  -- si es reversión de otro doc
created_at          timestamp
```

### `cont_asientos`
```
id                  uuid PK
empresa_id          uuid FK
periodo_id          uuid FK cont_periodos
documento_id        uuid FK cont_documentos
numero              int  -- correlativo por empresa+periodo
fecha               date
glosa               varchar(500)
estado              enum: BORRADOR | CONFIRMADO | REVERTIDO
moneda_origen       varchar(3)  -- moneda del documento origen
tipo_cambio_id      uuid nullable FK cont_tipo_cambio
total_debe_pyg      decimal(18,2)  -- validación: debe = haber siempre
total_haber_pyg     decimal(18,2)
usuario_id          uuid FK usuarios
created_at          timestamp
updated_at          timestamp
```

### `cont_asientos_det`
```
id                  uuid PK
asiento_id          uuid FK
cuenta_id           uuid FK cont_plan_cuentas
centro_costo_id     uuid nullable FK cont_centros_costo
descripcion         varchar(300)
debe_moneda         decimal(18,2) default 0  -- en moneda_origen del asiento
haber_moneda        decimal(18,2) default 0
debe_pyg            decimal(18,2) default 0  -- convertido a PYG
haber_pyg           decimal(18,2) default 0
orden               int
```

---

## Plan de cuentas — Seed Paraguay (estructura base)

```
1        ACTIVO
1.1      Activo Corriente
1.1.1    Caja y Bancos
1.1.1.01   Caja General
1.1.1.02   Caja Chica
1.1.1.03   Banco (cuenta corriente PYG)
1.1.1.04   Banco (cuenta corriente USD)
1.1.2    Cuentas a Cobrar
1.1.2.01   Clientes
1.1.2.02   Documentos a Cobrar
1.1.2.03   Anticipo a Proveedores
1.1.3    IVA Crédito Fiscal
1.1.3.01   IVA Crédito 10%
1.1.3.02   IVA Crédito 5%
1.1.4    Inventarios
1.1.4.01   Mercaderías
1.1.4.02   Productos Terminados
1.1.4.03   Materias Primas
1.2      Activo No Corriente
1.2.1    Activo Fijo
1.2.1.01   Muebles y Útiles
1.2.1.02   Equipos de Computación
1.2.1.03   Vehículos
1.2.1.04   (-) Depreciación Acumulada

2        PASIVO
2.1      Pasivo Corriente
2.1.1    Cuentas a Pagar
2.1.1.01   Proveedores
2.1.1.02   Documentos a Pagar
2.1.2    IVA Débito Fiscal
2.1.2.01   IVA Débito 10%
2.1.2.02   IVA Débito 5%
2.1.3    Retenciones y Aportes
2.1.3.01   IPS a Pagar
2.1.3.02   Retenciones de IRPC a Pagar
2.1.4    Préstamos Bancarios Corto Plazo
2.2      Pasivo No Corriente
2.2.1    Préstamos Bancarios Largo Plazo

3        PATRIMONIO NETO
3.1      Capital
3.1.1.01   Capital Integrado
3.2      Resultados
3.2.1.01   Resultado del Ejercicio
3.2.1.02   Resultados Acumulados

4        INGRESOS
4.1      Ingresos Operacionales
4.1.1.01   Ventas Gravadas 10%
4.1.1.02   Ventas Gravadas 5%
4.1.1.03   Ventas Exentas
4.2      Otros Ingresos
4.2.1.01   Intereses Ganados
4.2.1.02   Diferencia de Cambio Ganada

5        COSTOS
5.1      Costo de Ventas
5.1.1.01   Costo de Mercaderías Vendidas

6        GASTOS
6.1      Gastos Administrativos
6.1.1.01   Sueldos y Salarios
6.1.1.02   Cargas Sociales (IPS)
6.1.1.03   Alquileres
6.1.1.04   Servicios Públicos
6.1.1.05   Papelería y Útiles
6.1.1.06   Depreciaciones
6.2      Gastos Comerciales
6.2.1.01   Comisiones de Vendedores
6.2.1.02   Publicidad y Marketing
6.2.1.03   Fletes y Transporte
6.3      Gastos Financieros
6.3.1.01   Intereses Bancarios
6.3.1.02   Comisiones Bancarias
6.3.1.03   Diferencia de Cambio Perdida
```

---

## Integración contable automática

### Cuentas de mapeo por empresa (`cont_mapeo_cuentas`)

Cada empresa debe configurar qué cuenta contable corresponde a cada concepto del sistema. Se precarga con los defaults del seed.

```
empresa_id    uuid
concepto      enum  (ver lista abajo)
cuenta_id     uuid FK cont_plan_cuentas
```

**Conceptos mapeables:**
```
CLIENTES                  → 1.1.2.01
PROVEEDORES               → 2.1.1.01
VENTAS_10                 → 4.1.1.01
VENTAS_5                  → 4.1.1.02
VENTAS_EXENTAS            → 4.1.1.03
IVA_DEBITO_10             → 2.1.2.01
IVA_DEBITO_5              → 2.1.2.02
IVA_CREDITO_10            → 1.1.3.01
IVA_CREDITO_5             → 1.1.3.02
COSTO_VENTAS              → 5.1.1.01
INVENTARIO                → 1.1.4.01
CAJA_GENERAL              → 1.1.1.01
BANCO_PYG                 → 1.1.1.03
BANCO_USD                 → 1.1.1.04
```

---

### Asiento automático — Factura de Venta

**Evento:** `facturas.confirmada`  
**Servicio:** `ContabilidadIntegracionService.integrarFacturaVenta(facturaId)`

```
DEBE:
  Clientes (1.1.2.01)    → total factura

HABER:
  Ventas 10% (4.1.1.01)  → base imponible 10%
  Ventas 5%  (4.1.1.02)  → base imponible 5%
  Ventas exentas (4.1.1.03) → monto exento
  IVA Débito 10% (2.1.2.01) → IVA 10%
  IVA Débito 5%  (2.1.2.02) → IVA 5%
```

**Glosa:** `"Factura venta N° {numero_factura} - {nombre_cliente}"`

---

### Asiento automático — Factura de Compra

**Evento:** `compras.confirmada`  
**Servicio:** `ContabilidadIntegracionService.integrarFacturaCompra(compraId)`

```
DEBE:
  Inventario (1.1.4.01)       → subtotal s/IVA (o cuenta gasto según tipo)
  IVA Crédito 10% (1.1.3.01) → IVA 10%
  IVA Crédito 5%  (1.1.3.02) → IVA 5%

HABER:
  Proveedores (2.1.1.01)      → total compra
```

**Glosa:** `"Factura compra N° {numero} - {nombre_proveedor}"`

---

### Asiento automático — Cobro

**Evento:** `cobros.registrado`  
**Servicio:** `ContabilidadIntegracionService.integrarCobro(cobroId)`

```
DEBE:
  Caja/Banco según medio de pago  → monto cobrado

HABER:
  Clientes (1.1.2.01)             → monto cobrado
```

---

### Asiento automático — Pago a Proveedor

**Evento:** `pagos.registrado`  
**Servicio:** `ContabilidadIntegracionService.integrarPago(pagoId)`

```
DEBE:
  Proveedores (2.1.1.01)    → monto pagado

HABER:
  Caja/Banco según instrumento  → monto pagado
```

---

### Asiento de reversión — Anulación

Al anular cualquier documento ya integrado:

```
ContabilidadIntegracionService.revertirDocumento(documentoId)
→ Crear cont_documento con tipo=ANULACION, reversion_de_id = doc original
→ Crear asiento espejo: cada línea con DEBE←→HABER invertidos
→ Glosa: "REVERSIÓN: {glosa original}"
→ El asiento original queda estado=REVERTIDO (no se toca)
```

---

## Reglas de validación (se aplican antes de confirmar asiento)

1. **Partida doble**: `SUM(debe_pyg) = SUM(haber_pyg)` — si no, rechazar con 400
2. **Período abierto**: el período debe estar en estado `ABIERTO` o `AJUSTE` (AJUSTE solo para admins)
3. **Idempotencia**: `cont_documentos.origen_id` + `origen_tipo` con índice único — si ya existe, no generar dos veces
4. **Centro de costo obligatorio**: si `cuenta.acepta_cc = true`, la línea debe tener `centro_costo_id`
5. **Cuenta acepta movimientos**: solo cuentas hoja (`acepta_movimientos = true`) en el detalle
6. **No eliminar confirmados**: los asientos confirmados solo se pueden revertir, nunca DELETE

---

## Estructura del módulo NestJS

```
src/contabilidad/
├── contabilidad.module.ts
├── controllers/
│   ├── plan-cuentas.controller.ts
│   ├── ejercicios.controller.ts
│   ├── periodos.controller.ts
│   ├── asientos.controller.ts
│   ├── centros-costo.controller.ts
│   ├── tipo-cambio.controller.ts
│   ├── mapeo-cuentas.controller.ts
│   └── reportes.controller.ts
├── services/
│   ├── plan-cuentas.service.ts
│   ├── ejercicios.service.ts
│   ├── periodos.service.ts
│   ├── asientos.service.ts
│   ├── centros-costo.service.ts
│   ├── tipo-cambio.service.ts
│   ├── mapeo-cuentas.service.ts
│   ├── integracion.service.ts       ← motor de asientos automáticos
│   └── reportes.service.ts
├── dto/
│   └── ...
└── seeds/
    └── plan-cuentas-paraguay.seed.ts
```

---

## API Endpoints

### Plan de Cuentas
```
GET    /contabilidad/plan-cuentas              → árbol completo
POST   /contabilidad/plan-cuentas              → crear cuenta
PATCH  /contabilidad/plan-cuentas/:id          → editar
DELETE /contabilidad/plan-cuentas/:id          → eliminar (solo si sin movimientos)
POST   /contabilidad/plan-cuentas/seed         → cargar plan base Paraguay
```

### Ejercicios y Períodos
```
GET    /contabilidad/ejercicios                → listar
POST   /contabilidad/ejercicios                → crear ejercicio (genera 12 períodos si config=auto)
PATCH  /contabilidad/ejercicios/:id/cerrar     → cerrar ejercicio
GET    /contabilidad/periodos                  → listar con filtro ejercicio_id
PATCH  /contabilidad/periodos/:id/cerrar       → cerrar período (body: dias_ajuste?)
PATCH  /contabilidad/periodos/:id/reabrir      → reabrir (admin)
```

### Centros de Costo
```
GET    /contabilidad/centros-costo
POST   /contabilidad/centros-costo
PATCH  /contabilidad/centros-costo/:id
DELETE /contabilidad/centros-costo/:id
```

### Tipo de Cambio
```
GET    /contabilidad/tipo-cambio               → ?moneda=USD&fecha=2026-04-18
POST   /contabilidad/tipo-cambio               → registrar tasa del día
```

### Mapeo de Cuentas
```
GET    /contabilidad/mapeo-cuentas
PUT    /contabilidad/mapeo-cuentas             → bulk update (body: [{concepto, cuenta_id}])
```

### Asientos (manuales)
```
GET    /contabilidad/asientos                  → ?periodoId=&estado=
POST   /contabilidad/asientos                  → crear en borrador
PATCH  /contabilidad/asientos/:id/confirmar    → confirmar (valida partida doble)
POST   /contabilidad/asientos/:id/revertir     → genera asiento espejo
```

### Reportes
```
GET    /contabilidad/reportes/libro-diario     → ?periodoId=&formato=json|pdf|excel
GET    /contabilidad/reportes/libro-mayor      → ?cuentaId=&desde=&hasta=
GET    /contabilidad/reportes/balance-comprobacion → ?periodoId=
GET    /contabilidad/reportes/estado-resultados    → ?ejercicioId= o ?desde=&hasta=
GET    /contabilidad/reportes/balance-general      → ?fecha=
```

---

## Fases de implementación

### ✅ Fase 1 — Base de datos y seed
- Migraciones: todas las tablas `cont_*`
- Seed del plan de cuentas Paraguay (~100 cuentas)
- Seed de mapeo de cuentas default

### ✅ Fase 2 — Plan de cuentas, ejercicios y períodos
- CRUD plan de cuentas (árbol, validaciones)
- CRUD ejercicios (con generación automática de períodos)
- CRUD períodos (apertura/cierre/bloqueo configurable)
- Centros de costo
- Tipo de cambio

### ✅ Fase 3 — Asientos manuales y validaciones
- Crear asientos en borrador
- Confirmar con validación de partida doble
- Validación de período abierto
- Reversión de asientos
- Idempotencia

### ✅ Fase 4 — Integración automática
- `ContabilidadIntegracionService` con los 4 métodos: `integrarFacturaVenta`, `integrarFacturaCompra`, `integrarCobro`, `integrarPago`
- Hook en servicios existentes de ventas, compras y cobros
- Reversión al anular documentos

### ✅ Fase 5 — Reportes contables
- Libro Diario, Libro Mayor, Balance de Comprobación
- Estado de Resultados, Balance General
- Exportación PDF y Excel
- Filtros por ejercicio, período, rango de fechas y centro de costo

### ✅ Fase 6 — Frontend
- Tab "Contabilidad" en la aplicación (página `Contabilidad.jsx`)
- Tabs: Plan de Cuentas, Ejercicios, Asientos, Mapeo de Cuentas, Centros de Costo, Tipo de Cambio, Reportes
- Banner de onboarding (sin plan de cuentas / sin período abierto)
- Visor de reportes con exportación PDF/Excel
- Libro Mayor con selector de cuenta (Autocomplete)

---

## Fase 7 — Reportes Fiscales e Impuestos (en progreso)

Reportes basados en datos SIFEN (factura_cab / compra_cab), independientes del módulo contable.
Ubicación: `Reportes → Fiscal e Impuestos`

| Reporte | Backend | Frontend | Estado |
|---|---|---|---|
| Libro IVA Ventas | `facturas.service.ts` → `getLibroIvaVentas` | `ReporteLibroIVA.jsx` | ✅ |
| Libro IVA Compras | `compras.service.ts` → `getLibroIvaCompras` | `ReporteLibroIVACompras.jsx` | ✅ |
| Liquidación IVA | `facturas.service.ts` → `getLiquidacionIva` | `ReporteLiquidacionIVA.jsx` | ✅ |

### Notas técnicas Libro IVA
- Filtrar por `estado_sifen: EstadoFactura.APROBADO` (no por `estado` app-level)
- Número en formato SET: `dest-dpunexp-dnumdoc` → `001-001-0000001`
- Incluir CDC (44 chars) para trazabilidad SIFEN
- Notas de crédito restan del débito fiscal en el mismo período
- Notas de débito: no aplica (empresa no las emite)

---

## Archivos a crear/modificar

| Archivo | Tipo |
|---|---|
| `prisma/migrations/YYYYMMDD_contabilidad_tablas/migration.sql` | Nueva migración |
| `src/contabilidad/` (estructura completa) | Módulo nuevo |
| `src/facturas/facturas.service.ts` | Hook integración al confirmar |
| `src/compras/compras.service.ts` | Hook integración al confirmar |
| `src/cobros/cobros.service.ts` | Hook integración al registrar cobro |
| `src/pagos-proveedor/pagos-proveedor.service.ts` | Hook integración al registrar pago |
| `src/app.module.ts` | Registrar ContabilidadModule |

---

## Checklist de pruebas obligatorias

- [ ] Confirmar factura de venta → genera documento + asiento balanceado + IVA correcto
- [ ] Confirmar factura de compra → genera documento + asiento + IVA crédito
- [ ] Registrar cobro → genera asiento Caja/Banco vs Clientes
- [ ] Registrar pago → genera asiento Proveedores vs Caja/Banco
- [ ] Intentar asiento desbalanceado → 400 error
- [ ] Intentar asiento en período cerrado → 400 error
- [ ] Anular factura ya contabilizada → genera reversión, original queda REVERTIDO
- [ ] Libro Diario del mes → totales correctos
- [ ] Balance de Comprobación → total Debe = total Haber
- [ ] Idempotencia: doble integración del mismo documento → no crea dos asientos

---

## Prerrequisitos por empresa (Helpers de UI en Fase 6)

Para que la contabilidad funcione, **cada empresa debe tener**:

### 1. Plan de cuentas cargado
- Endpoint: `POST /contabilidad/plan-cuentas/seed`
- **UI**: En la pantalla de Contabilidad, si `GET /contabilidad/plan-cuentas` devuelve lista vacía, mostrar un **banner/alerta** prominente:
  > ⚠️ **Esta empresa no tiene plan de cuentas configurado.**  
  > Para poder registrar asientos automáticos y manuales, primero debes cargar el plan de cuentas estándar.  
  > [Cargar Plan de Cuentas Estándar] ← botón que llama al endpoint seed

### 2. Ejercicio con períodos abiertos
- Endpoint check: `GET /contabilidad/periodos?estado=ABIERTO`
- **UI**: Si la respuesta está vacía, mostrar banner:
  > ⚠️ **No existe un período contable abierto para la fecha actual.**  
  > Las facturas y cobros no se contabilizarán hasta que crees un ejercicio y abras sus períodos.  
  > [Crear Ejercicio] ← lleva a la pantalla de Ejercicios

### Flujo sugerido de onboarding contable en el Frontend
```
1. Empresa nueva entra a Contabilidad
2. Sistema detecta: ¿tiene plan de cuentas? → NO → Banner A
3. Usuario carga seed → Banner A desaparece
4. Sistema detecta: ¿tiene período abierto hoy? → NO → Banner B
5. Usuario crea ejercicio YYYY con períodos auto → Banner B desaparece
6. Sistema contable operativo ✓
```

### Implementación de detección en React
```jsx
// Hook: useContabilidadStatus.js
const useContabilidadStatus = (empresaId) => {
  const { data: cuentas } = useQuery(['plan-cuentas', empresaId], () =>
    api.get('/contabilidad/plan-cuentas?lista=true')
  );
  const { data: periodoActivo } = useQuery(['periodo-activo', empresaId], () =>
    api.get('/contabilidad/periodos/activo')  // devuelve { periodoId } o null
  );
  return {
    sinPlanCuentas: cuentas?.length === 0,
    sinPeriodoAbierto: !periodoActivo?.periodoId,
  };
};
```

---

## Auditoría Contable

El sistema usa el módulo de auditoría existente (`AuditModule` global) para registrar todas las operaciones críticas. Las operaciones auditadas son:

| Operación | `entity_type` | `action` |
|---|---|---|
| Crear asiento manual | `cont_asientos` | `CREATE` |
| Confirmar asiento | `cont_asientos` | `CONFIRM` |
| Revertir asiento | `cont_asientos` | `REVERT` |
| Cerrar período | `cont_periodos` | `CLOSE` |
| Reabrir período | `cont_periodos` | `REOPEN` |
| Bloquear período | `cont_periodos` | `BLOCK` |
| Crear ejercicio | `cont_ejercicios` | `CREATE` |
| Cerrar ejercicio | `cont_ejercicios` | `CLOSE` |
| Crear cuenta | `cont_plan_cuentas` | `CREATE` |
| Editar cuenta | `cont_plan_cuentas` | `UPDATE` |
| Eliminar cuenta | `cont_plan_cuentas` | `DELETE` |

Los asientos generados automáticamente por integración (facturas, cobros, pagos) **no generan audit log propio** — el trazado de origen se hace via `cont_documentos.origen_tipo` + `cont_documentos.origen_id`.

Para consultar el historial de auditoría contable desde el frontend: `GET /audit?entity_type=cont_asientos`.
