< Summary

Information
Class: FAU.Logica.Calculadores.CalculadorHaberesBase
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Calculadores/CalculadorHaberesBase.cs
Line coverage
95%
Covered lines: 110
Uncovered lines: 5
Coverable lines: 115
Total lines: 291
Line coverage: 95.6%
Branch coverage
89%
Covered branches: 43
Total branches: 48
Branch coverage: 89.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_PorcentajeDedicacionIntegral()100%11100%
CalcularAsync(...)100%1010100%
AgregarItemsDecreto(...)100%11100%
AgregarItemsCondicionales(...)83.33%272482.75%
CalcularDedicacionIntegral(...)50%22100%
AgregarSiExiste(...)100%22100%
AgregarItem(...)100%66100%
CalcularAniosServicio(...)100%11100%
CalcularModAportes19695(...)100%22100%
Suma()100%11100%
CalcularReintegroAportes(...)100%22100%

File(s)

/home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Calculadores/CalculadorHaberesBase.cs

#LineLine coverage
 1using FAU.Entidades;
 2using FAU.Logica.DTOs;
 3
 4namespace 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>
 19public abstract class CalculadorHaberesBase : ICalculadorHaberes
 20{
 21    /// <summary>Porcentaje de Dedicación Integral. Override por subclase.</summary>
 322    protected virtual decimal PorcentajeDedicacionIntegral => 0.10m; // 10% Personal Superior
 23
 24    public Task<ResultadoCalculo> CalcularAsync(
 25        RelacionLaboral funcionario, InsumosPeriodo insumos, Acumuladores acumuladores)
 26    {
 5327        var resultado = new ResultadoCalculo();
 28        try
 29        {
 30            // 1. Ítems base de la situación (SIN SDL)
 5331            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
 5135            wpara53 += AgregarItemsDecreto(funcionario, insumos, acumuladores, resultado);
 36
 37            // 3. Ítems condicionales (Riesgo Vuelo, Progresivo, etc.)
 38            //    También pueden sumar a wpara53
 5139            wpara53 += AgregarItemsCondicionales(funcionario, insumos, acumuladores, resultado);
 40
 41            // 4. SDL 5,3% — calculado con TODA la base
 5142            if (wpara53 > 0)
 43            {
 4844                var sdl = Math.Round(wpara53 * 0.053m, 2);
 4845                AgregarItem(48012, sdl, insumos, acumuladores, resultado);
 46
 4847                var wPara10 = wpara53 + sdl;
 48
 49                // 5. Dedicación Integral (virtual — cada situación usa su porcentaje)
 4850                CalcularDedicacionIntegral(wPara10, funcionario, insumos, acumuladores, resultado);
 51            }
 52
 53            // 6. Mod. Aportes Ley 19.695 (solo Ley Nueva)
 5154            if (!funcionario.Regimen.EsLeyVieja)
 55            {
 2056                CalcularModAportes19695(acumuladores, insumos, resultado);
 2057                CalcularReintegroAportes(acumuladores, insumos, resultado);
 58            }
 59
 5160            if (!funcionario.Situacion.AfectaCobro)
 1061                foreach (var item in resultado.Items)
 462                    item.EsReferencia = true;
 63
 5164            resultado.Estado = resultado.Incidencias.Count > 0
 5165                ? EstadoCalculo.Revisar
 5166                : EstadoCalculo.Calculado;
 5167        }
 268        catch (Exception ex)
 69        {
 270            resultado.Estado = EstadoCalculo.Error;
 271            resultado.MensajeError = ex.Message;
 272        }
 73
 5374        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    {
 4898        decimal extraWPara53 = 0m;
 99
 100        // ── Entran en base SDL (confirmado por fórmula PDF 048.012) ────────────
 48101        extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48004);  // Dec.180/985
 48102        extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48023);  // Rec.Salarial 2007
 48103        extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48026);  // Rec.Salarial 4.66%
 48104        extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42103);  // Mayor Resp. MDN
 48105        extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48038);  // Incr. Esc. K
 48106        extraWPara53 += AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42067);  // Equipo SS.OO (PDF: en
 107
 108        // ── NO entran en base SDL (según fórmula PDF) ──────────────────────────
 48109        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48009);  // Dec.203/992
 48110        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48015);  // Ley 17.269
 48111        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48017);  // Dec.191/003
 48112        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48040);  // Ley 19.355
 48113        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 48042);  // Incr. Sub Esc.K
 48114        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42536);  // Comp. Ley 18.834
 48115        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42403);  // Ley 20.075 Art.80
 48116        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 69006);  // Dif. Equipo Oficiales
 48117        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 69010);  // Viático por Traslado
 48118        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 42571);  // Comp. PAN (Policía Aérea Nacional)
 48119        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)
 48121        AgregarSiExiste(funcionario, insumos, acumuladores, resultado, 299);    // Comp. DI Oficial (043.003)
 48122        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
 48125        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    {
 51137        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.
 51143        if (!string.IsNullOrEmpty(funcionario.PrimaTecnica) && funcionario.PrimaTecnica != "VACIO")
 144        {
 3145            var remPT = insumos.ObtenerRemuneracion(funcionario.GradoId, 42092);
 3146            if (remPT is not null)
 2147                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).
 51152        if (funcionario.Superprima)
 153        {
 1154            var remIdoneidad = insumos.ObtenerRemuneracion(funcionario.GradoId, 42093);
 1155            if (remIdoneidad is not null)
 1156                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).
 51163        var remPrimaTec = insumos.ObtenerRemuneracion(funcionario.GradoId, 42025);
 51164        if (remPrimaTec is not null)
 0165            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
 51169        if (funcionario.RiesgoVuelo.HasValue && funcionario.RiesgoVuelo.Value > 0)
 170        {
 0171            var remVuelo = insumos.ObtenerRemuneracion(funcionario.GradoId, 42019);
 0172            if (remVuelo is not null)
 173            {
 0174                AgregarItem(42019, remVuelo.Monto, insumos, acumuladores, resultado);
 0175                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
 51186        var fechaPeriodo2 = new DateTime(insumos.ParametroPeriodo.Anio, insumos.ParametroPeriodo.Mes, 1);
 51187        var fechaBaseProgresivo = funcionario.FechaAscensoOficial ?? funcionario.FechaInicio;
 51188        var aniosBrutos = Math.Max(0, (int)((fechaPeriodo2 - fechaBaseProgresivo).TotalDays / 365.25));
 51189        var anios = Math.Max(0, aniosBrutos - (funcionario.AniosInactivos ?? 0));
 51190        if (anios > 0)
 191        {
 51192            var remProg = insumos.ObtenerRemuneracion(funcionario.GradoId, 44001);
 51193            if (remProg is not null)
 194            {
 2195                var acum = Math.Min(anios, 30);
 2196                var montoProg = remProg.Monto * acum;
 2197                AgregarItem(44001, montoProg, insumos, acumuladores, resultado);
 2198                extraWPara53 += montoProg;
 199            }
 200        }
 201
 51202        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    {
 39213        if (wPara10 <= 0) return;
 39214        var di = Math.Round(wPara10 * PorcentajeDedicacionIntegral, 2);
 39215        AgregarItem(43004, di, insumos, acumuladores, resultado);
 39216    }
 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    {
 912225        var rem = insumos.ObtenerRemuneracion(funcionario.GradoId, codigo);
 1815226        if (rem is null) return 0m;
 9227        AgregarItem(codigo, rem.Monto, insumos, acumuladores, resultado);
 9228        return rem.Monto;
 229    }
 230
 231    protected static void AgregarItem(
 232        int codigo, decimal monto, InsumosPeriodo insumos,
 233        Acumuladores acumuladores, ResultadoCalculo resultado)
 234    {
 1960235        var catalogoItem = insumos.CatalogoItems.FirstOrDefault(c => c.Codigo == codigo);
 238236        resultado.Items.Add(new ItemCalculado
 238237        {
 238238            CodigoConcepto = codigo,
 238239            NombreConcepto = catalogoItem?.Nombre ?? $"Concepto {codigo}",
 238240            Importe = monto
 238241        });
 238242        if (catalogoItem is not null)
 233243            acumuladores.AgregarItem(monto, catalogoItem);
 238244    }
 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    {
 48251        var fechaPeriodo = new DateTime(insumos.ParametroPeriodo.Anio, insumos.ParametroPeriodo.Mes, 1);
 48252        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    {
 112258        var monto048042 = resultado.Items.Where(i => i.CodigoConcepto == 48042).Sum(i => i.Importe);
 111259        var monto042403 = resultado.Items.Where(i => i.CodigoConcepto == 42403).Sum(i => i.Importe);
 20260        var baseCalculo = acumuladores.WNominal - monto048042 - monto042403;
 21261        if (baseCalculo <= 0) return;
 19262        var monto = Math.Round(baseCalculo * 0.0235m, 2);
 19263        AgregarItem(42413, monto, insumos, acumuladores, resultado);
 19264    }
 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    {
 906276        decimal Suma(int codigo) => resultado.Items.Where(i => i.CodigoConcepto == codigo).Sum(i => i.Importe);
 277
 20278        var base042414 =
 20279            Suma(69006)   +    // 069.006 Dif. Equipo Oficiales
 20280            Suma(69008)   +    // 069.008 Viático de Alimentación
 20281            Suma(69010)   +    // 069.010 Viático por Traslado
 20282            Suma(42092) * 2m + // 042.092 Prima Técnica (×2 según fórmula PDF)
 20283            Suma(43002)   +    // 043.002 Complemento DI
 20284            Suma(42571)   +    // 042.571 Comp. PAN
 20285            Suma(42572);       // 042.572 Comp. SAR
 286
 38287        if (base042414 <= 0m) return;
 2288        var monto = Math.Round(base042414 / 0.87m * 0.13m, 2);
 2289        AgregarItem(42414, monto, insumos, acumuladores, resultado);
 2290    }
 291}