# Plan: Módulo RRHH

**Fecha**: Mayo 2026 · **Revisado contra el código y producción**: Octubre 2026  
**Fuente funcional**: `Novasis_ERP_RRHH_Especificacion_v1.docx` (versión 1.0 — Abril 2026)  
**Estado**: en producción. Liquidación mensual/quincenal, IPS, anticipos, préstamos, desvinculaciones, comisiones y la integración contable están operativos y en uso real.

> **Nota de uso real (octubre 2026)**: acreditaciones bancarias, presentismo completo (relojes, marcaciones, turnos, permisos, tolerancias), vacaciones, planillas externas y documentos de legajo están **implementados pero sin uso en producción** — cero registros. Los pasos 5 a 7 del flujo de abajo nunca se ejecutaron contra datos reales; tomarlos como no probados en campo.

---

## Decisiones de diseño

| # | Decisión | Resolución |
|---|---|---|
| 1 | Alcance v1 | Implementar módulos M01–M15 priorizando liquidación mensual, IPS y desvinculación. |
| 2 | Motor de cálculo | Motor aislado de reglas RRHH (`rrhh-engine`) desacoplado de controllers para test unitarios. |
| 3 | Persistencia | PostgreSQL + Prisma con diseño multiempresa (`empresa_id` obligatorio en tablas operativas). |
| 4 | Parámetros legales | Porcentajes IPS, SMLV y límites legales parametrizados por vigencia (sin hardcode). |
| 5 | Inmutabilidad | Toda liquidación en estado `CERRADA` es inmutable; correcciones vía liquidación de ajuste. |
| 6 | Integración contable | La liquidación cerrada genera asiento de devengamiento; la acreditación confirmada genera egreso de tesorería y asiento de pago. |
| 7 | Importaciones externas | Comisiones/bonos/descuentos se cargan por Excel/CSV con trazabilidad por lote. |
| 8 | Acreditación bancaria | Generación de archivo por banco/formato; al confirmar registra pago en Tesorería si el módulo está activo. |
| 9 | Seguridad | RBAC + aislamiento estricto por empresa (RLS o enforcement a nivel servicio). |
| 10 | Estrategia de entrega | Fases incrementales con foco inicial en cálculo correcto y auditoría legal. |

---

## Flujo operativo vigente

Este flujo describe el comportamiento esperado de la implementación actual.

1. **Preparación contable autoservicio**
   - Ir a `Contabilidad > Mapeo de Cuentas`.
   - Ejecutar `Crear cuentas y mapeos faltantes`.
   - El sistema crea cuentas estándar faltantes y mapea conceptos de RRHH sin sobrescribir mapeos manuales.

2. **Alta de empleados**
   - RRHH carga empleados con sucursal, departamento, cargo, centro de costo contable, tipo, salario, banco y cuenta bancaria.
   - `regimen_ips` es la fuente de verdad para derivar si aporta IPS.

3. **Liquidación**
   - Se crea en `BORRADOR`, se calcula a `PRE_LIQUIDACION` y se cierra a `CERRADA`.
   - Una liquidación cerrada es inmutable.

4. **Asiento de devengamiento**
   - Al cerrar se genera automáticamente un documento contable con origen `rrhh_liquidacion` (tipo `LIQUIDACION_MENSUAL` o `LIQUIDACION_QUINCENAL_{n}`).
   - Debe: `SUELDOS_JORNALES`, `APORTE_PATRONAL_IPS`, `APORTE_ADMIN_IPS`.
   - Haber: `SUELDOS_A_PAGAR`, `IPS_A_DEPOSITAR`, `DESCUENTOS_VARIOS_RRHH`.
   - Las líneas del Debe se distribuyen por centro de costo contable del empleado; las del Haber van globales.
   - Se imputa al **último día del período liquidado**, no a la fecha de cierre.
   - Idempotente por `(empresa, origen_tipo, origen_id)`: reintegrar no duplica. Se dispara fire-and-forget, así que un fallo contable no bloquea el cierre — se reintenta con `POST /:id/integrar-contabilidad`.

5. **Acreditación bancaria**
   - Desde una liquidación cerrada se genera el archivo bancario con formato configurable.
   - Solo incluye empleados con neto positivo, banco y cuenta bancaria.

6. **Confirmación y Tesorería**
   - Al confirmar la acreditación se exige cuenta de tesorería si Tesorería está activo.
   - El sistema crea un movimiento `EGRESO` confirmado con origen `rrhh_acreditacion` y baja el saldo.

7. **Asiento de pago**
   - El egreso de tesorería genera un documento contable con origen `tes_movimientos`.
   - Debe: `SUELDOS_A_PAGAR`.
   - Haber: cuenta contable de la cuenta de tesorería/banco.

### Mapeos contables RRHH default

| Concepto | Cuenta estándar |
|---|---|
| `SUELDOS_JORNALES` | `6.1.1.01 — Sueldos y Salarios Administrativos` |
| `SUELDOS_A_PAGAR` | `2.1.3.04 — Sueldos y Jornales a Pagar` |
| `APORTE_PATRONAL_IPS` | `6.1.1.02 — Cargas Sociales - IPS Patronal` |
| `APORTE_ADMIN_IPS` | `6.1.1.02 — Cargas Sociales - IPS Patronal` |
| `IPS_A_DEPOSITAR` | `2.1.3.07 — IPS a Depositar` |
| `DESCUENTOS_VARIOS_RRHH` | `1.1.2.06 — Anticipos y Préstamos al Personal` |
| `AGUINALDO_FINIQUITO` | `6.1.1.04 — Aguinaldo` |
| `INDEMNIZACION` | `6.1.1.05 — Indemnización` |
| `PREAVISO` | `6.1.1.06 — Preaviso` |
| `VACACIONES_FINIQUITO` | `6.1.1.07 — Vacaciones` |

