# Plan: Integración Comisiones → Módulo RRHH

**Fecha**: Junio 2026  
**Contexto**: Novasis ERP — Code100  
**Módulos involucrados**: Módulo Comercial (supervisores, vendedores/cobradores) → Módulo RRHH (liquidación mensual)

---

## Estado actual y decisiones acordadas (actualización)

**Estado verificado en código (antes de implementar):**
- ❌ NO implementado: no existe la tabla `rrhh_comisiones_pendientes`, ni el `RrhhComisionesService`, ni el campo `empleado_id` en `supervisores` / `vendedores_cobradores`.
- ✅ Concepto `COMISION_VENTAS` ya sembrado en `src/rrhh/seeds/rrhh-defaults.ts` con `tipo=INGRESO`, `afecta_base_ips=true`, `afecta_base_aguinaldo=true`. El motor ya lo procesa si existe una línea (cubierto por `rrhh-engine.service.spec.ts`).
- ✅ Hoy la única vía de comisión en RRHH es **manual** (novedad o import de planilla con `COMISION_VENTAS`).

**Decisiones tomadas con el usuario:**
1. **Diseño de 2 conceptos** (como este plan): `rrhh_comisiones_pendientes` actúa solo como **buzón** de comisiones del módulo comercial. Las "comisiones propias de RRHH" (empresa que solo usa RRHH) **siguen cargándose manualmente** por novedad/planilla con `COMISION_VENTAS` (ya funciona). No se crea una tabla unificada.
2. **Implementar todo (Fases A–E)**, con entrega **incremental: backend (A+B+C) primero**, luego **frontend (D)**, y **QA (E)** al final.
3. **Module-aware** (criterio transversal del ERP): la **importación desde el comercial** y su UI solo están disponibles si el módulo `COMISIONES` está activo en la suscripción de la empresa. Gating con el patrón ya usado: backend `tieneModulo('COMISIONES')` (ver `contabilidad/services/integracion.service.ts`), frontend `hasModulePlan('COMISIONES')` de `useAuthStore` (ver `MapeoCuentasTab.jsx`). Si la empresa solo tiene RRHH, no se muestra la importación; las comisiones manuales siguen disponibles.

**Fuentes comerciales confirmadas para importar:**
- **Supervisores**: `comisiones_supervisores` (`supervisor_id`, `empresa_id`, `periodo_desde/hasta`, `total_comision`, `estado`).
- **Vendedores/Cobradores (y supervisores) agregado por período**: `liquidaciones_comisiones` (`tipo` = `vendedor|supervisor`, `vendedor_cobrador_id`/`supervisor_id`, `periodo_desde/hasta`, `total_comisiones`, `estado`). Fuente per-período recomendada.
- **NO se usan**: `comisiones` (granular por factura/recibo) ni `comisiones_nuevas` (comisiones SaaS de holding/reseller).

> El resto del documento (modelo de datos, servicios, endpoints, fases) se mantiene como diseño de referencia y se implementa según estas decisiones.

---

## Objetivo

Permitir que las comisiones generadas en el módulo administrativo/comercial (supervisores y vendedores/cobradores) puedan ser revisadas, aprobadas e incluidas como concepto de liquidación en la nómina mensual del empleado en el módulo RRHH, afectando correctamente la base de IPS.

---

## Decisiones de diseño

| # | Decisión | Resolución |
|---|---|---|
| 1 | Vinculación comercial → RRHH | Campo `empleado_id` en tablas `supervisores` y `vendedores_cobradores` (FK opcional hacia `rrhh_empleados`). |
| 2 | Cola de comisiones pendientes | Nueva tabla `rrhh_comisiones_pendientes` que actúa como bandeja de entrada desde el módulo comercial. |
| 3 | Flujo de aprobación | RRHH aprueba o rechaza cada comisión individualmente antes de incluirla en la liquidación. |
| 4 | Momento de inclusión | Durante la pre-liquidación (estado `BORRADOR` o `PRE_LIQUIDACION`). |
| 5 | Concepto RRHH | Usar el concepto seed existente `COMISION_VENTAS` (tipo `INGRESO`, `afecta_base_ips = true`). |
| 6 | Origen en detalle | `origen = 'COMISION'` en `rrhh_liquidaciones_detalle`, con `referencia_origen_id` apuntando a `rrhh_comisiones_pendientes.id`. |
| 7 | Impacto IPS | La comisión suma a la base aportable de IPS (obrero + patronal + admin). |
| 8 | Trazabilidad | Cada comisión aprobada/rechazada registra usuario, fecha y observación. |
| 9 | Origen comercial | Se leen desde `comisiones_supervisores` y la tabla equivalente de vendedores/cobradores. |
| 10 | No duplicación | Una comisión comercial solo puede estar una vez en `rrhh_comisiones_pendientes` por período. |

