Plataforma técnica · Orchestrator
Finiquito Service
Remuneraciones Finiquitos Indemnizacion
FiniquitoService cierra el contrato laboral del trabajador. Recolecta datos del contrato, la causal, las últimas remuneraciones y el saldo de vacaciones; calcula años de servicio con la regla de redondeo del art. 163, aplica IAS y aviso previo según la causal, paga el feriado proporcional pendiente y genera el documento PDF final.
El dominio está partido en seis archivos para que cada responsabilidad sea testeable por separado: orquestación (FiniquitoService), persistencia (FiniquitoRepository), input builder, calculator, formatters de presentación y generador HTML→PDF.
Ubicación
Section titled “Ubicación”Directoryorchestrator/src/domain/finiquitos/
- FiniquitoService.ts
- FiniquitoRepository.ts
- FiniquitoCalculator.ts
- FiniquitoInputBuilder.ts
- FiniquitoFormatters.ts
- FiniquitoHtmlGenerator.ts
- types.ts
Tabla: remuneraciones.finiquitos. PDFs en disco: storage/<tenant_sin_prefijo>/finiquitos/FINIQUITO_<rut>_<numero>.pdf.
API pública
Section titled “API pública”| Método | Firma | Resultado |
|---|---|---|
findAll | (ctx, filter: FiniquitoFilter) => Promise<...> | Lista con joins (empleado, contrato, causal). |
simulate | (ctx, data: FiniquitoParams) => Promise<ServiceResult<SimulationResult>> | Calcula sin persistir. |
generate | (ctx, data: FiniquitoParams) => Promise<ServiceResult<{ message, id }>> | Transaccional. Marca contrato como terminado y crea el finiquito. |
delete | (ctx, id) => Promise<ServiceResult<boolean>> | Borra el registro (no toca PDF en disco). |
getDownloadPath | (ctx, id) => Promise<string | null> | Genera (o regenera) el PDF y devuelve la ruta. |
FiniquitoParams
Section titled “FiniquitoParams”interface FiniquitoParams { contratoId: string; empleadoId?: string; fechaTermino: string; // YYYY-MM-DD causalCodigo: string; // ej. "161-1", "159-2" overrides?: { aviso_previo?: "APLICA" | "NO_APLICA"; meses_aviso?: number; fecha_finiquito?: string; };}Validaciones: contratoId, fechaTermino y causalCodigo son obligatorios.
Flujo de generación
Section titled “Flujo de generación”-
validateRequiredconfirma los 3 campos obligatorios. -
Dentro de
withTransaction(client):calculatePayload(client, data)recolecta todo y calcula.- Arma el objeto
CreateFiniquitoDataconnumero_finiquito = "FIN-<YEAR>-<TIMESTAMP_LAST_6>". FiniquitoRepository.terminateContract(client, contractId, fechaTermino)cierra el contrato (estado=TERMINADO,fecha_termino).FiniquitoRepository.createFiniquito(client, data)persiste el finiquito.
-
Devuelve
{ message: "Finiquito generado exitosamente", id }.
calculatePayload (privado)
Section titled “calculatePayload (privado)”El método central de cálculo combina seis fuentes:
| Paso | Origen | Resultado |
|---|---|---|
| Mapeo legacy de causal | FiniquitoFormatters.mapLegacyCausal | Normaliza códigos antiguos. |
| Datos del contrato | FiniquitoRepository.getContractData | Sueldo base, fecha inicio, etc. |
| Causal | CommonDataService.getCausal | Nombre, flags de IAS y aviso previo, tope años. |
| Antigüedad | calculateAntiquity(inicio, termino) | Años decimales + años para IAS (con redondeo art. 163). |
| Últimas remuneraciones | FiniquitoRepository.getLastRemunerations | Promedio imponible para baseIAS. |
| Saldo de feriado | VacationService.getEmployeeBalance(client, empleadoId, fechaTermino) | Días pendientes y corridos. |
Antigüedad y redondeo (art. 163)
Section titled “Antigüedad y redondeo (art. 163)”let yearsForIAS = Math.floor(totalYearsDecimal);if (years >= 1 && months >= 6) yearsForIAS += 1;Un trabajador con 7 años 6 meses se redondea a 8 años. Por debajo de 6 meses, se trunca. El redondeo solo aplica si ya hay al menos 1 año completo.
Base indemnizatoria
Section titled “Base indemnizatoria”Si hay últimas remuneraciones, la base IAS es el promedio del total_imponible de esas liquidaciones; si no, fallback al sueldo_base del contrato:
let baseIAS = Number(contract.sueldo_base);if (lastLiqs.length > 0) { const sum = lastLiqs.reduce((acc, curr) => acc + Number(curr.total_imponible), 0); baseIAS = sum / lastLiqs.length;}Tope UF de IAS
Section titled “Tope UF de IAS”const TOPE_UF = 90;const VALOR_UF = 38000;const topePesos = TOPE_UF * VALOR_UF;const baseIAS_Tope = Math.min(baseIAS, topePesos);IAS y aviso previo
Section titled “IAS y aviso previo”| Concepto | Aplica si | Cálculo |
|---|---|---|
| IAS | causal.aplica_indemnizacion_anios_servicio === true | Math.min(yearsForIAS, causal.tope_meses_ias ?? 11) × baseIAS_Tope |
| Aviso previo | causal.aplica_indemnizacion_aviso_previo === true y overrides.aviso_previo !== "NO_APLICA" | baseIAS_Tope × 1 mes |
Feriado proporcional
Section titled “Feriado proporcional”const valorDia = await this.getValorDia(fechaTermino, contract.sueldo_base);const diasParaPago = vacBalance?.dias_corridos || diasPendientes;const montoVacaciones = Math.round(diasParaPago * valorDia);getValorDia lee el sueldo mínimo del indicador del día y usa max(sueldo_base, sueldoMinimo) / 30 como valor diario. Si falla la lectura de indicadores, fallback a sueldo_base / 30.
Total líquido
Section titled “Total líquido”const totalIndem = montoIAS + montoAviso;const totalLiquido = totalIndem + montoVacaciones;Resultado de simulate
Section titled “Resultado de simulate”{ simulated: true, aniosServicio: "8.42", // años decimales (string con 2 decimales) aniosServicioRed: 8, // años para IAS (entero) indemnizaciones: { total: 5_400_000, // IAS + aviso ias: 4_800_000, aviso: 600_000, vacaciones: 320_000, }, topeUF: 90, valorUF: 38000, totalLiquido: 5_720_000, diasVacaciones: 12,}Estado del finiquito
Section titled “Estado del finiquito”| Estado | Significado |
|---|---|
CALCULADO | Persistido por generate. Estado inicial. |
APROBADO | Validado por usuario (transición administrada externamente). |
RATIFICADO | Firmado ante ministro de fe / canal electrónico. |
ANULADO | Sin efecto. |
generate deja el finiquito en CALCULADO. Las transiciones posteriores se manejan vía endpoints específicos no documentados acá.
Generación de PDF
Section titled “Generación de PDF”getDownloadPath(ctx, id) invoca generatePdf que:
-
Carga el finiquito desde DB.
-
Carga datos del empleado con JOIN contra contrato + cargo.
-
Carga empresa (
CompanyService.get) y representante legal activo (LegalRepService.getActive). -
Lee
detalle_snapshotdesde la fila (JSON con los conceptos calculados). -
Arma
FiniquitoContextcon:- Folio, fecha de emisión formateada.
- Datos formateados de empresa, trabajador y representante.
- Haberes desde el snapshot.
- Total líquido en pesos y en palabras (
FiniquitoFormatters.numberToText).
-
Invoca
FiniquitoHtmlGenerator.generate(ctx)que devuelve unBufferdel PDF. -
Escribe el PDF en
storage/<tenant_sin_prefijo>/finiquitos/FINIQUITO_<rut>_<numero>.pdf. -
Devuelve la ruta absoluta.
ctx.tenantDb.replace("nostromo_", "") quita el prefijo del tenant antes de armar la ruta — útil cuando el tenant lleva el namespace.
FiniquitoFormatters
Section titled “FiniquitoFormatters”| Helper | Uso |
|---|---|
formatRut(rut) | Aplica separadores de miles y guión. |
formatDate(date) | Formato corto en español. |
numberToText(n) | Convierte número a palabras (para el total líquido en el documento). |
mapLegacyCausal(code) | Normaliza códigos antiguos al esquema actual. |
Errores
Section titled “Errores”| Origen | Mensaje | Cuándo |
|---|---|---|
validateRequired | "<campo> es requerido" | Falta contratoId, fechaTermino o causalCodigo. |
calculatePayload | "Contrato no encontrado" | contratoId inexistente. |
calculatePayload | "Causal no encontrada" | causalCodigo no está en CommonDataService. |
assertExists | "Finiquito <id> not found" | generatePdf con id que no existe. |
Consumidores
Section titled “Consumidores”| Service | Cómo consume |
|---|---|
ContractService | terminateContract cierra el contrato vigente con fecha_termino y estado=TERMINADO. |
EmployeeService | Indirectamente: tras finiquito, el empleado debería pasar a estado FINIQUITADO (manejado por proceso externo). |
DJ1887Service | Incluye indemnizaciones pagadas y feriado proporcional en la declaración anual de sueldos. |
Sevastopol finiquitos-view-island | Llama a simulate y generate desde la UI. |