⚠️ Estos mapeos **no se siembran solos al arrancar el backend**: hay que ejecutar el botón `Crear cuentas y mapeos faltantes` (`POST contabilidad/mapeo-cuentas/sembrar-faltantes`). Existe además la migración `20260709_rrhh_sueldos_a_pagar_mapeo` como respaldo para los 6 conceptos del núcleo. Si falta un mapeo, el asiento no se genera y queda el motivo registrado.

### Otros asientos que genera RRHH

Además del devengamiento y el pago de la planilla:

- **Finiquitos**: al procesar una desvinculación se asienta con `INDEMNIZACION`, `PREAVISO`, `VACACIONES_FINIQUITO` y `AGUINALDO_FINIQUITO`, y con Tesorería activa genera el egreso del pago.
- **Viáticos** (`src/rendicion-viaticos/`): adelantos, devoluciones y pago de CxP a empleado, con sus reversiones. Usa los mapeos `ANTICIPO_VIATICO` y `REINTEGRO_EMPLEADO_PENDIENTE`.
- **Comisiones**: las liquidadas por el módulo de vendedores/cobradores tienen asiento propio (`LIQUIDACION_COMISIONES`, `PAGO_COMISIONES`, `COMISION_SUPERVISOR`). Las que entran por nómina **no** tienen asiento separado: se devengan dentro de `SUELDOS_JORNALES`. Liquidar la misma comisión por los dos caminos la contabiliza dos veces.

---

## Alcance funcional (M01–M15)

| Módulo | Descripción | Prioridad |
|---|---|---|
| M01 | Catastro de Empleados | Alta |
| M02 | Cargos y Estructura Organizacional | Alta |
| M03 | Tipos de Empleado | Alta |
| M04 | Vacaciones, permisos y ausencias | Alta |
| M05 | Préstamos a empleados | Alta |
| M06 | Anticipos de salario | Alta |
| M07 | Conceptos de liquidación | Alta |
| M08 | Planillas externas (Excel/CSV) | Alta |
| M09 | Pre-liquidación | Crítica |
| M10 | Liquidación mensual definitiva | Crítica |
| M11 | Liquidación quincenal | Alta |
| M12 | Acreditación bancaria (TXT) | Alta |
| M13 | Reporte IPS / REOP | Crítica |
| M14 | Desvinculación / finiquito | Alta |
| M15 | Parámetros del sistema | Alta |

---

## Parámetros legales y configurables

Residen en **`rrhh_parametros_sistema`** (por empresa, con vigencia temporal). El seed (`src/rrhh/seeds/rrhh-defaults.ts`) carga **24 claves**:

Legales y de cálculo:

- `IPS_PORCENTAJE_OBRERO = 0.09`
- `IPS_PORCENTAJE_PATRONAL = 0.165`
- `IPS_PORCENTAJE_ADMIN = 0.01`
- `SMLV_MENSUAL = 2800000`
- `SMLV_DIARIO = 93333`
- `SUBSIDIO_FAMILIAR_PORCENTAJE = 0.05`
- `HORAS_LABORALES_MES = 200`
- `PORCENTAJE_MICROEMPRESA_BASE_IPS = 0.80`
- `LIMITE_DESCUENTO_SALARIO = 0.30`
- `LIMITE_EMBARGO_JUDICIAL = 0.50`

De liquidación:

- `PERMITIR_NETO_NEGATIVO_EXCEPCION = false`
- `DIAS_BASE_LIQUIDACION = 0` (0 = días reales del mes)
- `HEREDAR_DESCUENTOS_MANUALES_MES_CERRADO = false`

De presentismo (todas con prefijo `PRESENTISMO_`):

- `PRESENTISMO_TOLERANCIA_ENTRADA_MIN = 5`
- `PRESENTISMO_TOLERANCIA_SALIDA_MIN = 5`
- `PRESENTISMO_GENERAR_NOVEDAD_AUTOMATICA = true`
- `PRESENTISMO_HORAS_AUSENCIA_MEDIA_JORNADA = 4`

De legajos: `LEGAJO_DIAS_ALERTA_VENCIMIENTO`, `LEGAJO_TAMANIO_MAX_ARCHIVO_MB`, `LEGAJO_MIME_HABILITADOS`, `LEGAJO_ALERTAS_EMAIL_HABILITADO`, `LEGAJO_DIAS_ANTICIPACION_EMAIL`, `LEGAJO_COMPLETITUD_OBLIGATORIO_BLOQUEA`, `LEGAJO_PRESIGNED_URL_EXPIRY_SEC`.

El mismo seed carga 17 conceptos de liquidación por defecto.

Reglas no parametrizables (deben quedar validadas en código):

- Escala de vacaciones por antigüedad (Art. 219 CT).
- Aguinaldo proporcional anual (Art. 244 CT).
- Preaviso e indemnización por antigüedad (Art. 87/91/94 CT).

---

## Modelo de datos propuesto

### 1) Estructura organizacional y empleados