---

## Modelo de datos

### 1. Vinculación: empleado en tablas comerciales

```sql
-- Agregar FK opcional a supervisores
ALTER TABLE supervisores 
  ADD COLUMN IF NOT EXISTS empleado_id UUID REFERENCES rrhh_empleados(id) ON DELETE SET NULL;

-- Agregar FK opcional a vendedores_cobradores
ALTER TABLE vendedores_cobradores 
  ADD COLUMN IF NOT EXISTS empleado_id UUID REFERENCES rrhh_empleados(id) ON DELETE SET NULL;

-- Índices
CREATE INDEX idx_supervisores_empleado_id ON supervisores(empleado_id);
CREATE INDEX idx_vendedores_cobradores_empleado_id ON vendedores_cobradores(empleado_id);
```

> **Nota**: El campo es opcional para mantener compatibilidad. Supervisores/vendedores sin `empleado_id` no aparecen en el flujo RRHH.

---

### 2. Cola de comisiones pendientes (bandeja de entrada RRHH)

```sql
CREATE TABLE rrhh_comisiones_pendientes (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id),

  -- Quién cobra
  empleado_id UUID NOT NULL REFERENCES rrhh_empleados(id),

  -- Período de liquidación destino
  periodo_anio INT NOT NULL,
  periodo_mes INT NOT NULL,

  -- Origen comercial
  origen_tipo VARCHAR(30) NOT NULL, -- SUPERVISOR / VENDEDOR / COBRADOR
  origen_id UUID NOT NULL,          -- ID del supervisor o vendedor_cobrador
  origen_tabla VARCHAR(50) NOT NULL, -- 'comisiones_supervisores' / 'comisiones_vendedores'
  comision_origen_id UUID NOT NULL,  -- ID de la fila en la tabla origen

  -- Montos
  monto_bruto DECIMAL(18,2) NOT NULL,
  descripcion TEXT,

  -- Estado RRHH
  estado VARCHAR(20) NOT NULL DEFAULT 'PENDIENTE',
  -- PENDIENTE / APROBADA / RECHAZADA / INCLUIDA_EN_LIQUIDACION

  -- Trazabilidad de aprobación
  revisado_por UUID,               -- user_id
  fecha_revision TIMESTAMP,
  motivo_rechazo TEXT,

  -- Liquidación donde se incluyó (si fue aprobada)
  liquidacion_id UUID REFERENCES rrhh_liquidaciones_cabecera(id),
  detalle_id UUID REFERENCES rrhh_liquidaciones_detalle(id),

  -- Auditoría
  creado_por UUID,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),

  -- Unicidad: un registro de comisión comercial solo puede entrar una vez por período
  UNIQUE (comision_origen_id, origen_tabla, periodo_anio, periodo_mes)
);

CREATE INDEX idx_rrhh_comisiones_pendientes_empleado ON rrhh_comisiones_pendientes(empleado_id, periodo_anio, periodo_mes);
CREATE INDEX idx_rrhh_comisiones_pendientes_estado ON rrhh_comisiones_pendientes(empresa_id, estado, periodo_anio, periodo_mes);
```

---

### 3. Actualizar `rrhh_liquidaciones_detalle.origen`

El campo `origen` ya existe como `VARCHAR(30)`. Agregar `'COMISION'` al enum de valores permitidos:

```
AUTOMATICO | MANUAL | IMPORTADO | ANTICIPO | PRESTAMO | COMISION
```

No requiere migración SQL — es solo una convención de aplicación.

---

### 4. Actualizar concepto seed `COMISION_VENTAS`

Verificar que en `rrhh_conceptos_liquidacion` el seed tenga:

