Skip to content

OperationsService

Orchestrator Services

Compras-VentasOrchestratorServicio

OperationsService coordina la generacion de detalle contable para documentos SII de ventas, compras y boletas. Toma documentos normalizados desde el esquema operaciones_sii, los clasifica por concepto contable y persiste lineas en operaciones_sii.compras_ventas_detalle.

El servicio tambien permite consultar documentos fuente, revisar el estado de contabilizacion por periodo, contabilizar ingresos netos de ventas o boletas y reversar esa contabilizacion cuando corresponde.

Esta pagina describe el flujo tecnico actual de forma publica. No incluye rutas locales, credenciales, nombres de bases de datos de clientes ni detalles operativos que dependan de un entorno privado.

El servicio concentra cinco responsabilidades:

  • generar detalle contable automatico para VENTA, COMPRA o BOLETA;
  • impedir reprocesos accidentales cuando ya existen detalles para el periodo solicitado;
  • clasificar cada documento en conceptos activos de operaciones_sii.conceptos_operaciones;
  • marcar ingresos netos de ventas o boletas como contabilizados cuando el llamador lo solicita;
  • exponer consultas de documentos fuente y resumen de estado por tipo documental.

La generacion no registra asientos definitivos en el libro mayor. Produce detalle operacional auditable, con estado inicial PENDIENTE, que otros flujos pueden contabilizar, pagar, anular o consumir como base de declaraciones.

  • DirectoryOrchestrator
    • OperacionesService
    • OperacionesRepository
    • ventasContabilizables
  • DirectoryMother
    • operaciones_sii.ventas
    • operaciones_sii.compras
    • operaciones_sii.boletas
    • operaciones_sii.compras_otros_impuestos
    • operaciones_sii.conceptos_operaciones
    • operaciones_sii.compras_ventas_detalle

Los fragmentos siguientes muestran el flujo publico del metodo generate. Los nombres de repositorio y campos corresponden al dominio actual; los identificadores reales se expresan como parametros o placeholders.

  1. Recibir el contexto de servicio y validar que el tipo documental venga informado.

    async generate(
    ctx: ServiceContext,
    params: OperationsGenerateParams,
    ): Promise<ServiceResult<{ message: string; count: number }>> {
    this.validateRequired(params, ['tipo_documento']);
    }
  2. Validar que contabilizar_ingresos solo se use para ventas o boletas.

    if (
    params.contabilizar_ingresos &&
    !['VENTA', 'BOLETA'].includes(params.tipo_documento)
    ) {
    throw new ValidationError(
    'contabilizar_ingresos solo aplica a VENTA o BOLETA',
    );
    }
  3. Abrir una transaccion y resolver si corresponde reprocesar o bloquear.

    return this.withTransaction(ctx, async (client, outbox) => {
    if (params.forzar_reproceso) {
    await OperacionesRepository.deleteDetails(
    client,
    params.tipo_documento,
    params.periodo_desde,
    params.periodo_hasta,
    );
    } else {
    const existingDetails =
    await OperacionesRepository.countExistingDetails(
    client,
    params.tipo_documento,
    params.periodo_desde,
    params.periodo_hasta,
    );
    }
    });
  4. Cargar conceptos activos y construir el mapa codigo -> id.

    const concepts = await OperacionesRepository.fetchConcepts(client);
    const conceptMap = new Map<string, number>();
    concepts.forEach((c) => conceptMap.set(c.codigo, c.id));
    const getConceptId = (code: string) => {
    const id = conceptMap.get(code);
    if (!id) throw new Error(`Concepto no encontrado: ${code}`);
    return id;
    };
  5. Delegar el procesamiento segun tipo_documento.

    if (params.tipo_documento === 'VENTA') {
    await this.processVentas(client, params, getConceptId, results, ctx.userId);
    } else if (params.tipo_documento === 'COMPRA') {
    await this.processCompras(client, params, getConceptId, results, ctx.userId);
    } else if (params.tipo_documento === 'BOLETA') {
    await this.processBoletas(client, params, getConceptId, results, ctx.userId);
    }
  6. Persistir el detalle generado en compras_ventas_detalle.

    await OperacionesRepository.createBulkDetails(client, results);
  7. Si el llamador pidio contabilizar ingresos, marcar lineas netas de ventas o boletas y publicar eventos.

    if (params.contabilizar_ingresos) {
    const contabilizacion =
    await OperacionesRepository.contabilizarIngresosNetos(
    client,
    params.tipo_documento as 'VENTA' | 'BOLETA',
    params.periodo_desde,
    params.periodo_hasta,
    ctx.userId,
    );
    for (const eventPayload of contabilizacion.eventos) {
    outbox.queue('venta:contabilizada', eventPayload);
    }
    }