Nota vigente: los centros de costo ya no son una tabla propia de RRHH. Se usan los centros de costo contables (`cont_centros_costo`) para que liquidaciones y asientos compartan la misma dimensión analítica. La tabla `rrhh_centros_costo` fue eliminada (migración `20260707_unify_centros_costo`).

**Alcance de esta sección**: el esquema real tiene **36 tablas `rrhh_*`**; acá se documentan las 20 del núcleo. Las otras 16 viven en sus propios planes:

- Presentismo (10 tablas: relojes, marcaciones raw/limpias/log/ajustes, turnos, bloques, asignaciones, permisos, tolerancias) → `plan-rrhh-presentismo.md`
- Legajos (3: tipos de documento, documentos, alertas de vencimiento) → `plan-rrhh-legajos.md`
- Comisiones (`rrhh_comisiones_pendientes`) → `plan-integracion-comisiones-rrhh.md`
- Tablas hija: `rrhh_planillas_externas_det`, `rrhh_reportes_ips_det`

Los viáticos no usan prefijo `rrhh_`: van por `rendicion_viatico` y `cuentas_pagar_empleado`.

El DDL de abajo es ilustrativo. La fuente de verdad es `prisma/schema.prisma`, que además define 21 enums `rrhh_*` (estados y tipos) que acá se muestran como VARCHAR.

```sql
CREATE TABLE rrhh_tipos_empleado (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  codigo VARCHAR(40) NOT NULL, -- DEPENDIENTE_GENERAL, CONTRATADO_FACTURA, etc.
  nombre VARCHAR(100) NOT NULL,
  -- OJO: el flag aporta_ips NO vive acá, vive en rrhh_empleados.aporta_ips
  recibe_aguinaldo BOOLEAN NOT NULL DEFAULT true,
  activo BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE (empresa_id, codigo)
);

CREATE TABLE rrhh_cargos (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  codigo VARCHAR(20),
  nombre VARCHAR(100) NOT NULL,
  salario_minimo_cargo DECIMAL(18,2),
  salario_maximo_cargo DECIMAL(18,2),
  activo BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_departamentos (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  sucursal_id UUID NOT NULL REFERENCES sucursales(id),
  codigo VARCHAR(20),
  nombre VARCHAR(100) NOT NULL,
  responsable_empleado_id UUID,
  activo BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_empleados (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  numero_empleado VARCHAR(20) NOT NULL,
  nombres VARCHAR(100) NOT NULL,
  apellidos VARCHAR(100) NOT NULL,
  cedula_identidad VARCHAR(20) NOT NULL,
  ruc VARCHAR(20),
  fecha_nacimiento DATE NOT NULL,
  sexo CHAR(1) NOT NULL,
  estado_civil VARCHAR(20) NOT NULL,
  cantidad_hijos INT NOT NULL DEFAULT 0,
  email VARCHAR(150),
  telefono VARCHAR(30),
  direccion TEXT,
  ciudad_id UUID,
  sucursal_id UUID NOT NULL REFERENCES sucursales(id),
  departamento_id UUID NOT NULL REFERENCES rrhh_departamentos(id),
  cargo_id UUID NOT NULL REFERENCES rrhh_cargos(id),
  centro_costo_id UUID NOT NULL REFERENCES cont_centros_costo(id),
  tipo_empleado_id UUID NOT NULL REFERENCES rrhh_tipos_empleado(id),
  fecha_ingreso DATE NOT NULL,
  fecha_egreso DATE,
  salario_base DECIMAL(18,2) NOT NULL,
  tipo_salario VARCHAR(20) NOT NULL, -- MENSUAL/JORNAL/COMISION
  banco_id UUID,
  cuenta_bancaria VARCHAR(30),
  tipo_cuenta VARCHAR(20),
  numero_ips VARCHAR(20),
  aporta_ips BOOLEAN NOT NULL DEFAULT true,
  regimen_ips VARCHAR(30) NOT NULL DEFAULT 'GENERAL',
  estado VARCHAR(20) NOT NULL DEFAULT 'ACTIVO',
  observaciones TEXT,
  foto_url VARCHAR(255),
  creado_por UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE (empresa_id, numero_empleado),
  UNIQUE (empresa_id, cedula_identidad)
);
```

### 2) Conceptos y parámetros

```sql
CREATE TABLE rrhh_parametros_sistema (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id), -- siempre por empresa, no hay globales
  clave VARCHAR(50) NOT NULL,
  nombre VARCHAR(100) NOT NULL,
  valor TEXT NOT NULL,
  tipo_dato VARCHAR(20) NOT NULL,
  categoria VARCHAR(30) NOT NULL,
  editable BOOLEAN NOT NULL DEFAULT true,
  vigencia_desde DATE NOT NULL,
  vigencia_hasta DATE,
  modificado_por UUID,
  modificado_en TIMESTAMP DEFAULT NOW(),
  UNIQUE (empresa_id, clave, vigencia_desde)
);

CREATE TABLE rrhh_conceptos_liquidacion (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id), -- los del sistema se marcan con es_del_sistema, no con empresa NULL
  codigo VARCHAR(20) NOT NULL,
  nombre VARCHAR(100) NOT NULL,
  tipo VARCHAR(10) NOT NULL, -- INGRESO/EGRESO
  categoria VARCHAR(30) NOT NULL,
  afecta_base_ips BOOLEAN NOT NULL DEFAULT false,
  afecta_base_aguinaldo BOOLEAN NOT NULL DEFAULT false,
  formula TEXT,
  es_fijo BOOLEAN NOT NULL DEFAULT false,
  es_porcentaje BOOLEAN NOT NULL DEFAULT false,
  porcentaje DECIMAL(6,4),
  es_del_sistema BOOLEAN NOT NULL DEFAULT false,
  aplica_a VARCHAR(20) NOT NULL DEFAULT 'TODOS',
  orden_visualizacion INT NOT NULL DEFAULT 0,
  activo BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE (empresa_id, codigo)
);
```