```sql
-- Estado real del seed vigente (src/rrhh/seeds/rrhh-defaults.ts): no requiere cambios.
UPDATE rrhh_conceptos_liquidacion 
SET 
  tipo = 'INGRESO',
  afecta_base_ips = true,
  afecta_base_aguinaldo = true,
  es_fijo = false,
  es_porcentaje = false,
  aplica_a = 'TODOS'
WHERE codigo = 'COMISION_VENTAS' AND es_del_sistema = true;
```

> El seed vigente ya tiene `afecta_base_ips = true` y `afecta_base_aguinaldo = true`. La comisión suma
> a la base de IPS y de aguinaldo. Si se quisiera que NO afecte aguinaldo, sería un parámetro de empresa
> (no contemplado en esta entrega).

---

## Servicios Backend

### Módulo: `rrhh-comisiones` (nuevo)

Archivo: `src/rrhh/comisiones/rrhh-comisiones.service.ts`

#### Método 1: `importarDesdeModuloComercial`

```typescript
/**
 * Busca comisiones generadas en el módulo comercial para el período dado
 * y las carga en rrhh_comisiones_pendientes si no existen aún.
 * Solo incluye supervisores/vendedores con empleado_id vinculado.
 */
async importarDesdeModuloComercial(
  empresaId: string,
  periodoAnio: number,
  periodoMes: number,
  importadoPor: string
): Promise<{ importadas: number; yaExistentes: number; sinVinculo: number }>
```

Lógica:
1. Consultar `comisiones_supervisores` donde `fecha_periodo` coincide con el período y el supervisor tiene `empleado_id != NULL`.
2. Consultar tabla equivalente de vendedores/cobradores con misma condición.
3. Por cada comisión, hacer `upsert` en `rrhh_comisiones_pendientes` (el UNIQUE previene duplicados).
4. Retornar resumen de resultados.

#### Método 2: `listarPendientesPorPeriodo`

```typescript
async listarPendientesPorPeriodo(
  empresaId: string,
  periodoAnio: number,
  periodoMes: number,
  filtros?: { estado?: string; empleadoId?: string }
): Promise<RrhhComisionPendienteDto[]>
```

#### Método 3: `aprobarComision`

```typescript
async aprobarComision(
  comisionPendienteId: string,
  revisadoPor: string
): Promise<void>
// Cambia estado a APROBADA, registra revisado_por y fecha_revision
```

#### Método 4: `rechazarComision`

```typescript
async rechazarComision(
  comisionPendienteId: string,
  motivo: string,
  revisadoPor: string
): Promise<void>
// Cambia estado a RECHAZADA con motivo
```

#### Método 5: `incluirEnLiquidacion` (llamado por el motor de liquidación)

```typescript
/**
 * Incluye todas las comisiones APROBADAS del período en la liquidación
 * dada. Solo aplica a empleados incluidos en esa liquidación.
 * Inserta filas en rrhh_liquidaciones_detalle con origen='COMISION'.
 * Actualiza estado a INCLUIDA_EN_LIQUIDACION.
 */
async incluirEnLiquidacion(
  liquidacionId: string,
  empresaId: string,
  periodoAnio: number,
  periodoMes: number
): Promise<{ incluidas: number }>
```

---

### Integración con el motor de liquidación (`rrhh-engine`)

En el método `calcularLiquidacion` o `recalcularLiquidacion`, después de procesar los conceptos automáticos (salario, IPS, subsidio, anticipos, préstamos), agregar:

```typescript
// Paso N: incluir comisiones aprobadas del período
await this.rrhhComisionesService.incluirEnLiquidacion(
  liquidacion.id,
  liquidacion.empresa_id,
  liquidacion.periodo_anio,
  liquidacion.periodo_mes
);
```

La comisión ya entra como línea de `rrhh_liquidaciones_detalle` con `concepto_id` apuntando a `COMISION_VENTAS`, y el motor de IPS la toma automáticamente porque `afecta_base_ips = true`.

---

## API Endpoints

