Skip to content

Plataforma técnica · Orchestrator

Ppm Service

Orchestrator Reportes Ppm

PpmService aplica corrección monetaria al PPM declarado mes a mes para llevarlo a valor de diciembre y reconocer el ajuste como crédito tributario adicional contra el impuesto a la renta del año. Extiende ReportesServiceSupport para reusar buildPpmActualizacionAnualResumen y buildPpmFactorMap.

El cálculo del PPM original vive en F29GeneratorService (línea 142 del F29). Este service consume los PPM ya declarados y solo actualiza por inflación.

MétodoTXPropósito
getPpmActualizacionAnual(ctx, anio)Computa la tabla 12-mes en memoria. No persiste.
contabilizarPpmActualizacionAnual(ctx, anio)Persiste el ajuste mes a mes y los marca CONTABILIZADO.
reversarPpmActualizacionAnual(ctx, anio)Revierte los CONTABILIZADO a estado base.
flowchart TB
  IN["getPpmActualizacionAnual(anio)"]
  VAL["validate anio"]
  POOL["getPool(ctx.tenantDb)"]
  PAR["Promise.all"]
  Q1["listPpmDeclaradoAnual(anio)<br/>→ F29 con ppm > 0 del año"]
  Q2["listPpmActualizacionesAnual(anio)<br/>→ ajustes ya persistidos"]
  Q3["CommonDataService.getCorreccionMonetaria(anio)<br/>→ factores SII por mes_origen/mes_destino"]
  BUILD["buildPpmActualizacionAnualResumen<br/>(en Support)"]
  OUT["PpmActualizacionAnualResumen"]

  IN --> VAL --> POOL --> PAR
  PAR --> Q1 & Q2 & Q3 --> BUILD --> OUT
async getPpmActualizacionAnual(
ctx: ServiceContext,
anio: number,
): Promise<ServiceResult<PpmActualizacionAnualResumen>>

Output (composición de 12 filas):

interface PpmActualizacionAnualRow {
id: number | null;
anio: number;
mes: number; // 1..12
periodo: string; // "2026-03"
declaracion_f29_id: string | null;
declaracion_estado: string | null;
ppm_declarado: number;
factor_aplicable: number;
ppm_actualizado: number; // ppm_declarado * factor
ajuste_correccion: number; // ppm_actualizado - ppm_declarado (clamp ≥ 0)
cuenta_activo_codigo: string; // default "1108001"
cuenta_ingreso_codigo: string; // default "4201002"
fuente_url: string | null;
estado: "PENDIENTE" | "CONTABILIZADO";
fecha_contabilizacion: string | null;
fecha_reversa: string | null;
}
interface PpmActualizacionAnualResumen {
anio: number;
total_declarado: number;
total_actualizado: number;
total_ajuste: number;
meses_declarados: number;
meses_con_ajuste: number;
meses_contabilizados: number;
estado_general: "PENDIENTE" | "PARCIAL" | "CONTABILIZADO";
filas: PpmActualizacionAnualRow[]; // siempre 12
}

El builder pide a CommonDataService.getCorreccionMonetaria(anio) los factores SII vigentes. Cada fila tiene mes_origen y mes_destino. La regla en buildPpmFactorMap:

// Solo factores que llevan al cierre anual
if (mesDestino !== 12) continue;
map.set(mesOrigen, { factor, fuenteUrl });

Es decir: solo se conservan los factores que actualizan desde un mes cualquiera hasta diciembre. Si la tabla SII tiene factores intermedios (mes-a-mes), se ignoran.

Luego, para cada PPM declarado, resolvePpmFactorSourceMonth decide qué mes_origen usar:

SiMes origen del factor
fecha_pago es ISO YYYY-MM-DD del mismo anioNumber(MM) de fecha_pago
fecha_pago es parseable como Date del mismo aniogetMonth() + 1 de la fecha
fecha_pago ausente y declarado.mes < 12declarado.mes + 1 (el factor se aplica desde el mes siguiente al período declarado)
fecha_pago ausente y declarado.mes === 12null (no hay factor; el PPM de diciembre no se actualiza)

