| | | 1 | | using FAU.Entidades; |
| | | 2 | | using FAU.Logica.DTOs; |
| | | 3 | | |
| | | 4 | | namespace FAU.Logica.Calculadores; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Clase base de todas las calculadoras de haberes. |
| | | 8 | | /// |
| | | 9 | | /// Flujo de cálculo (basado en SUM33100.PRG FoxPro): |
| | | 10 | | /// 1. CalcularItemsBase → ítems propios de cada situación, retorna wpara53 (SIN SDL) |
| | | 11 | | /// 2. AgregarItemsDecreto → ítems fijos por grado (decretos + recuperaciones + etc.), |
| | | 12 | | /// actualiza wpara53 si el ítem entra en la base SDL |
| | | 13 | | /// 3. CalcularItemsCondicionales → Riesgo de Vuelo, Progresivo, etc. |
| | | 14 | | /// 4. CalcularSDL → 048.012 = wpara53 × 5,3% (DESPUÉS de todos los ítems base) |
| | | 15 | | /// 5. CalcularDI → 043.004 = wPara10 × porcentaje (virtual, override por situación) |
| | | 16 | | /// 6. CalcularModAportes19695 → 042.413 (solo Ley Nueva) |
| | | 17 | | /// 7. CalcularReintegroAportes → 042.414 (solo Ley Nueva) |
| | | 18 | | /// </summary> |
| | | 19 | | public abstract class CalculadorHaberesBase : ICalculadorHaberes |
| | | 20 | | { |
| | | 21 | | /// <summary>Porcentaje de Dedicación Integral. Override por subclase.</summary> |
| | 3 | 22 | | protected virtual decimal PorcentajeDedicacionIntegral => 0.10m; // 10% Personal Superior |
| | | 23 | | |
| | | 24 | | public Task<ResultadoCalculo> CalcularAsync( |
| | | 25 | | RelacionLaboral funcionario, InsumosPeriodo insumos, Acumuladores acumuladores) |
| | | 26 | | { |
| | 53 | 27 | | var resultado = new ResultadoCalculo(); |
| | | 28 | | try |
| | | 29 | | { |
| | | 30 | | // 1. Ítems base de la situación (SIN SDL) |
| | 53 | 31 | | var wpara53 = CalcularItemsBase(funcionario, insumos, acumuladores, resultado); |
| | | 32 | | |
| | | 33 | | // 2. Decretos y recuperaciones fijas por grado |
| | | 34 | | // Retorna el extra que TAMBIÉN entra en la base SDL |
| | 51 | 35 | | wpara53 += AgregarItemsDecreto(funcionario, insumos, acumuladores, resultado); |
| | | 36 | | |
| | | 37 | | // 3. Ítems condicionales (Riesgo Vuelo, Progresivo, etc.) |
| | | 38 | | // También pueden sumar a wpara53 |
| | 51 | 39 | | wpara53 += AgregarItemsCondicionales(funcionario, insumos, acumuladores, resultado); |
| | | 40 | | |
| | | 41 | | // 4. SDL 5,3% — calculado con TODA la base |
| | 51 | 42 | | if (wpara53 > 0) |
| | | 43 | | { |
| | 48 | 44 | | var sdl = Math.Round(wpara53 * 0.053m, 2); |
| | 48 | 45 | | AgregarItem(48012, sdl, insumos, acumuladores, resultado); |
| | | 46 | | |
| | 48 | 47 | | var wPara10 = wpara53 + sdl; |
| | | 48 | | |
| | | 49 | | // 5. Dedicación Integral (virtual — cada situación usa su porcentaje) |
| | 48 | 50 | | CalcularDedicacionIntegral(wPara10, funcionario, insumos, acumuladores, resultado); |
| | | 51 | | } |
| | | 52 | | |
| | | 53 | | // 6. Mod. Aportes Ley 19.695 (solo Ley Nueva) |
| | 51 | 54 | | if (!funcionario.Regimen.EsLeyVieja) |
| | | 55 | | { |
| | 20 | 56 | | CalcularModAportes19695(acumuladores, insumos, resultado); |
| | 20 | 57 | | CalcularReintegroAportes(acumuladores, insumos, resultado); |
| | | 58 | | } |
| | | 59 | | |
| | 51 | 60 | | if (!funcionario.Situacion.AfectaCobro) |
| | 10 | 61 | | foreach (var item in resultado.Items) |
| | 4 | 62 | | item.EsReferencia = true; |
| | | 63 | | |
| | 51 | 64 | | resultado.Estado = resultado.Incidencias.Count > 0 |
| | 51 | 65 | | ? EstadoCalculo.Revisar |
| | 51 | 66 | | : EstadoCalculo.Calculado; |
| | 51 | 67 | | } |
| | 2 | 68 | | catch (Exception ex) |
| | | 69 | | { |
| | 2 | 70 | | resultado.Estado = EstadoCalculo.Error; |
| | 2 | 71 | | resultado.MensajeError = ex.Message; |
| | 2 | 72 | | } |
| | | 73 | | |
| | 53 | 74 | | return Task.FromResult(resultado); |
| | | 75 | | } |
| | | 76 | | |
| | | 77 | | // ── Métodos abstractos/virtuales que cada calculadora puede sobreescribir ────── |
| | | 78 | | |
| | | 79 | | /// <summary> |
| | | 80 | | /// Calcula los ítems propios de la situación. |
| | | 81 | | /// NO incluye SDL (048.012) ni DI (043.004) — los maneja la clase base. |
| | | 82 | | /// Retorna wpara53: suma de los ítems que entran en la base del SDL. |
| | | 83 | | /// </summary> |
| | | 84 | | protected abstract decimal CalcularItemsBase( |
| | | 85 | | RelacionLaboral funcionario, InsumosPeriodo insumos, |
| | | 86 | | Acumuladores acumuladores, ResultadoCalculo resultado); |
| | | 87 | | |
| | | 88 | | /// <summary> |
| | | 89 | | /// Agrega decretos y recuperaciones fijas por grado (lookup en remuneraciones_grado). |
| | | 90 | | /// Fórmula SDL PDF: 048.004, 048.023, 048.026 SÍ entran en base SDL. |
| | | 91 | | /// 048.009, 048.015, 048.017, 042.403 NO entran en base SDL. |
| | | 92 | | /// Retorna el extra a sumar a wpara53 (solo ítems que entran en SDL). |
| | | 93 | | /// </summary> |
| | | 94 | | protected virtual decimal AgregarItemsDecreto( |
| | | 95 | | RelacionLaboral funcionario, InsumosPeriodo insumos, |
| | | 96 | | Acumuladores acumuladores, ResultadoCalculo resultado) |
| | | 97 | | { |
| | 48 | 98 | | decimal extraWPara53 = 0m; |
| | | 99 | | |
| | | 100 | | // ── Entran en base SDL (confirmado por fórmula PDF 048.012) ──────────── |
| | 48 | 101 | | extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48004); // Dec.180/985 |
| | 48 | 102 | | extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48023); // Rec.Salarial 2007 |
| | 48 | 103 | | extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48026); // Rec.Salarial 4.66% |
| | 48 | 104 | | extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42103); // Mayor Resp. MDN |
| | 48 | 105 | | extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48038); // Incr. Esc. K |
| | 48 | 106 | | extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42067); // Equipo SS.OO (PDF: en |
| | | 107 | | |
| | | 108 | | // ── NO entran en base SDL (según fórmula PDF) ────────────────────────── |
| | 48 | 109 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48009); // Dec.203/992 |
| | 48 | 110 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48015); // Ley 17.269 |
| | 48 | 111 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48017); // Dec.191/003 |
| | 48 | 112 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48040); // Ley 19.355 |
| | 48 | 113 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48042); // Incr. Sub Esc.K |
| | 48 | 114 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42536); // Comp. Ley 18.834 |
| | 48 | 115 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42403); // Ley 20.075 Art.80 |
| | 48 | 116 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 69006); // Dif. Equipo Oficiales |
| | 48 | 117 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 69010); // Viático por Traslado |
| | 48 | 118 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42571); // Comp. PAN (Policía Aérea Nacional) |
| | 48 | 119 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42572); // Comp. SAR (Búsqueda y Rescate) |
| | | 120 | | // Complemento Dedicación Integral (código catálogo 299=Oficial, 315=Subalterno) |
| | 48 | 121 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 299); // Comp. DI Oficial (043.003) |
| | 48 | 122 | | AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 315); // Comp. DI Subalterno (043.003) |
| | | 123 | | // TODO: confirmar OG para los códigos internos 39 y 154 con DFC si no están alineados |
| | | 124 | | |
| | 48 | 125 | | return extraWPara53; |
| | | 126 | | } |
| | | 127 | | |
| | | 128 | | /// <summary> |
| | | 129 | | /// Ítems condicionales: Riesgo de Vuelo, Progresivo, Prima Técnica, etc. |
| | | 130 | | /// Override por subclase si la situación los tiene. |
| | | 131 | | /// Retorna el extra a sumar a wpara53 (solo ítems en base SDL). |
| | | 132 | | /// </summary> |
| | | 133 | | protected virtual decimal AgregarItemsCondicionales( |
| | | 134 | | RelacionLaboral funcionario, InsumosPeriodo insumos, |
| | | 135 | | Acumuladores acumuladores, ResultadoCalculo resultado) |
| | | 136 | | { |
| | 51 | 137 | | decimal extraWPara53 = 0m; |
| | | 138 | | |
| | | 139 | | // 042.092 — Prima Técnica PTI/PT (categorías A/B/C designadas por Orden del Comando) |
| | | 140 | | // El campo prima_tecnica de RelacionLaboral indica si aplica ('A','B','C' o 'VACIO'). |
| | | 141 | | // El monto viene de remuneraciones_grado por grado (DFC carga el monto correcto). |
| | | 142 | | // Sujeto a Montepío. TODO: implementar diferenciación por categoría si los montos difieren. |
| | 51 | 143 | | if (!string.IsNullOrEmpty(funcionario.PrimaTecnica) && funcionario.PrimaTecnica != "VACIO") |
| | | 144 | | { |
| | 3 | 145 | | var remPT = insumos.ObtenerRemuneracion(funcionario.GradoId, 42092); |
| | 3 | 146 | | if (remPT is not null) |
| | 2 | 147 | | AgregarItem(42092, remPT.Monto, insumos, acumuladores, resultado); |
| | | 148 | | } |
| | | 149 | | |
| | | 150 | | // 042.093 — Prima Idoneidad (sin aporte), si el funcionario tiene superprima. |
| | | 151 | | // No entra en la base del SDL (igual que 042.092). Legacy SUM33306 (cod 221). |
| | 51 | 152 | | if (funcionario.Superprima) |
| | | 153 | | { |
| | 1 | 154 | | var remIdoneidad = insumos.ObtenerRemuneracion(funcionario.GradoId, 42093); |
| | 1 | 155 | | if (remIdoneidad is not null) |
| | 1 | 156 | | AgregarItem(42093, remIdoneidad.Monto, insumos, acumuladores, resultado); |
| | | 157 | | } |
| | | 158 | | |
| | | 159 | | // 042.025 — Prima Técnica Permanente |
| | | 160 | | // Confirmado DFC 2026-06-10: "Se calcula automáticamente" para SOM y Sgto.1ro. |
| | | 161 | | // La data-driven check: si el grado tiene remuneración para 42025, se emite. |
| | | 162 | | // No entra en base SDL (no figura en fórmula de 048.012). |
| | 51 | 163 | | var remPrimaTec = insumos.ObtenerRemuneracion(funcionario.GradoId, 42025); |
| | 51 | 164 | | if (remPrimaTec is not null) |
| | 0 | 165 | | AgregarItem(42025, remPrimaTec.Monto, insumos, acumuladores, resultado); |
| | | 166 | | |
| | | 167 | | // 042.019 — Riesgo de Vuelo (si relacion_laboral.riesgo_vuelo > 0) |
| | | 168 | | // PDF: SÍ entra en base SDL y en base DI |
| | 51 | 169 | | if (funcionario.RiesgoVuelo.HasValue && funcionario.RiesgoVuelo.Value > 0) |
| | | 170 | | { |
| | 0 | 171 | | var remVuelo = insumos.ObtenerRemuneracion(funcionario.GradoId, 42019); |
| | 0 | 172 | | if (remVuelo is not null) |
| | | 173 | | { |
| | 0 | 174 | | AgregarItem(42019, remVuelo.Monto, insumos, acumuladores, resultado); |
| | 0 | 175 | | extraWPara53 += remVuelo.Monto; |
| | | 176 | | } |
| | | 177 | | } |
| | | 178 | | |
| | | 179 | | // 044.001 — Progresivo / Prima por Antigüedad |
| | | 180 | | // Equivalente a Personal.Anios_Trop del FoxPro: |
| | | 181 | | // años de servicio efectivo = (fecha_periodo - fecha_base) - anios_inactivos |
| | | 182 | | // Fecha base: oficiales cuentan desde la fecha de ascenso a oficial; tropa desde el ingreso. |
| | | 183 | | // (FechaAscensoOficial es NULL para tropa → se usa FechaInicio.) |
| | | 184 | | // anios_inactivos: períodos de baja/inactividad ya registrados en relacion_laboral |
| | | 185 | | // Fórmula: min(años_efectivos, 30) × monto_unitario |
| | 51 | 186 | | var fechaPeriodo2 = new DateTime(insumos.ParametroPeriodo.Anio, insumos.ParametroPeriodo.Mes, 1); |
| | 51 | 187 | | var fechaBaseProgresivo = funcionario.FechaAscensoOficial ?? funcionario.FechaInicio; |
| | 51 | 188 | | var aniosBrutos = Math.Max(0, (int)((fechaPeriodo2 - fechaBaseProgresivo).TotalDays / 365.25)); |
| | 51 | 189 | | var anios = Math.Max(0, aniosBrutos - (funcionario.AniosInactivos ?? 0)); |
| | 51 | 190 | | if (anios > 0) |
| | | 191 | | { |
| | 51 | 192 | | var remProg = insumos.ObtenerRemuneracion(funcionario.GradoId, 44001); |
| | 51 | 193 | | if (remProg is not null) |
| | | 194 | | { |
| | 2 | 195 | | var acum = Math.Min(anios, 30); |
| | 2 | 196 | | var montoProg = remProg.Monto * acum; |
| | 2 | 197 | | AgregarItem(44001, montoProg, insumos, acumuladores, resultado); |
| | 2 | 198 | | extraWPara53 += montoProg; |
| | | 199 | | } |
| | | 200 | | } |
| | | 201 | | |
| | 51 | 202 | | return extraWPara53; |
| | | 203 | | } |
| | | 204 | | |
| | | 205 | | /// <summary> |
| | | 206 | | /// Calcula Dedicación Integral (043.004). |
| | | 207 | | /// Override en subclases que usan porcentaje distinto o ítem diferente (p.ej. PAO). |
| | | 208 | | /// </summary> |
| | | 209 | | protected virtual void CalcularDedicacionIntegral( |
| | | 210 | | decimal wPara10, RelacionLaboral funcionario, |
| | | 211 | | InsumosPeriodo insumos, Acumuladores acumuladores, ResultadoCalculo resultado) |
| | | 212 | | { |
| | 39 | 213 | | if (wPara10 <= 0) return; |
| | 39 | 214 | | var di = Math.Round(wPara10 * PorcentajeDedicacionIntegral, 2); |
| | 39 | 215 | | AgregarItem(43004, di, insumos, acumuladores, resultado); |
| | 39 | 216 | | } |
| | | 217 | | |
| | | 218 | | // ── Helpers internos ────────────────────────────────────────────────────────── |
| | | 219 | | |
| | | 220 | | /// <summary>Agrega el ítem si existe en remuneraciones. Retorna el monto agregado (o 0).</summary> |
| | | 221 | | protected static decimal AgregarSiExiste( |
| | | 222 | | RelacionLaboral funcionario, InsumosPeriodo insumos, |
| | | 223 | | Acumuladores acumuladores, ResultadoCalculo resultado, int codigo) |
| | | 224 | | { |
| | 912 | 225 | | var rem = insumos.ObtenerRemuneracion(funcionario.GradoId, codigo); |
| | 1815 | 226 | | if (rem is null) return 0m; |
| | 9 | 227 | | AgregarItem(codigo, rem.Monto, insumos, acumuladores, resultado); |
| | 9 | 228 | | return rem.Monto; |
| | | 229 | | } |
| | | 230 | | |
| | | 231 | | protected static void AgregarItem( |
| | | 232 | | int codigo, decimal monto, InsumosPeriodo insumos, |
| | | 233 | | Acumuladores acumuladores, ResultadoCalculo resultado) |
| | | 234 | | { |
| | 1960 | 235 | | var catalogoItem = insumos.CatalogoItems.FirstOrDefault(c => c.Codigo == codigo); |
| | 238 | 236 | | resultado.Items.Add(new ItemCalculado |
| | 238 | 237 | | { |
| | 238 | 238 | | CodigoConcepto = codigo, |
| | 238 | 239 | | NombreConcepto = catalogoItem?.Nombre ?? $"Concepto {codigo}", |
| | 238 | 240 | | Importe = monto |
| | 238 | 241 | | }); |
| | 238 | 242 | | if (catalogoItem is not null) |
| | 233 | 243 | | acumuladores.AgregarItem(monto, catalogoItem); |
| | 238 | 244 | | } |
| | | 245 | | |
| | | 246 | | // No se necesita FechaBaseProgresivo — ver AgregarItemsCondicionales donde se calcula |
| | | 247 | | // Anios_Trop = años desde fecha_inicio MENOS anios_inactivos (campo en relacion_laboral) |
| | | 248 | | |
| | | 249 | | protected static int CalcularAniosServicio(RelacionLaboral funcionario, InsumosPeriodo insumos) |
| | | 250 | | { |
| | 48 | 251 | | var fechaPeriodo = new DateTime(insumos.ParametroPeriodo.Anio, insumos.ParametroPeriodo.Mes, 1); |
| | 48 | 252 | | return Math.Max(0, (int)((fechaPeriodo - funcionario.FechaInicio).TotalDays / 365.25)); |
| | | 253 | | } |
| | | 254 | | |
| | | 255 | | private static void CalcularModAportes19695( |
| | | 256 | | Acumuladores acumuladores, InsumosPeriodo insumos, ResultadoCalculo resultado) |
| | | 257 | | { |
| | 112 | 258 | | var monto048042 = resultado.Items.Where(i => i.CodigoConcepto == 48042).Sum(i => i.Importe); |
| | 111 | 259 | | var monto042403 = resultado.Items.Where(i => i.CodigoConcepto == 42403).Sum(i => i.Importe); |
| | 20 | 260 | | var baseCalculo = acumuladores.WNominal - monto048042 - monto042403; |
| | 21 | 261 | | if (baseCalculo <= 0) return; |
| | 19 | 262 | | var monto = Math.Round(baseCalculo * 0.0235m, 2); |
| | 19 | 263 | | AgregarItem(42413, monto, insumos, acumuladores, resultado); |
| | 19 | 264 | | } |
| | | 265 | | |
| | | 266 | | /// <summary> |
| | | 267 | | /// 042.414 — Reintegro de Aportes por Cambio de Régimen de Aportación. |
| | | 268 | | /// Fórmula PDF: Σ(069.006 + 069.008 + 069.010 + 042.092×2 + 043.002 + 042.571 + 042.572) / 0.87 × 13% |
| | | 269 | | /// 042.092 cuenta doble porque el régimen nuevo aplica aporte doble sobre esa compensación. |
| | | 270 | | /// Los insumos deben haberse emitido previamente en resultado.Items. |
| | | 271 | | /// Solo aplica en régimen Ley Nueva (llamado desde CalcularAsync junto con 042.413). |
| | | 272 | | /// </summary> |
| | | 273 | | private static void CalcularReintegroAportes( |
| | | 274 | | Acumuladores acumuladores, InsumosPeriodo insumos, ResultadoCalculo resultado) |
| | | 275 | | { |
| | 906 | 276 | | decimal Suma(int codigo) => resultado.Items.Where(i => i.CodigoConcepto == codigo).Sum(i => i.Importe); |
| | | 277 | | |
| | 20 | 278 | | var base042414 = |
| | 20 | 279 | | Suma(69006) + // 069.006 Dif. Equipo Oficiales |
| | 20 | 280 | | Suma(69008) + // 069.008 Viático de Alimentación |
| | 20 | 281 | | Suma(69010) + // 069.010 Viático por Traslado |
| | 20 | 282 | | Suma(42092) * 2m + // 042.092 Prima Técnica (×2 según fórmula PDF) |
| | 20 | 283 | | Suma(43002) + // 043.002 Complemento DI |
| | 20 | 284 | | Suma(42571) + // 042.571 Comp. PAN |
| | 20 | 285 | | Suma(42572); // 042.572 Comp. SAR |
| | | 286 | | |
| | 38 | 287 | | if (base042414 <= 0m) return; |
| | 2 | 288 | | var monto = Math.Round(base042414 / 0.87m * 0.13m, 2); |
| | 2 | 289 | | AgregarItem(42414, monto, insumos, acumuladores, resultado); |
| | 2 | 290 | | } |
| | | 291 | | } |