```
# Importar comisiones del módulo comercial al pool RRHH
POST /v1/rrhh/comisiones/importar
Body: { periodo_anio, periodo_mes }
Permiso: RRHH_COMISIONES_IMPORTAR

# Listar comisiones pendientes de revisión
GET /v1/rrhh/comisiones/pendientes?periodo_anio=&periodo_mes=&estado=&empleado_id=
Permiso: RRHH_COMISIONES_REVISAR

# Aprobar una comisión
PATCH /v1/rrhh/comisiones/:id/aprobar
Permiso: RRHH_COMISIONES_REVISAR

# Rechazar una comisión
PATCH /v1/rrhh/comisiones/:id/rechazar
Body: { motivo }
Permiso: RRHH_COMISIONES_REVISAR

# Aprobar masivamente (por empleado o todas del período)
POST /v1/rrhh/comisiones/aprobar-masivo
Body: { ids: UUID[] }
Permiso: RRHH_COMISIONES_REVISAR

# Historial de comisiones de un empleado
GET /v1/rrhh/empleados/:empleadoId/comisiones?anio=&mes=
Permiso: RRHH_REPORTES_VER
```

---

## Permisos nuevos

Agregar a la matriz de permisos RRHH:

| Permiso | Descripción |
|---|---|
| `RRHH_COMISIONES_IMPORTAR` | Disparar importación desde módulo comercial |
| `RRHH_COMISIONES_REVISAR` | Aprobar / rechazar comisiones pendientes |

Roles que deben tenerlos: `SUPER_ADMIN`, `GERENTE_RRHH`, `OPERADOR_RRHH`.

---

## Flujo completo de operación

```
MÓDULO COMERCIAL                    MÓDULO RRHH

1. Admin genera comisiones          
   supervisores por período         
   POST /supervisores/:id/          
   comisiones/generar               
   → comisiones_supervisores        
                                    
                                    2. RRHH importa al pool
                                       POST /rrhh/comisiones/importar
                                       { periodo_anio, periodo_mes }
                                       → rrhh_comisiones_pendientes
                                          estado: PENDIENTE

                                    3. RRHH revisa lista de pendientes
                                       GET /rrhh/comisiones/pendientes
                                       Ve: empleado, monto, origen

                                    4. RRHH aprueba o rechaza
                                       PATCH /rrhh/comisiones/:id/aprobar
                                       PATCH /rrhh/comisiones/:id/rechazar
                                       → estado: APROBADA / RECHAZADA

                                    5. RRHH calcula pre-liquidación
                                       POST /rrhh/liquidaciones/:id/calcular
                                       El motor incluye automáticamente
                                       comisiones APROBADAS del período
                                       → rrhh_liquidaciones_detalle
                                          origen: COMISION
                                          concepto: COMISION_VENTAS
                                          afecta_base_ips: true

                                    6. RRHH verifica, ajusta y cierra
                                       POST /rrhh/liquidaciones/:id/cerrar
                                       Las comisiones quedan inmutables
```

---

## Cálculo de IPS con comisión

El motor ya calcula IPS sobre los conceptos marcados con `afecta_base_ips = true`. Con la comisión incluida:

```
Base IPS = Salario Base + Horas Extra + Comisión Ventas + otros ingresos con afecta_base_ips=true

IPS Obrero  = Base IPS × 9%
IPS Patronal = Base IPS × 16.5%
IPS Admin    = Base IPS × 1%

Neto = Total Ingresos - IPS Obrero - Descuentos
```

Regla legal: `Base IPS >= SMLV` (regla 4 del plan) se sigue respetando aunque la comisión sea 0.

---

## Frontend — Pantalla de revisión de comisiones

Ubicación sugerida: Tab "Comisiones" dentro del módulo de Liquidaciones RRHH.

### Sección 1: Importar

- Selector de período (mes/año)
- Botón **"Importar desde Módulo Comercial"**
- Resultado: contador de importadas / ya existentes / sin vínculo

### Sección 2: Revisar y aprobar

Tabla con columnas:
- Empleado (nombre + número)
- Tipo (Supervisor / Vendedor / Cobrador)
- Período comercial de origen
- Monto bruto
- Estado (badge: Pendiente / Aprobada / Rechazada / Incluida)
- Acciones: Aprobar / Rechazar (con modal de motivo)

Filtros: por estado, por empleado, por tipo de origen.

Botón **"Aprobar seleccionadas"** para aprobación masiva.

### Sección 3: Vinculación (configuración)