Conceptos base del sistema (seed inicial):

- `SALARIO_BASE`, `SALARIO_PROPORCIONAL`
- `HORAS_EXTRA_50`, `HORAS_EXTRA_100`
- `SUBSIDIO_FAMILIAR`
- `ANTICIPO_QUINCENAL`
- `IPS_OBRERO`, `IPS_PATRONAL`, `IPS_ADMIN`
- `CUOTA_PRESTAMO`
- `DESCUENTO_AUSENCIA`, `DESCUENTO_TARDANZA`
- `COMISION_VENTAS`, `BONO_DESEMPENO`

### 3) Vacaciones, asistencia y novedades

```sql
CREATE TABLE rrhh_vacaciones_saldo (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),
  anio INT NOT NULL,
  dias_correspondidos DECIMAL(5,2) NOT NULL,
  dias_tomados DECIMAL(5,2) NOT NULL DEFAULT 0,
  dias_pendientes DECIMAL(5,2) NOT NULL,
  dias_compensados DECIMAL(5,2) NOT NULL DEFAULT 0,
  cerrado BOOLEAN NOT NULL DEFAULT false,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE (empleado_id, anio)
);

CREATE TABLE rrhh_vacaciones_solicitudes (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),
  fecha_inicio DATE NOT NULL,
  fecha_fin DATE NOT NULL,
  dias_habiles INT NOT NULL,
  tipo VARCHAR(20) NOT NULL, -- VACACION/PERMISO/LICENCIA_MEDICA/LICENCIA_MATERNIDAD
  estado VARCHAR(20) NOT NULL DEFAULT 'SOLICITADO',
  aprobado_por UUID,
  observacion TEXT,
  afecta_salario BOOLEAN NOT NULL DEFAULT false,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_asistencia_novedades (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),
  fecha DATE NOT NULL,
  tipo_novedad VARCHAR(30) NOT NULL, -- AUSENCIA/TARDANZA/SALIDA_ANTICIPADA
  hora_entrada_real TIME,
  hora_entrada_esperada TIME,
  minutos_tardanza INT,
  justificado BOOLEAN NOT NULL DEFAULT false,
  tipo_justificacion VARCHAR(50),
  impacta_liquidacion BOOLEAN NOT NULL DEFAULT true,
  observacion TEXT,
  liquidacion_id UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
```

### 4) Préstamos y anticipos

```sql
CREATE TABLE rrhh_prestamos (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  fecha_otorgamiento DATE NOT NULL,
  monto_total DECIMAL(18,2) NOT NULL,
  cantidad_cuotas INT NOT NULL,
  monto_cuota DECIMAL(18,2) NOT NULL,
  tipo_descuento VARCHAR(20) NOT NULL, -- MENSUAL/QUINCENAL
  primer_descuento_en DATE NOT NULL,
  estado VARCHAR(20) NOT NULL DEFAULT 'ACTIVO',
  saldo_pendiente DECIMAL(18,2) NOT NULL,
  observacion TEXT,
  aprobado_por UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_prestamos_cuotas (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  prestamo_id UUID NOT NULL REFERENCES rrhh_prestamos(id) ON DELETE CASCADE,
  numero_cuota INT NOT NULL,
  periodo_anio INT NOT NULL,
  periodo_mes INT NOT NULL,
  periodo_tipo VARCHAR(20) NOT NULL, -- MENSUAL/QUINCENAL_1/QUINCENAL_2
  monto DECIMAL(18,2) NOT NULL,
  estado VARCHAR(20) NOT NULL DEFAULT 'PENDIENTE',
  liquidacion_id UUID,
  fecha_descuento DATE,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE (prestamo_id, numero_cuota)
);

CREATE TABLE rrhh_anticipos_salario (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  fecha_solicitud DATE NOT NULL,
  monto DECIMAL(18,2) NOT NULL,
  periodo_anio INT NOT NULL,
  periodo_mes INT NOT NULL,
  estado VARCHAR(20) NOT NULL DEFAULT 'PENDIENTE', -- PENDIENTE/APROBADO/DESCONTADO/ANULADO
  aprobado_por UUID,
  observacion TEXT,
  liquidacion_mensual_id UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
```

### 5) Liquidaciones

