Skip to content

Plataforma técnica · Orchestrator

Honorarios Service

Remuneraciones Honorarios Bhe

HonorariosService orquesta el ciclo completo de boletas de honorarios electrónicas (BHE): importación desde el SII, clasificación por RUT del prestador, cálculo de retención según vigencia anual, persistencia con encabezado + detalle, contabilización y reversión. Emite eventos en el outbox al contabilizar/reversar para que los consumidores reaccionen post-COMMIT.

A diferencia de payroll, honorarios no produce liquidación calculada por el motor: la BHE viene desde el SII con sus montos ya determinados; este service clasifica, persiste y proporciona los hooks contables.

  • Directoryorchestrator/src/domain/honorarios/
    • HonorariosService.ts
    • HonorariosRepository.ts
    • types.ts

Tablas: remuneraciones.honorarios (encabezado), remuneraciones.honorarios_detalle (líneas), remuneraciones.prestadores_externos (catálogo). Fuente de BHE: operaciones_sii.boletas_honorarios.

MétodoFirmaResultado
list(ctx, year?, month?) => Promise<HonorarioEntity[]>Lista honorarios persistidos con filtro opcional.
listPrestadores(ctx) => Promise<PrestadorExterno[]>Catálogo de prestadores clasificados.
createPrestador(ctx, data) => Promise<ServiceResult<...>>Alta de prestador. Normaliza RUT.
updatePrestador(ctx, id, data) => Promise<ServiceResult<...>>Update parcial.
deletePrestador(ctx, id) => Promise<ServiceResult<{ deleted: true }>>Borra prestador.
contabilizar(ctx, id) => Promise<ServiceResult<...>>Marca honorario como contabilizado. Encola honorario:contabilizado.
reversarContabilizacion(ctx, id) => Promise<ServiceResult<...>>Revierte. Encola honorario:reversado.
generate(ctx, dto: GenerateHonorariosDTO) => Promise<ServiceResult<...>>Pipeline SII → honorario. Transaccional.

generate({ periodo, force }) es el método central. Procesa todas las BHE del periodo y persiste honorarios estructurados.

  1. Valida periodo con regex ^\d{4}-\d{2}$.

  2. Dentro de withTransaction(client):

    • Precarga el mapa de conceptos (HON-001..HON-005, HON-RET) por código → id. Si falta HON-001, falla con ValidationError("Missing default concept HON-001").
    • Si force=true, borra los honorarios del periodo con deleteHonorariosByPeriod.
    • Lee las BHE del periodo desde operaciones_sii.boletas_honorarios.
  3. Para cada BHE:

    • Verifica duplicado por (numero_boleta, rut_persona) salvo force=true. Si existe, suma a ignored.
    • Normaliza RUT: rut.replace(/\./g, "").toUpperCase().
    • Busca prestador clasificado y aplica la regla de routing (tabla más abajo).
    • Inserta encabezado honorarios con montos, periodo y estado="RECIBIDA".
    • Inserta detalle bruto con concepto_id resuelto.
    • Si la retención > 0 y existe el concepto HON-RET, inserta detalle de retención.
    • Si algo falla en el item, suma a errors y continúa con el siguiente.
  4. Devuelve { processed, ignored, errors, message }.

HonorariosService.generate clasifica cada BHE consultando prestadores_externos.tipo_servicio:

tipo_servicio del prestadorCódigo conceptoTipo de servicio asignado
CONTABILIDADHON-004CONSULTORIA
LEGALHON-005CONSULTORIA
TECNICOHON-002TECNICO
CAPACITACIONHON-003CAPACITACION
otro o sin prestadorHON-001OTRO / PROFESIONAL
let conceptoCode = "HON-001";
let tipoServicio = "PROFESIONAL";
if (prestador) {
switch (prestador.tipo_servicio) {
case "CONTABILIDAD": conceptoCode = "HON-004"; tipoServicio = "CONSULTORIA"; break;
case "LEGAL": conceptoCode = "HON-005"; tipoServicio = "CONSULTORIA"; break;
case "TECNICO": conceptoCode = "HON-002"; tipoServicio = "TECNICO"; break;
case "CAPACITACION": conceptoCode = "HON-003"; tipoServicio = "CAPACITACION"; break;
default: conceptoCode = "HON-001"; tipoServicio = "OTRO"; break;
}
}

Una BHE de un RUT no clasificado entra como HON-001 y queda visible para que el operador la reclasifique manualmente.

El motor lee boleta.tipo para determinar quién asume la retención y, por lo tanto, qué monto registrar en el detalle de retención:

const montoRet =
boleta.tipo === "RECIBIDAS"
? boleta.retencion_emisor
: boleta.retencion_receptor;
boleta.tipoSignificadoRetención registrada
RECIBIDASEmpresa receptora; emisor asume la retención.retencion_emisor (descuento al pago del prestador).
otro (típicamente EMITIDAS_TERCEROS)Empresa paga la retención por separado.retencion_receptor (carga adicional para la empresa).

El detalle de retención se inserta con concepto HON-RET y descripción "Retención 10% (Legal)" (la tasa real puede diferir; el comentario es descriptivo).

EventoTriggerPayload
honorario:contabilizadocontabilizar(ctx, id){ honorarioId, rutPrestador, monto, retencion, periodo: { anio, mes } }
honorario:reversadoreversarContabilizacion(ctx, id){ honorarioId, rutPrestador, prevEstado }
return this.withTransaction(ctx, async (client, outbox) => {
const row = await HonorariosRepository.contabilizarHonorario(client, id, ctx.userId);
if (!row) throw new ValidationError("Honorario no encontrado o no está pendiente de contabilizar");
outbox.queue("honorario:contabilizado", { ... });
return this.success(row);
});

Los eventos se emiten después del COMMIT, garantizando que los consumidores (asiento contable, dashboard, etc.) sólo reaccionen a estados realmente persistidos.

CampoRegla
rut, nombres, apellidosObligatorios. validateRequired.
rutNormalizado: sin puntos, uppercase, trim.
nombres, apellidosTrim.
tipo_servicioDefault "OTROS" si no se envía.
cuenta_gasto_codigonull si vacío.
ValidaciónResultado si falla
periodo formato YYYY-MMValidationError("Format required: YYYY-MM")
Existencia del concepto HON-001 en el catálogoValidationError("Missing default concept HON-001")
OrigenMensajeCuándo
validateRequired"<campo> es requerido"Falta rut, nombres o apellidos al crear prestador.
ValidationError"Prestador externo no encontrado"deletePrestador con id inexistente.
assertExists"PrestadorExterno <id> not found"updatePrestador con id inexistente.
ValidationError"Honorario no encontrado o no está pendiente de contabilizar"contabilizar con id ya contabilizado o inexistente.
ValidationError"Honorario no encontrado o no está contabilizado"reversarContabilizacion con id no contabilizado.
ValidationError"Format required: YYYY-MM"generate con periodo mal formado.
ValidationError"Missing default concept HON-001"Catálogo sin el concepto base.
ConsumidorCómo consume
Sevastopol honorarios-view-islandCRUD de prestadores + invocación de generate para importar el periodo.
F29GeneratorServiceLee monto_retencion agrupado por periodo para la línea de retenciones de segunda categoría.
DJ1879ServiceLee detalle por prestador (monto bruto y retención) para la declaración jurada anual.
Eventos honorario:contabilizadoDisparan creación de asiento contable y refresh de dashboards.