Skip to content

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.

  • 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.

MétodoFirmaResultado
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.
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.

  1. validateRequired confirma los 3 campos obligatorios.

  2. Dentro de withTransaction(client):

    • calculatePayload(client, data) recolecta todo y calcula.
    • Arma el objeto CreateFiniquitoData con numero_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.
  3. Devuelve { message: "Finiquito generado exitosamente", id }.

El método central de cálculo combina seis fuentes:

PasoOrigenResultado
Mapeo legacy de causalFiniquitoFormatters.mapLegacyCausalNormaliza códigos antiguos.
Datos del contratoFiniquitoRepository.getContractDataSueldo base, fecha inicio, etc.
CausalCommonDataService.getCausalNombre, flags de IAS y aviso previo, tope años.
AntigüedadcalculateAntiquity(inicio, termino)Años decimales + años para IAS (con redondeo art. 163).
Últimas remuneracionesFiniquitoRepository.getLastRemunerationsPromedio imponible para baseIAS.
Saldo de feriadoVacationService.getEmployeeBalance(client, empleadoId, fechaTermino)Días pendientes y corridos.
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.

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;
}
const TOPE_UF = 90;
const VALOR_UF = 38000;
const topePesos = TOPE_UF * VALOR_UF;
const baseIAS_Tope = Math.min(baseIAS, topePesos);
ConceptoAplica siCálculo
IAScausal.aplica_indemnizacion_anios_servicio === trueMath.min(yearsForIAS, causal.tope_meses_ias ?? 11) × baseIAS_Tope
Aviso previocausal.aplica_indemnizacion_aviso_previo === true y overrides.aviso_previo !== "NO_APLICA"baseIAS_Tope × 1 mes
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.

const totalIndem = montoIAS + montoAviso;
const totalLiquido = totalIndem + montoVacaciones;
{
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,
}
EstadoSignificado
CALCULADOPersistido por generate. Estado inicial.
APROBADOValidado por usuario (transición administrada externamente).
RATIFICADOFirmado ante ministro de fe / canal electrónico.
ANULADOSin efecto.

generate deja el finiquito en CALCULADO. Las transiciones posteriores se manejan vía endpoints específicos no documentados acá.

getDownloadPath(ctx, id) invoca generatePdf que:

  1. Carga el finiquito desde DB.

  2. Carga datos del empleado con JOIN contra contrato + cargo.

  3. Carga empresa (CompanyService.get) y representante legal activo (LegalRepService.getActive).

  4. Lee detalle_snapshot desde la fila (JSON con los conceptos calculados).

  5. Arma FiniquitoContext con:

    • 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).
  6. Invoca FiniquitoHtmlGenerator.generate(ctx) que devuelve un Buffer del PDF.

  7. Escribe el PDF en storage/<tenant_sin_prefijo>/finiquitos/FINIQUITO_<rut>_<numero>.pdf.

  8. 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.

HelperUso
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.
OrigenMensajeCuá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.
ServiceCómo consume
ContractServiceterminateContract cierra el contrato vigente con fecha_termino y estado=TERMINADO.
EmployeeServiceIndirectamente: tras finiquito, el empleado debería pasar a estado FINIQUITADO (manejado por proceso externo).
DJ1887ServiceIncluye indemnizaciones pagadas y feriado proporcional en la declaración anual de sueldos.
Sevastopol finiquitos-view-islandLlama a simulate y generate desde la UI.