Si no hay factor para el mes resuelto, default { factor: 1, fuenteUrl: null } — el PPM se mantiene sin ajuste.

const ppmDeclarado = roundPpmAmount(declarado?.ppm_declarado ?? 0);
const ppmActualizado = ppmDeclarado > 0
? roundPpmAmount(ppmDeclarado * factorData.factor)
: 0;
const ajusteCorreccion = Math.max(ppmActualizado - ppmDeclarado, 0);

ajuste_correccion se clamp a ≥ 0: una deflación que reduzca el PPM actualizado no genera ajuste negativo (el SII no permite reducir el PPM declarado por corrección monetaria).

estado_general se calcula sobre las filas que tienen ajuste_correccion > 0.01:

CondiciónEstado
0 filas con ajuste > 0.01PENDIENTE
Todas las filas con ajuste están CONTABILIZADOCONTABILIZADO
Al menos 1 contabilizado pero no todasPARCIAL
Hay ajustes pero ninguno contabilizadoPENDIENTE
async contabilizarPpmActualizacionAnual(
ctx: ServiceContext,
anio: number,
): Promise<ServiceResult<{ anio: number; contabilizados: number; ajuste_total: number }>>

Flujo dentro de withTransaction:

  1. Recomputa el resumen (listPpmDeclaradoAnual + factores). No usa lo persistido previamente — siempre parte del PPM declarado fresco para evitar drift.
  2. Si total_declarado <= 0ValidationError("No hay PPM declarados en el año para contabilizar").
  3. Mapea las 12 filas a payload de upsert con cuentas default (1108001 activo PPM por recuperar, 4201002 ingreso ajuste corrección).
  4. upsertPpmActualizacionesAnual(client, payload, userId) persiste 12 filas en reportes.ppm_actualizaciones_anual con estado CONTABILIZADO.
  5. Retorna { contabilizados, ajuste_total } contando solo filas con ppm_declarado > 0.01.

No emite eventos. La contabilización del asiento (cargo a activo, crédito a ingreso) se delega al lado SQL del upsert (el repo persiste las cuentas, pero los asientos formales viven en el módulo contable que consume reportes.ppm_actualizaciones_anual).

async reversarPpmActualizacionAnual(
ctx: ServiceContext,
anio: number,
): Promise<ServiceResult<{ anio: number; reversados: number; ajuste_total: number }>>

Flujo:

  1. Lista las actualizaciones persistidas del año.
  2. Filtra estado === 'CONTABILIZADO'. Si 0 → ValidationError("No hay actualizaciones PPM contabilizadas para reversar").
  3. reversarPpmActualizacionesAnual(client, anio, userId) revierte el estado (delegado al repo).
  4. Retorna { reversados, ajuste_total }.

reportes.ppm_actualizaciones_anual:

ColumnaTipoNotas
idbigserialPK
anio, mesintPeríodo (12 filas por año)
declaracion_f29_iduuidFK al F29 que originó el PPM
ppm_declaradonumericMonto declarado en el F29
factor_aplicablenumericFactor SII aplicado
ppm_actualizadonumericround(ppm_declarado × factor)
ajuste_correccionnumericDiferencia (clamp ≥ 0)
cuenta_activo_codigotextDefault 1108001
cuenta_ingreso_codigotextDefault 4201002
fuente_urltextURL de la circular SII vigente
estadotextPENDIENTE o CONTABILIZADO
fecha_contabilizacion, fecha_reversatimestampAudit
CódigoCuentaRol
1108001PPM por recuperarActivo donde se acumula el PPM disponible como crédito anual
4201002Ingreso por corrección monetaria PPMIngreso no operacional que reconoce el ajuste por inflación

Estos defaults se persisten en cada fila; si una empresa usa otras cuentas, el payload del upsert debería pasarlos (no expuesto hoy desde el service). El default cubre el caso por defecto del plan de cuentas chileno IFRS-compatible.

El PPM se actualiza una vez al año al cierre. No es un cálculo mensual rolling. La fila SII que importa es la que lleva desde el mes_origen (mes del pago PPM) hasta diciembre. Factores intermedios (e.g. enero → marzo) existen para otros cálculos (corrección de capital propio tributario) pero no aplican aquí.