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.
Ubicación
Section titled “Ubicación”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.
API pública
Section titled “API pública”| Método | Firma | Resultado |
|---|---|---|
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. |
Pipeline de importación
Section titled “Pipeline de importación”generate({ periodo, force }) es el método central. Procesa todas las BHE del periodo y persiste honorarios estructurados.
-
Valida
periodocon regex^\d{4}-\d{2}$. -
Dentro de
withTransaction(client):- Precarga el mapa de conceptos (
HON-001..HON-005,HON-RET) por código → id. Si faltaHON-001, falla conValidationError("Missing default concept HON-001"). - Si
force=true, borra los honorarios del periodo condeleteHonorariosByPeriod. - Lee las BHE del periodo desde
operaciones_sii.boletas_honorarios.
- Precarga el mapa de conceptos (
-
Para cada BHE:
- Verifica duplicado por
(numero_boleta, rut_persona)salvoforce=true. Si existe, suma aignored. - Normaliza RUT:
rut.replace(/\./g, "").toUpperCase(). - Busca prestador clasificado y aplica la regla de routing (tabla más abajo).
- Inserta encabezado
honorarioscon montos, periodo yestado="RECIBIDA". - Inserta detalle bruto con
concepto_idresuelto. - Si la retención > 0 y existe el concepto
HON-RET, inserta detalle de retención. - Si algo falla en el item, suma a
errorsy continúa con el siguiente.
- Verifica duplicado por
-
Devuelve
{ processed, ignored, errors, message }.
Routing por tipo de servicio
Section titled “Routing por tipo de servicio”HonorariosService.generate clasifica cada BHE consultando prestadores_externos.tipo_servicio:
tipo_servicio del prestador | Código concepto | Tipo de servicio asignado |
|---|---|---|
CONTABILIDAD | HON-004 | CONSULTORIA |
LEGAL | HON-005 | CONSULTORIA |
TECNICO | HON-002 | TECNICO |
CAPACITACION | HON-003 | CAPACITACION |
| otro o sin prestador | HON-001 | OTRO / 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.
Modalidad de retención
Section titled “Modalidad de retención”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.tipo | Significado | Retención registrada |
|---|---|---|
RECIBIDAS | Empresa 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).
Eventos del outbox
Section titled “Eventos del outbox”| Evento | Trigger | Payload |
|---|---|---|
honorario:contabilizado | contabilizar(ctx, id) | { honorarioId, rutPrestador, monto, retencion, periodo: { anio, mes } } |
honorario:reversado | reversarContabilizacion(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.
Validaciones y normalización
Section titled “Validaciones y normalización”createPrestador / updatePrestador
Section titled “createPrestador / updatePrestador”| Campo | Regla |
|---|---|
rut, nombres, apellidos | Obligatorios. validateRequired. |
rut | Normalizado: sin puntos, uppercase, trim. |
nombres, apellidos | Trim. |
tipo_servicio | Default "OTROS" si no se envía. |
cuenta_gasto_codigo | null si vacío. |
generate
Section titled “generate”| Validación | Resultado si falla |
|---|---|
periodo formato YYYY-MM | ValidationError("Format required: YYYY-MM") |
Existencia del concepto HON-001 en el catálogo | ValidationError("Missing default concept HON-001") |
Errores
Section titled “Errores”| Origen | Mensaje | Cuá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. |
Consumidores
Section titled “Consumidores”| Consumidor | Cómo consume |
|---|---|
Sevastopol honorarios-view-island | CRUD de prestadores + invocación de generate para importar el periodo. |
F29GeneratorService | Lee monto_retencion agrupado por periodo para la línea de retenciones de segunda categoría. |
DJ1879Service | Lee detalle por prestador (monto bruto y retención) para la declaración jurada anual. |
Eventos honorario:contabilizado | Disparan creación de asiento contable y refresh de dashboards. |