processVentas lee documentos desde operaciones_sii.ventas y genera hasta dos lineas por documento: neto e IVA. La clasificacion depende del tipo de documento y de los montos informados.

CasoConcepto netoConcepto IVATratamiento
Venta afectaVENTA-PRD-NETVENTA-PRD-IVAReconoce neto e IVA debito.
Venta exentaVENTA-EXE-NETNo aplicaUsa monto_exento cuando no hay neto afecto.
Nota de credito de ventaNC-VENTA-NETNC-VENTA-IVAInvierte neto e IVA con signo negativo.
Nota de debito de ventaND-VENTA-NETND-VENTA-IVARegistra neto e IVA con signo positivo.
Factura de compra recibida como ventaFACT-COMP-NETFACT-COMP-IVA-RETUsa el RUT cliente como emisor y calcula IVA no retenido.

Para facturas de compra registradas en el libro de ventas, el IVA neto se calcula restando IVA retenido total y parcial al IVA informado.

const montoIva = Number(v.monto_iva || 0);
const ivaRetenido =
Number(v.iva_retenido_total || 0) +
Number(v.iva_retenido_parcial || 0);
return Math.max(0, montoIva - ivaRetenido);

processCompras lee documentos desde operaciones_sii.compras y agrega otros_impuestos desde operaciones_sii.compras_otros_impuestos mediante LEFT JOIN y json_agg. El procesamiento genera lineas para neto, IVA recuperable, IVA no recuperable, otros impuestos y diferencias de redondeo.

CasoConcepto netoConcepto IVATratamiento
Compra afectaCOMP-MER-NETCOMP-MER-IVAReconoce neto de compra e IVA credito fiscal.
Nota de credito de compraNC-COMPRA-NETNC-COMPRA-IVAInvierte neto e IVA recuperable con signo negativo.
Nota de debito de compraND-COMPRA-NETND-COMPRA-IVARegistra neto e IVA recuperable con signo positivo.
IVA no recuperableID fijo 30No aplicaRegistra gasto por IVA no recuperable.
Otros impuestosCOMP-OI-{codigo} o COMP-OINo aplicaUsa concepto especifico por codigo y cae a generico si existe.
RedondeoCOMP-RDONo aplicaRegistra diferencias de uno o dos pesos.

El IVA no recuperable se trata como gasto separado y respeta el signo de las notas de credito.

const montoIvaNoRecuperable = Number(c.monto_iva_no_recuperable || 0);
if (montoIvaNoRecuperable === 0) return;
const monto = esNC
? -Math.abs(montoIvaNoRecuperable)
: Math.abs(montoIvaNoRecuperable);

Los otros impuestos de compra buscan primero un concepto especifico por codigo SII, por ejemplo COMP-OI-27, y luego el concepto generico COMP-OI. Si ninguno existe en el tenant, la linea se omite.

La diferencia de redondeo compara el total del documento contra la suma de neto, IVA recuperable, IVA no recuperable, activo fijo, exento y otros impuestos. Solo se genera COMP-RDO cuando la diferencia absoluta es de uno o dos pesos.