Pantalla para asignar `empleado_id` a supervisores y vendedores/cobradores existentes:

- Lista de supervisores/vendedores sin vinculación
- Selector de empleado RRHH para cada uno
- Botón guardar

---

## Fases de implementación

### Fase A — Vinculación (2-3 días)

- Migración: `empleado_id` en `supervisores` y `vendedores_cobradores`.
- Actualizar Prisma schema.
- UI de vinculación en módulo comercial o RRHH (sección Configuración).
- Endpoint: `PATCH /supervisores/:id` acepta `empleado_id`.
- Endpoint: `PATCH /vendedores-cobradores/:id` acepta `empleado_id`.

### Fase B — Pool de comisiones RRHH (3-4 días)

- Migración: tabla `rrhh_comisiones_pendientes`.
- Actualizar Prisma schema.
- `RrhhComisionesService` con métodos importar / listar / aprobar / rechazar.
- Controller + DTOs + validaciones.
- Permiso `RRHH_COMISIONES_IMPORTAR` y `RRHH_COMISIONES_REVISAR`.
- Tests unitarios del service.

### Fase C — Integración con motor de liquidación (2-3 días)

- Actualizar `rrhh-engine` para llamar `incluirEnLiquidacion` al calcular.
- Asegurar que `COMISION_VENTAS` seed tiene `afecta_base_ips = true`.
- Verificar cálculo de IPS con comisión incluida.
- Tests unitarios: liquidación con comisión, sin comisión, con comisión rechazada.

### Fase D — Frontend (4-5 días)

- Tab "Comisiones" en módulo Liquidaciones RRHH.
- Pantalla de importar + tabla de revisión + acciones aprobación/rechazo.
- Pantalla de vinculación supervisores/vendedores → empleados.
- Integración con API endpoints.
- Mostrar en recibo de sueldo el concepto `COMISION_VENTAS` con monto.

### Fase E — QA y cierre (2-3 días)

- Test E2E: generar comisión comercial → importar → aprobar → calcular liquidación → verificar IPS → cerrar.
- Caso: comisión rechazada no aparece en liquidación.
- Caso: supervisor sin `empleado_id` no aparece en pool RRHH.
- Caso: comisión ya incluida no puede reimportarse (UNIQUE).
- Documentación técnica actualizada.

**Total estimado: 13-18 días**

---

## Reglas de negocio específicas

1. No se pueden aprobar comisiones para empleados que no están incluidos en la liquidación activa del período.
2. Si la liquidación está en estado `CERRADA`, no se pueden incluir más comisiones (bloqueo igual al resto del módulo).
3. Una comisión `INCLUIDA_EN_LIQUIDACION` no se puede rechazar ni reeditar.
4. Comisiones de períodos anteriores pueden importarse en liquidaciones de ajuste.
5. El rechazo de una comisión debe registrar motivo obligatorio.
6. Si se recalcula la pre-liquidación, las comisiones ya incluidas no se duplican (verificar por `referencia_origen_id`).

---

## Riesgos y mitigaciones

| Riesgo | Mitigación |
|---|---|
| Supervisores/vendedores sin `empleado_id` vinculado | UI de vinculación + alerta en importación con contador `sin_vinculo`. |
| Duplicación de comisiones al recalcular | UNIQUE en `comision_origen_id + tabla + período` y verificación antes de insertar en detalle. |
| Comisión aprobada pero empleado fuera de liquidación | Validar en `incluirEnLiquidacion` que el empleado está en `rrhh_liq_empleado_resumen`. |
| Cambio de salario base entre comisión y liquidación | La base IPS se calcula al momento del cierre, con el salario vigente — la comisión es solo un monto fijo. |
| Comisiones de períodos cruzados | El campo `periodo_anio/mes` en `rrhh_comisiones_pendientes` es el período RRHH de destino, no el comercial. |

---

## Entregables

- Migración `rrhh_comisiones_pendientes` + FK en tablas comerciales.
- `RrhhComisionesService` completo con tests.
- Motor de liquidación actualizado para incluir comisiones aprobadas.
- Endpoints y permisos documentados.
- UI de vinculación y revisión de comisiones.
- Test E2E del flujo completo.
- Sección en recibo PDF mostrando `COMISION_VENTAS`.