```sql
CREATE TABLE rrhh_liquidaciones_cabecera (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  sucursal_id UUID REFERENCES sucursales(id), -- NULL = todas
  periodo_anio INT NOT NULL,
  periodo_mes INT NOT NULL,
  quincena INT, -- 1 o 2 para tipo QUINCENAL
  tipo VARCHAR(20) NOT NULL DEFAULT 'MENSUAL', -- MENSUAL/QUINCENAL/AGUINALDO/VACACIONES/FINIQUITO
  tipo_empleado_id UUID REFERENCES rrhh_tipos_empleado(id), -- permite liquidar por tipo
  estado VARCHAR(20) NOT NULL DEFAULT 'BORRADOR',
  recalculo_pendiente BOOLEAN NOT NULL DEFAULT false,
  fecha_calculo TIMESTAMP,
  anulado_por UUID,
  fecha_anulacion TIMESTAMP,
  creado_por UUID,
  total_ingresos DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_egresos DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_neto DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_ips_obrero DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_ips_patronal DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_ips_admin DECIMAL(18,2) NOT NULL DEFAULT 0,
  cantidad_empleados INT NOT NULL DEFAULT 0,
  fecha_pago_programada DATE,
  fecha_cierre TIMESTAMP,
  cerrado_por UUID,
  numero_recalculos INT NOT NULL DEFAULT 0,
  observaciones TEXT,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Índice único PARCIAL (no un UNIQUE simple): permite rehacer una liquidación
-- anulada, y separa por quincena / sucursal / tipo de empleado.
CREATE UNIQUE INDEX rrhh_liq_cab_periodo_unique ON rrhh_liquidaciones_cabecera (
  empresa_id, periodo_anio, periodo_mes, tipo,
  COALESCE(quincena, 0),
  COALESCE(sucursal_id, '00000000-0000-0000-0000-000000000000'::uuid),
  COALESCE(tipo_empleado_id, '00000000-0000-0000-0000-000000000000'::uuid)
) WHERE estado <> 'ANULADA';

CREATE TABLE rrhh_liquidaciones_detalle (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  liquidacion_id UUID NOT NULL REFERENCES rrhh_liquidaciones_cabecera(id) ON DELETE CASCADE,
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),
  concepto_id UUID NOT NULL REFERENCES rrhh_conceptos_liquidacion(id),
  tipo VARCHAR(10) NOT NULL,
  monto DECIMAL(18,2) NOT NULL,
  base_calculo DECIMAL(18,2),
  porcentaje_aplicado DECIMAL(6,4),
  origen VARCHAR(30) NOT NULL, -- AUTOMATICO/MANUAL/IMPORTADO/ANTICIPO/PRESTAMO
  referencia_origen_id UUID,
  centro_costo_id UUID REFERENCES cont_centros_costo(id),
  observacion TEXT,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_liq_empleado_resumen (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  liquidacion_id UUID NOT NULL REFERENCES rrhh_liquidaciones_cabecera(id) ON DELETE CASCADE,
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),
  salario_bruto DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_ingresos DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_egresos DECIMAL(18,2) NOT NULL DEFAULT 0,
  ips_obrero DECIMAL(18,2) NOT NULL DEFAULT 0,
  ips_patronal DECIMAL(18,2) NOT NULL DEFAULT 0,
  ips_admin DECIMAL(18,2) NOT NULL DEFAULT 0,
  base_ips DECIMAL(18,2),
  neto_a_pagar DECIMAL(18,2) NOT NULL DEFAULT 0,
  dias_trabajados INT,
  dias_mes INT,
  alerta_descuento BOOLEAN,
  recibo_generado BOOLEAN NOT NULL DEFAULT false,
  recibo_url VARCHAR(255),
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE (liquidacion_id, empleado_id)
);
```

### 6) Importaciones, bancos, IPS y desvinculación

