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étodos
Section titled “Métodos”| Método | TX | Propó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. |
El cómputo: getPpmActualizacionAnual
Section titled “El cómputo: getPpmActualizacionAnual”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}Resolución del factor de corrección
Section titled “Resolución del factor de corrección”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 anualif (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:
| Si | Mes origen del factor |
|---|---|
fecha_pago es ISO YYYY-MM-DD del mismo anio | Number(MM) de fecha_pago |
fecha_pago es parseable como Date del mismo anio | getMonth() + 1 de la fecha |
fecha_pago ausente y declarado.mes < 12 | declarado.mes + 1 (el factor se aplica desde el mes siguiente al período declarado) |
fecha_pago ausente y declarado.mes === 12 | null (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.
Cálculo por fila
Section titled “Cálculo por fila”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
Section titled “Estado general”estado_general se calcula sobre las filas que tienen ajuste_correccion > 0.01:
| Condición | Estado |
|---|---|
| 0 filas con ajuste > 0.01 | PENDIENTE |
Todas las filas con ajuste están CONTABILIZADO | CONTABILIZADO |
| Al menos 1 contabilizado pero no todas | PARCIAL |
| Hay ajustes pero ninguno contabilizado | PENDIENTE |
contabilizarPpmActualizacionAnual
Section titled “contabilizarPpmActualizacionAnual”async contabilizarPpmActualizacionAnual( ctx: ServiceContext, anio: number,): Promise<ServiceResult<{ anio: number; contabilizados: number; ajuste_total: number }>>Flujo dentro de withTransaction:
- Recomputa el resumen (
listPpmDeclaradoAnual+ factores). No usa lo persistido previamente — siempre parte del PPM declarado fresco para evitar drift. - Si
total_declarado <= 0→ValidationError("No hay PPM declarados en el año para contabilizar"). - Mapea las 12 filas a payload de upsert con cuentas default (
1108001activo PPM por recuperar,4201002ingreso ajuste corrección). upsertPpmActualizacionesAnual(client, payload, userId)persiste 12 filas enreportes.ppm_actualizaciones_anualcon estadoCONTABILIZADO.- Retorna
{ contabilizados, ajuste_total }contando solo filas conppm_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).
reversarPpmActualizacionAnual
Section titled “reversarPpmActualizacionAnual”async reversarPpmActualizacionAnual( ctx: ServiceContext, anio: number,): Promise<ServiceResult<{ anio: number; reversados: number; ajuste_total: number }>>Flujo:
- Lista las actualizaciones persistidas del año.
- Filtra
estado === 'CONTABILIZADO'. Si 0 →ValidationError("No hay actualizaciones PPM contabilizadas para reversar"). reversarPpmActualizacionesAnual(client, anio, userId)revierte el estado (delegado al repo).- Retorna
{ reversados, ajuste_total }.
Persistencia
Section titled “Persistencia”reportes.ppm_actualizaciones_anual:
| Columna | Tipo | Notas |
|---|---|---|
id | bigserial | PK |
anio, mes | int | Período (12 filas por año) |
declaracion_f29_id | uuid | FK al F29 que originó el PPM |
ppm_declarado | numeric | Monto declarado en el F29 |
factor_aplicable | numeric | Factor SII aplicado |
ppm_actualizado | numeric | round(ppm_declarado × factor) |
ajuste_correccion | numeric | Diferencia (clamp ≥ 0) |
cuenta_activo_codigo | text | Default 1108001 |
cuenta_ingreso_codigo | text | Default 4201002 |
fuente_url | text | URL de la circular SII vigente |
estado | text | PENDIENTE o CONTABILIZADO |
fecha_contabilizacion, fecha_reversa | timestamp | Audit |
Convención de cuentas default
Section titled “Convención de cuentas default”| Código | Cuenta | Rol |
|---|---|---|
1108001 | PPM por recuperar | Activo donde se acumula el PPM disponible como crédito anual |
4201002 | Ingreso por corrección monetaria PPM | Ingreso 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.
Por qué mes_destino = 12
Section titled “Por qué mes_destino = 12”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í.