processBoletas lee documentos desde operaciones_sii.boletas y genera dos lineas por boleta.

LineaConceptoMonto
NetoVENTA-BOL-NETneto
IVAVENTA-BOL-IVAiva

Cuando tipo_doc viene nulo desde base de datos, el servicio usa 39 como tipo DTE por defecto. La razon social se normaliza como Consumidor Final.

La opcion contabilizar_ingresos no genera nuevas lineas; marca como contabilizadas lineas netas ya insertadas en compras_ventas_detalle. Solo aplica a VENTA y BOLETA.

Los conceptos considerados contabilizables son:

[
'VENTA-PRD-NET',
'VENTA-BOL-NET',
'VENTA-EXE-NET',
'NC-VENTA-NET',
'ND-VENTA-NET',
'FACT-COMP-NET',
]

La marca actualiza las lineas pendientes con:

CampoValor aplicado
estadoCONTABILIZADO
referenciaEXISTENCIAS
fecha_contabilizacionfecha_documento
updated_byUsuario ejecutor
updated_atFecha actual

Por cada documento afectado, el repositorio recupera un payload y el servicio publica venta:contabilizada en el outbox. Para ventas, el payload se obtiene desde operaciones_sii.ventas; para boletas, desde operaciones_sii.boletas.

reversarContabilizacionIngresos revierte solo ingresos netos de VENTA o BOLETA que hayan sido marcados por este flujo.

  1. Validar que tipo_documento sea VENTA o BOLETA.

    this.validateRequired(params, ['tipo_documento']);
    if (!['VENTA', 'BOLETA'].includes(params.tipo_documento)) {
    throw new ValidationError(
    'reversar contabilización de ingresos solo aplica a VENTA o BOLETA',
    );
    }
  2. Buscar lineas en estado CONTABILIZADO o PAGADO, con referencia = 'EXISTENCIAS'.

  3. Volver las lineas a PENDIENTE y limpiar referencia y fecha_contabilizacion.

    SET estado = 'PENDIENTE',
    referencia = NULL,
    fecha_contabilizacion = NULL,
    updated_by = $user,
    updated_at = CURRENT_TIMESTAMP

La reversa no aplica a compras, porque las compras no usan contabilizar_ingresos y pueden derivar hacia flujos distintos, como activo fijo, existencias, gastos o impuestos.

EntradaDescripcion
ctxContexto de servicio con base tenant y usuario ejecutor.
tipo_documentoCOMPRA, VENTA o BOLETA.
periodo_desdePeriodo inicial opcional en formato YYYY-MM.
periodo_hastaPeriodo final opcional en formato YYYY-MM.
forzar_reprocesoSi es verdadero, borra detalles previos del tipo y periodo antes de regenerar.
contabilizar_ingresosSi es verdadero, contabiliza ingresos netos generados para ventas o boletas.

Los identificadores reales deben tratarse como datos de entorno. En documentacion publica, usar placeholders como $TENANT_ID, $PERIODO_DESDE o $PERIODO_HASTA.

Catalogo de conceptos activos que traduce codigos de dominio a identificadores numericos.

Campo conceptualUso
idIdentificador usado en compras_ventas_detalle.concepto_id.
codigoCodigo semantico usado por el servicio, como COMP-MER-NET o VENTA-PRD-IVA.
activoSolo los conceptos activos participan en la generacion.

Tabla operacional donde se persiste el resultado de la generacion.