```sql
CREATE TABLE rrhh_planillas_externas (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  periodo_anio INT NOT NULL,
  periodo_mes INT NOT NULL,
  nombre VARCHAR(100) NOT NULL,
  archivo_url VARCHAR(255) NOT NULL,
  estado VARCHAR(20) NOT NULL DEFAULT 'IMPORTADA',
  aplicada_en_liquidacion_id UUID,
  total_registros INT NOT NULL DEFAULT 0,
  total_ingresos DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_egresos DECIMAL(18,2) NOT NULL DEFAULT 0,
  errores_importacion JSONB,
  creado_por UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_formatos_acreditacion_bancaria (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id), -- el formato es por empresa, no global por banco
  banco_id UUID NOT NULL REFERENCES bancos(id),
  nombre VARCHAR(100) NOT NULL,
  encoding VARCHAR(20) NOT NULL DEFAULT 'UTF-8',
  separador_campos VARCHAR(5),
  separador_lineas VARCHAR(5) DEFAULT '\n',
  tipo_archivo VARCHAR(20) NOT NULL, -- DELIMITADO/POSICIONAL
  tiene_cabecera BOOLEAN NOT NULL DEFAULT false,
  tiene_pie BOOLEAN NOT NULL DEFAULT false,
  template_cabecera TEXT,
  template_detalle TEXT NOT NULL,
  template_pie TEXT,
  extension_archivo VARCHAR(10) NOT NULL DEFAULT 'txt',
  descripcion TEXT,
  activo BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_acreditaciones_bancarias (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  liquidacion_id UUID NOT NULL REFERENCES rrhh_liquidaciones_cabecera(id),
  banco_id UUID NOT NULL REFERENCES bancos(id),
  formato_id UUID REFERENCES rrhh_formatos_acreditacion_bancaria(id),
  cuenta_tesoreria_id UUID REFERENCES tes_cuentas(id),
  fecha_generacion TIMESTAMP NOT NULL DEFAULT NOW(),
  fecha_pago_programada DATE,
  fecha_envio TIMESTAMP,
  fecha_confirmacion TIMESTAMP,
  cantidad_registros INT NOT NULL,
  monto_total DECIMAL(18,2) NOT NULL,
  nombre_archivo VARCHAR(255) NOT NULL,
  contenido_archivo TEXT NOT NULL,
  encoding VARCHAR(20) NOT NULL DEFAULT 'UTF-8',
  estado VARCHAR(20) NOT NULL DEFAULT 'GENERADO',
  observaciones TEXT,
  generado_por UUID,
  anulado_por UUID,
  fecha_anulacion TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_reportes_ips (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  liquidacion_id UUID NOT NULL REFERENCES rrhh_liquidaciones_cabecera(id),
  periodo_anio INT NOT NULL,
  periodo_mes INT NOT NULL,
  total_empleados INT NOT NULL,
  total_base_aportable DECIMAL(18,2) NOT NULL,
  total_aporte_obrero DECIMAL(18,2) NOT NULL,
  total_aporte_patronal DECIMAL(18,2) NOT NULL,
  total_aporte_admin DECIMAL(18,2) NOT NULL,
  total_a_depositar DECIMAL(18,2) NOT NULL,
  estado VARCHAR(20) NOT NULL DEFAULT 'GENERADO',
  numero_planilla_ips VARCHAR(50),
  cuenta_tesoreria_id UUID REFERENCES tes_cuentas(id),
  fecha_declaracion DATE,
  fecha_pago DATE,
  comprobante_url VARCHAR(255),
  observaciones TEXT,
  anulado_por UUID,
  fecha_anulacion TIMESTAMP,
  generado_por UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE rrhh_desvinculaciones (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),
  empresa_id UUID NOT NULL REFERENCES empresas(id),
  tipo_desvinculacion VARCHAR(30) NOT NULL,
  fecha_comunicacion DATE NOT NULL,
  fecha_ultimo_dia DATE NOT NULL,
  fecha_calculo_antiguedad DATE NOT NULL,
  anios_antiguedad DECIMAL(5,2) NOT NULL,
  meses_fraccion INT NOT NULL DEFAULT 0,
  salario_base_calculo DECIMAL(18,2) NOT NULL,
  jornal_diario_calculo DECIMAL(18,2) NOT NULL,
  salario_pendiente DECIMAL(18,2) NOT NULL DEFAULT 0,
  dias_vacaciones_proporcional DECIMAL(5,2) NOT NULL DEFAULT 0,
  monto_vacaciones_proporcional DECIMAL(18,2) NOT NULL DEFAULT 0,
  dias_vacaciones_no_gozadas DECIMAL(5,2) NOT NULL DEFAULT 0,
  monto_vacaciones_no_gozadas DECIMAL(18,2) NOT NULL DEFAULT 0,
  aguinaldo_proporcional DECIMAL(18,2) NOT NULL DEFAULT 0,
  dias_preaviso INT NOT NULL DEFAULT 0,
  monto_preaviso DECIMAL(18,2) NOT NULL DEFAULT 0,
  anios_indemnizacion INT NOT NULL DEFAULT 0,
  monto_indemnizacion DECIMAL(18,2) NOT NULL DEFAULT 0,
  otros_haberes DECIMAL(18,2) NOT NULL DEFAULT 0,
  otros_descuentos DECIMAL(18,2) NOT NULL DEFAULT 0,
  total_liquidacion_final DECIMAL(18,2) NOT NULL DEFAULT 0,
  estado VARCHAR(20) NOT NULL DEFAULT 'CALCULADO', -- CALCULADO/APROBADO/PAGADO/ANULADO
  cuenta_tesoreria_id UUID REFERENCES tes_cuentas(id),
  observaciones TEXT,
  aprobado_por UUID,
  fecha_aprobacion TIMESTAMP,
  pagado_por UUID,
  fecha_pago TIMESTAMP,
  anulado_por UUID,
  fecha_anulacion TIMESTAMP,
  creado_por UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
```

---

## Reglas de negocio obligatorias

1. Una liquidación `CERRADA` no se puede editar (`HTTP 409`).
2. `neto_a_pagar` no puede ser negativo salvo excepción formal parametrizada y auditada (`PERMITIR_NETO_NEGATIVO_EXCEPCION` + confirmación + motivo ≥10 caracteres). Responde **HTTP 400**.
3. IPS aplica solo cuando `empleado.aporta_ips = true`.
4. `base_ips` nunca por debajo del `SMLV` vigente, **prorrateado por días trabajados**: con alta o egreso a mitad de mes el piso baja en proporción.
5. Si régimen es `MICROEMPRESA_80`, base IPS mínima = `0.80 * SMLV`.
6. Anticipos aprobados del período se descuentan automáticamente en mensual.
7. Cuotas de préstamos pendientes del período se descuentan automáticamente.
8. No cerrar mensual si existe quincenal del mismo período no cerrada.
9. Descuentos del empleado no deben superar 30% del salario (salvo embargo judicial). La clasificación embargo / no-embargo es **por texto** del concepto: cuenta como embargo si su categoría, código o nombre contiene "EMBARGO". No hay un flag dedicado.
10. Embargos judiciales no deben superar 50% del salario.
11. Al desvincular, bloquear inclusión del empleado en nuevas liquidaciones corrientes, y también en la del mes del egreso si su finiquito sigue vigente.
12. Todas las tablas llevan `created_at`/`updated_at` y columnas de actor por acción (`creado_por`, `aprobado_por`, `anulado_por`, `cerrado_por`). **No existen `created_by`/`updated_by` genéricos**, y `AuditService` solo se usa hoy para la excepción de neto negativo.
13. Si Contabilidad está activa, una liquidación cerrada debe poder integrarse de forma idempotente a un asiento de devengamiento.
14. Si Tesorería está activa, confirmar una acreditación exige cuenta de tesorería válida y registra un egreso idempotente.
15. Si Contabilidad está activa, el egreso de tesorería de una acreditación debe poder integrarse a un asiento de pago idempotente.

---

## Flujo de estados

### Liquidación

| Estado | Transiciones permitidas | Responsable |
|---|---|---|
| `BORRADOR` | `PRE_LIQUIDACION`, `ANULADA` | RRHH |
| `PRE_LIQUIDACION` | `PRE_LIQUIDACION` (recalcular), `BORRADOR`, `CERRADA` | RRHH |
| `CERRADA` | Sin transición | Sistema |
| `ANULADA` | Sin transición | Administrador |

### Solicitudes de vacaciones

| Estado | Transiciones |
|---|---|
| `SOLICITADO` | `APROBADO`, `RECHAZADO`, `CANCELADO` |
| `APROBADO` | `CANCELADO` |
| `RECHAZADO` | Sin transición |
| `CANCELADO` | Sin transición |

### Préstamos y anticipos

- Préstamo: `ACTIVO` -> `CANCELADO` / `SUSPENDIDO`
- Cuota préstamo: `PENDIENTE` -> `DESCONTADO` / `REPROGRAMADO`
- Anticipo: `PENDIENTE` -> `APROBADO` -> `DESCONTADO` o `ANULADO`

---

## API

Prefijo real: `/api/v1/...`. Hay **26 controllers y ~172 endpoints** bajo `src/rrhh/`; abajo van los principales. Para el detalle completo, Swagger en `/docs`.

**Núcleo de liquidación**

- `GET|POST /api/v1/rrhh/empleados`, `PATCH /api/v1/rrhh/empleados/:id` (no hay `PUT`)
- `GET|POST /api/v1/rrhh/parametros`, `GET /api/v1/rrhh/parametros/vigente/:clave`
- `GET /api/v1/rrhh/conceptos`
- `POST /api/v1/rrhh/liquidaciones`, `/:id/calcular`, `/:id/cerrar`, `/:id/anular`
- `GET /api/v1/rrhh/liquidaciones/:id/planilla`, `/:id/planilla-control`, `/:id/asiento-contable`
- `POST /api/v1/rrhh/liquidaciones/:id/integrar-contabilidad`, `/:id/lineas-manuales`

**Acreditaciones bancarias**

- `GET|POST /api/v1/rrhh/acreditaciones-bancarias`, `/:id/descargar`
- `PATCH /:id/enviar`, `/:id/confirmar`, `/:id/anular`

**Resto**

- IPS: controller `rrhh/reportes-ips` — se **genera** con `POST` desde una liquidación CERRADA; luego `declarar`, `pagar`, `reop`. No existe `GET /reportes/ips/:anio/:mes`.
- Vacaciones: `GET /api/v1/rrhh/vacaciones/saldos` (por query), `POST saldos/recalcular`, más solicitudes con aprobar/rechazar/cancelar.
- Desvinculaciones: `POST /api/v1/rrhh/desvinculaciones/preview` calcula sin persistir (no existe `/:id/calcular`); luego `aprobar`, `pagar`, `anular`.
- Préstamos y anticipos: CRUD + cuotas y descuento.
- Planillas externas: importar, validar, aplicar.

**Grupos que este plan no cubre** (tienen su propio documento): presentismo y marcaciones (~29 endpoints), turnos (9), legajos (19), comisiones a nómina (7), catálogos (17), novedades, formatos bancarios. Viáticos vive fuera de `src/rrhh/`, en `src/rendicion-viaticos/`.

---

## Permisos

El catálogo real vive en `src/seguridad/seeds/seguridad.seed-data.ts` (módulo `RRHH`) y se sincroniza solo al arrancar el backend. **20 submódulos, 85 privilegios.**

Convención: submódulos `RRHH_<NOMBRE>`; privilegios `RH_<SUB3>_<RECURSO>_<ACCION>` — prefijo `RH_`, no `RRHH_`.

| Submódulo | Privilegios |
|---|---|
| `RRHH_EMPLEADOS` | `RH_EMP_EMPLEADO_CREAR/EDITAR/ELIMINAR/EXPORTAR/VER` |
| `RRHH_CONFIG` | `RH_CFG_CATALOGO_EDITAR/VER`, `RH_CFG_PARAMETRO_EDITAR/VER` |
| `RRHH_CONCEPTOS` | `RH_CON_CONCEPTO_CREAR/EDITAR/ELIMINAR/VER` |
| `RRHH_LIQUIDACIONES` | `RH_LIQ_LIQUIDACION_CREAR/APROBAR/PAGAR/ANULAR/VER` |
| `RRHH_IPS` | `RH_IPS_IPS_GENERAR/EXPORTAR/VER` |
| `RRHH_BANCARIO` | `RH_BAN_ACREDITACION_GENERAR/VER`, `RH_BAN_FORMATO_EDITAR` |
| `RRHH_COMISIONES` | `RH_COM_COMISION_VER/IMPORTAR/REVISAR` |
| `RRHH_DESVINCULACIONES` | `RH_DES_DESVINCULACION_CREAR/EDITAR/PROCESAR/VER` |
| `RRHH_VACACIONES` | `RH_VAC_VACACION_APROBAR/CREAR/EDITAR/ELIMINAR/VER` |
| `RRHH_ANTICIPOS` | `RH_ANT_ANTICIPO_*`, `RH_ANT_PRESTAMO_*` |
| `RRHH_VIATICOS` | `RH_VIA_RENDICION_*`, `RH_VIA_GASTO_CARGAR`, `RH_VIA_CIERRE_EJECUTAR`, `RH_VIA_CXP_PAGAR`, `RH_VIA_CONFIG_EDITAR` |
| `RRHH_MARCACIONES` | `RH_MAR_MARCACION_CREAR/EDITAR/ELIMINAR/EXPORTAR/VER` |
| `RRHH_TURNOS` | `RH_TUR_TURNO_CREAR/EDITAR/ELIMINAR/VER` |
| `RRHH_PERMISOS` | `RH_PER_PERMISO_APROBAR/CREAR/EDITAR/ELIMINAR/VER` |
| `RRHH_NOVEDADES` | `RH_NOV_NOVEDAD_CREAR/EDITAR/ELIMINAR/VER` |
| `RRHH_PLANILLAS` | `RH_PLA_PLANILLA_GENERAR/EXPORTAR/VER` |
| `RRHH_LEGAJOS` | `RH_LEG_LEGAJO_*`, `RH_LEG_DOCUMENTO_CREAR/ELIMINAR/VER` |
| `RRHH_VENCIMIENTOS` | `RH_VEN_VENCIMIENTO_VER` |
| `RRHH_REPORTES` | `RH_REP_REPORTE_VER/EXPORTAR` |
| `RRHH_LIQUIDACIONES_IA` (addon) | `RH_LIA_LIQ_IA_PROCESAR/VER` |