Campo conceptualUso
tipo_documentoTipo origen: VENTA, COMPRA o BOLETA.
documento_idIdentificador del documento fuente.
tipo_dteTipo DTE informado por SII o valor normalizado.
folioFolio del documento.
fecha_documentoFecha del documento SII.
rut_emisorRUT emisor o proveedor, segun tipo documental.
rut_receptorRUT receptor o cliente, segun tipo documental.
razon_socialNombre asociado al documento.
año, mesPeriodo contable usado para filtros y resumen.
concepto_idConcepto operacional resuelto desde el catalogo.
montoMonto de la linea operacional, con signo segun documento.
glosaDescripcion generada por el servicio.
origenActualmente AUTOMATICO.
estadoEstado operacional de la linea.
referenciaMarca usada por flujos posteriores, por ejemplo EXISTENCIAS.
TablaUso en el servicio
operaciones_sii.ventasFuente de documentos de venta, notas y facturas de compra en libro de ventas.
operaciones_sii.comprasFuente de compras, notas, IVA recuperable/no recuperable y montos de activo fijo.
operaciones_sii.compras_otros_impuestosDetalle de impuestos adicionales asociados a compras.
operaciones_sii.boletasFuente de boletas con neto, IVA y total.

getDetails usa OperacionesRepository.findDocuments para devolver documentos fuente normalizados. El filtro puede usar periodo, o bien periodo_desde y periodo_hasta.

TipoFuenteNormalizacion principal
VENTAfetchVentasfecha_documento, documento_tipo, rut_emisor, razon_social_emisor.
COMPRAfetchComprasmonto_iva se expone desde monto_iva_recuperable.
BOLETAfetchBoletasdocumento_tipo queda como Boleta y razon_social_emisor como Cliente Boleta.

estadoContabilizacion compara documentos fuente contra documentos con detalle para cada tipo: VENTA, COMPRA y BOLETA.

Estado generalCondicion
VACIONo hay documentos fuente ni detalle generado.
INCONSISTENTEHay faltantes o sobrantes entre origen y detalle.
PENDIENTEHay documentos fuente, pero no hay detalle generado.
PAGADOTodos los documentos estan pagados.
CONTABILIZADOTodos los documentos estan contabilizados o pagados.
VALIDADOTodos los documentos tienen detalle en estado pendiente.
PARCIALExiste mezcla de estados o avance incompleto.

El resumen tambien expone total_origen, total_detalle, validados, contabilizados, pagados, anulados, faltantes, sobrantes, procesados y pendientes.

OperacionEstado esperadoResultado
generate sin detalle previoSin filas del tipo y periodoInserta detalle con origen automatico.
generate con detalle previoforzar_reproceso = falseRechaza para evitar duplicidad.
generate con reprocesoforzar_reproceso = trueBorra detalle del tipo y periodo, luego regenera.
contabilizar_ingresosLineas netas PENDIENTE de venta o boletaMarca CONTABILIZADO y publica eventos.
reversarContabilizacionIngresosLineas CONTABILIZADO o PAGADO con referencia EXISTENCIASVuelve a PENDIENTE y limpia la referencia.
ReglaMotivo
Resolver conceptos por codigo y no por ID salvo IVA no recuperablePermite que el catalogo sea legible y configurable por tenant.
Bloquear reprocesos sin forzar_reprocesoEvita duplicar detalle operacional para el mismo periodo.
Usar año * 100 + mes para rangos de periodo en resumen y detallePermite comparar rangos mensuales sin depender de fechas del documento.
Mantener compras, ventas y boletas en una tabla de detalle comunSimplifica reportes operacionales y conciliacion por periodo.
Generar eventos solo al contabilizar ingresos netosSepara la preparacion del detalle de la señal contable hacia otros dominios.
Omitir otros impuestos sin concepto configuradoEvita fallar el procesamiento completo por configuraciones incompletas de conceptos opcionales.
Registrar redondeos solo hasta dos pesosDistingue ajustes menores de diferencias materiales que requieren revision.

Esta documentacion nombra servicios, schemas, tablas, estados y conceptos porque forman parte del contrato tecnico del sistema. No publica rutas locales de desarrollo, credenciales, endpoints internos protegidos ni datos reales de empresas.

Cuando se requiera un ejemplo operativo, usar placeholders:

{
"tenantId": "$TENANT_ID",
"tipo_documento": "COMPRA",
"periodo_desde": "$PERIODO_DESDE",
"periodo_hasta": "$PERIODO_HASTA"
}