**No existen perfiles de sistema de RRHH.** Los 14 perfiles sembrados (Administrador, Cajero, Vendedor, Supervisor, Cobrador, Tesorero, Contador, Encargado Stock, Encargado Compras, Solo Lectura y los de Ecommerce) no agrupan permisos de RRHH: hay que armar perfiles a medida por empresa.

La auditoría no tiene privilegio propio en RRHH; vive en el módulo ADM.

---

## Integraciones con módulos existentes

- **Contabilidad**: mapeos default autoservicio; asiento de devengamiento por liquidación cerrada; asiento de pago por movimiento de tesorería.
- **Tesorería/Bancos**: emisión de archivo bancario por banco/formato; al confirmar acreditación crea egreso confirmado y descuenta saldo de cuenta.
- **Usuarios/Seguridad**: permisos finos y aislamiento por empresa/sucursal.
- **Documentos**: almacenamiento de recibos PDF, planillas importadas y reportes IPS.

---

## Plan de implementación

### Fase 1 — Base de RRHH (Semanas 1-4)

- Migraciones Prisma de estructura organizacional, empleados, tipos y parámetros.
- CRUDs base + validaciones legales mínimas (`CI`, salario, multiempresa).
- Seed de conceptos del sistema y parámetros críticos.

### Fase 2 — Liquidación Core (Semanas 5-9)

- Motor de cálculo mensual/quincenal con tests unitarios exhaustivos.
- Estados `BORRADOR`/`PRE_LIQUIDACION`/`CERRADA` y bloqueo por cierre.
- Cálculo automático: salario, IPS, subsidio, anticipos y cuotas de préstamos.

### Fase 3 — Operación RRHH (Semanas 10-14)

- Vacaciones, solicitudes, ausencias y tardanzas con impacto automático.
- Importación de planillas externas Excel/CSV con validaciones por fila.
- Acreditación bancaria multi-banco (plantillas + archivos TXT).
- Confirmación de acreditación integrada con Tesorería y Contabilidad.

### Fase 4 — Cumplimiento y cierre laboral (Semanas 15-18)

- Reporte IPS mensual + archivo REOP.
- Recibo de sueldo PDF por empleado.
- Proceso completo de desvinculación y liquidación final.
- Proceso anual de aguinaldo y aguinaldo proporcional al cese.

---

## Estrategia de pruebas

- Unit tests del motor de cálculo (casos legales y edge cases) con cobertura alta.
- Integration tests API para transiciones de estado y bloqueo de liquidaciones cerradas.
- Tests de regresión para parámetros por vigencia y cambios de normativa.
- Pruebas E2E para flujo mensual: pre-liquidar -> cerrar -> asiento de liquidación -> recibos -> IPS -> archivo banco -> confirmar acreditación -> egreso tesorería -> asiento de pago.

Casos obligatorios:

- Empleado con ingreso/egreso en mitad de mes.
- Empleado `MICROEMPRESA_80` y base IPS mínima.
- Neto negativo por descuentos excesivos.
- Finiquito por despido injustificado y por renuncia.
- Aguinaldo anual vs aguinaldo proporcional por cese.

---

## Riesgos y mitigaciones

- **Cambios normativos frecuentes**: resolver con parámetros versionados por vigencia.
- **Errores en cálculo de nómina**: aislar engine + tests + trazabilidad por concepto.
- **Volumen alto de cálculo**: procesamiento asíncrono por lotes y colas.
- **Errores de importación externa**: validaciones por fila + reporte detallado de errores.
- **Riesgo de fuga entre empresas**: enforcement multicapa (JWT claims + filtros + RLS).

---

## Entregables mínimos de v1

- Módulo RRHH operativo con liquidación mensual cerrable y auditable.
- Reporte IPS, archivo de acreditación bancaria por banco e integración Tesorería/Contabilidad del pago.
- Gestión de vacaciones, anticipos, préstamos y planillas externas.
- Finiquito por desvinculación conforme reglas laborales parametrizadas.
- Documentación técnica + matriz de trazabilidad legal por regla implementada.
