< Summary

Information
Class: FAU.Logica.Services.LiquidacionService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/LiquidacionService.cs
Line coverage
84%
Covered lines: 412
Uncovered lines: 73
Coverable lines: 485
Total lines: 747
Line coverage: 84.9%
Branch coverage
85%
Covered branches: 128
Total branches: 150
Branch coverage: 85.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
GenerarLiquidacionAsync()85.36%968287.37%
ObtenerEstadosAsync()100%210%
ObtenerItemsAsync()100%210%
ObtenerResumenAsync()100%210%
CalcularAportesLegalesAsync()100%44100%
CalcularIrpfAsync()87.5%8896.42%
CrearDeduccionesIrpfAsync()100%11100%
CrearItemsDescuentosPersonalesAsync()91.66%121296.29%
CrearEntradaDeficit(...)50%4481.81%
CrearItemsAportesLegales(...)100%11100%
AgregarItemAporteLegal(...)100%44100%
BuscarCatalogoItem(...)100%44100%
CrearItemLiquidacion(...)50%22100%
CrearItemLiquidacion(...)100%11100%
CalcularSueldoBaseParaTraslado(...)100%66100%
get_Montepio()100%11100%
get_Total()100%11100%
get_TotalIrpf()100%11100%

File(s)

/home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/LiquidacionService.cs

#LineLine coverage
 1using FAU.DataAccess;
 2using FAU.DataAccess.Repositories;
 3using FAU.Entidades;
 4using FAU.Logica.Calculadores;
 5using FAU.Logica.Calculators;
 6using FAU.Logica.DTOs;
 7using Microsoft.EntityFrameworkCore;
 8
 9namespace FAU.Logica.Services;
 10
 11public class LiquidacionService : ILiquidacionService
 12{
 13    private const int CodigoIrpf = 517;
 14    private const int CodigoMontepioSubalterno = 550;
 15    private const int CodigoSanidad = 520;
 16    private const string CodigoFrl = "FRL";
 17
 18    // Mapeo TipoCompensacion.Codigo â†’ código OG del catálogo
 119    private static readonly IReadOnlyDictionary<string, int> CodigosCompensacion =
 120        new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
 121        {
 122            ["PAN"]             = 42571,
 123            ["SAR"]             = 42572,
 124            ["VUELO"]           = 42553,
 125            ["ASIDUIDAD_VUELO"] = 42553,
 126            ["FRONTERA"]        = 42573,
 127            ["NOCTURNIDAD"]     = 52001,
 128            ["BARRERA"]         = 42511,
 129            ["CUSTODIA"]        = 42000,
 130        };
 31
 32    private readonly ApplicationDbContext _context;
 33    private readonly IAuditoriaService _auditoriaService;
 34    private readonly ICurrentUserContext _currentUser;
 35    private readonly IInsumosPeriodoLoader _insumosLoader;
 36    private readonly IProcesadorBeneficiosService _procesadorBeneficios;
 37    // Calculadoras de haberes base
 38    private readonly ICalculadorFactory _calculadorFactory;
 39    private readonly ICalcularDiferencia615Service _diferencia615Service;
 40    // Descuentos legales, IRPF y déficit
 41    private readonly IDescuentoPersonalRepository _descuentoPersonalRepository;
 42    private readonly IDependienteRepository _dependienteRepository;
 43    private readonly IDeficitCalculator _deficitCalculator;
 44    private readonly IDescuentosLegalesCalculator _descuentosLegalesCalculator;
 45    private readonly IIrpfService _irpfService;
 46
 3447    public LiquidacionService(
 3448        ApplicationDbContext context,
 3449        IAuditoriaService auditoriaService,
 3450        ICurrentUserContext currentUser,
 3451        IInsumosPeriodoLoader insumosLoader,
 3452        IProcesadorBeneficiosService procesadorBeneficios,
 3453        ICalculadorFactory calculadorFactory,
 3454        ICalcularDiferencia615Service diferencia615Service,
 3455        IDescuentoPersonalRepository descuentoPersonalRepository,
 3456        IDependienteRepository dependienteRepository,
 3457        IDeficitCalculator deficitCalculator,
 3458        IDescuentosLegalesCalculator descuentosLegalesCalculator,
 3459        IIrpfService irpfService)
 60    {
 3461        _context = context;
 3462        _auditoriaService = auditoriaService;
 3463        _currentUser = currentUser;
 3464        _insumosLoader = insumosLoader;
 3465        _procesadorBeneficios = procesadorBeneficios;
 3466        _calculadorFactory = calculadorFactory;
 3467        _diferencia615Service = diferencia615Service;
 3468        _descuentoPersonalRepository = descuentoPersonalRepository;
 3469        _dependienteRepository = dependienteRepository;
 3470        _deficitCalculator = deficitCalculator;
 3471        _descuentosLegalesCalculator = descuentosLegalesCalculator;
 3472        _irpfService = irpfService;
 3473    }
 74
 75    public async Task<(bool Exito, string? Error)> GenerarLiquidacionAsync(long periodoId, int version)
 76    {
 3477        var periodo = await _context.PeriodosLiquidacion
 3478            .AsNoTracking()
 3479            .FirstOrDefaultAsync(p => p.Id == periodoId && p.Activo);
 80
 3481        if (periodo is null)
 182            return (false, "Período de liquidación no encontrado o no está activo");
 83
 3384        if (version <= 0)
 185            return (false, "La versión debe ser mayor a cero");
 86
 3287        var existItems = await _context.LiquidacionItems
 3288            .AnyAsync(li => li.PeriodoId == periodoId && li.Version == version);
 89
 3290        if (existItems)
 191            return (false, "Ya existe una liquidación generada para ese período y versión");
 92
 93        InsumosPeriodo insumos;
 94        try
 95        {
 3196            insumos = await _insumosLoader.CargarAsync(periodoId);
 3197        }
 098        catch (InvalidOperationException ex) when (ex.Message.Contains("arámetro"))
 99        {
 0100            return (false, $"No se encontraron parámetros para el período {periodo.Anio}/{periodo.Mes}");
 101        }
 102
 103        // Usamos el mismo patrón de Join que funciona con el HasConversion de Estado enum→string
 31104        var rlRaw = await _context.RelacionesLaborales
 31105            .AsNoTracking()
 31106            .Where(r => r.Estado == EstadoRelacion.Activo)
 31107            .Join(
 31108                _context.Personas.AsNoTracking(),
 31109                r => r.PersonaId,
 31110                p => p.Id,
 31111                (r, p) => new { RL = r, Persona = p })
 31112            .ToListAsync();
 113
 31114        if (!rlRaw.Any())
 1115            return (false, "No se encontraron relaciones laborales activas para generar la liquidación");
 116
 117        // Cargar navigation properties necesarias para el pipeline de calculadoras
 61118        var situacionIds = rlRaw.Select(x => x.RL.SituacionId).Distinct().ToHashSet();
 61119        var regimenIds   = rlRaw.Select(x => x.RL.RegimenId).Distinct().ToHashSet();
 120
 30121        var situacionesDict = await _context.Situaciones.AsNoTracking()
 30122            .Where(s => situacionIds.Contains(s.Id))
 45123            .ToDictionaryAsync(s => s.Id);
 124
 30125        var regimenesDict = await _context.Regimenes.AsNoTracking()
 30126            .Where(r => regimenIds.Contains(r.Id))
 47127            .ToDictionaryAsync(r => r.Id);
 128
 129        // Materializar RelacionLaboral con navigation properties resueltas en memoria
 30130        var relacionesLaborales = rlRaw.Select(x =>
 30131        {
 31132            x.RL.Persona = x.Persona;
 31133            x.RL.Situacion = situacionesDict.GetValueOrDefault(x.RL.SituacionId)!;
 31134            x.RL.Regimen   = regimenesDict.GetValueOrDefault(x.RL.RegimenId)!;
 31135            return x.RL;
 30136        }).ToList();
 137
 30138        var primerDiaPeriodo = new DateOnly(periodo.Anio, periodo.Mes, 1);
 30139        var itemsToSave = new List<LiquidacionItem>();
 30140        var incidenciasToSave = new List<IncidenciaCalculo>();
 30141        var estadosPersonal = new Dictionary<string, string>();
 142
 143        // Pre-load historico de primas únicas en una sola consulta
 61144        var cedulasPersonas = relacionesLaborales.Select(r => r.Persona.Cedula).ToHashSet();
 30145        var itemsHistoricosGlobal = await _context.LiquidacionItems
 30146            .AsNoTracking()
 30147            .Where(li => cedulasPersonas.Contains(li.Cedula) &&
 30148                         (li.CodigoConcepto == "340" || li.CodigoConcepto == "350" || li.CodigoConcepto == "370"))
 30149            .ToListAsync();
 150
 151        // Índice de catálogo por código (usado por helpers de aportes legales)
 30152        var catalogoPorCodigo = insumos.CatalogoItems
 199153            .GroupBy(i => i.Codigo)
 382154            .ToDictionary(g => g.Key, g => g.First());
 155
 122156        foreach (var rl in relacionesLaborales)
 157        {
 31158            var cedula = rl.Persona.Cedula;
 159            try
 160            {
 31161                var acumuladores = new Acumuladores();
 31162                var itemsHaberes = new List<ItemCalculado>();
 31163                var itemsBeneficios = new List<ItemBeneficioCalculado>();
 164
 165                // --- Paso 1-3: Calculador de haberes base ---
 31166                var calculador = rl.Situacion?.Calculador is not null
 31167                    ? _calculadorFactory.Resolver(rl.Situacion.Calculador)
 31168                    : null;
 169
 31170                if (calculador is not null)
 171                {
 15172                    var novedad = insumos.ObtenerNovedad(rl.PersonaId);
 15173                    var factorProrrateo = (novedad is not null && novedad.DiasAfectados < 30)
 15174                        ? novedad.DiasAfectados / 30m
 15175                        : 1m;
 176
 15177                    var resultadoCalculo = await calculador.CalcularAsync(rl, insumos, acumuladores);
 178
 15179                    if (factorProrrateo < 1m)
 180                    {
 24181                        foreach (var item in resultadoCalculo.Items)
 10182                            item.Importe = Math.Round(item.Importe * factorProrrateo, 2);
 183
 2184                        acumuladores.Reiniciar();
 24185                        foreach (var item in resultadoCalculo.Items)
 186                        {
 59187                            var ci = insumos.CatalogoItems.FirstOrDefault(c => c.Codigo == item.CodigoConcepto);
 18188                            if (ci is not null) acumuladores.AgregarItem(item.Importe, ci);
 189                        }
 190                    }
 191
 15192                    itemsHaberes.AddRange(resultadoCalculo.Items);
 193
 30194                    foreach (var inc in resultadoCalculo.Incidencias)
 0195                        incidenciasToSave.Add(new IncidenciaCalculo
 0196                        {
 0197                            PeriodoId = periodoId, Version = version, PersonaId = rl.PersonaId,
 0198                            Tipo = TipoIncidenciaCalculo.CalculoSimplificado,
 0199                            Descripcion = inc, CreadoEn = DateTime.UtcNow
 0200                        });
 201                }
 202                else
 203                {
 16204                    incidenciasToSave.Add(new IncidenciaCalculo
 16205                    {
 16206                        PeriodoId = periodoId, Version = version, PersonaId = rl.PersonaId,
 16207                        Tipo = TipoIncidenciaCalculo.ErrorCalculo,
 16208                        Descripcion = $"Calculador no registrado: {rl.Situacion?.Calculador}",
 16209                        CreadoEn = DateTime.UtcNow
 16210                    });
 211                }
 212
 213                // --- Paso 4: Beneficios sociales ---
 31214                var beneficios = insumos.ObtenerBeneficios(rl.PersonaId);
 31215                var itemsHistoricoPersona = itemsHistoricosGlobal.Where(h => h.Cedula == cedula).ToList();
 31216                var sueldoBasePersona = CalcularSueldoBaseParaTraslado(insumos, rl.GradoId);
 31217                var resultadoBeneficios = _procesadorBeneficios.ProcesarBeneficios(
 31218                    rl.PersonaId, beneficios, insumos.ParametroPeriodo, insumos.CatalogoItems,
 31219                    itemsHistoricoPersona, primerDiaPeriodo,
 31220                    insumos.BeneficiariosPorBeneficio, insumos.InstructivoAsigFamiliar,
 31221                    insumos.InstructivoPsf, insumos.TablaPorHogar,
 31222                    sueldoBaseParaTraslado: sueldoBasePersona);
 31223                itemsBeneficios.AddRange(resultadoBeneficios.Items);
 224
 62225                foreach (var inc in resultadoBeneficios.Incidencias)
 0226                    incidenciasToSave.Add(new IncidenciaCalculo
 0227                    {
 0228                        PeriodoId = periodoId, Version = version, PersonaId = rl.PersonaId,
 0229                        Tipo = inc.Tipo, Descripcion = inc.Descripcion, CreadoEn = DateTime.UtcNow
 0230                    });
 231
 232                // --- Paso 5: Compensaciones por lote ---
 31233                var compensaciones = insumos.ObtenerCompensaciones(rl.PersonaId);
 66234                foreach (var comp in compensaciones)
 235                {
 2236                    var tipoCode = comp.LoteCompensacion?.TipoCompensacion?.Codigo ?? "";
 2237                    if (!CodigosCompensacion.TryGetValue(tipoCode, out var ogCode)) continue;
 238
 2239                    var monto = comp.Monto ?? 0m;
 2240                    if (monto <= 0) continue;
 241
 18242                    var ci = insumos.CatalogoItems.FirstOrDefault(c => c.Codigo == ogCode);
 2243                    itemsHaberes.Add(new ItemCalculado
 2244                    {
 2245                        CodigoConcepto = ogCode,
 2246                        NombreConcepto = ci?.Nombre ?? tipoCode,
 2247                        Importe = monto
 2248                    });
 4249                    if (ci is not null) acumuladores.AgregarItem(monto, ci);
 250                }
 251
 252                // --- Paso 6: Diferencia por ascenso 042.615 ---
 31253                var diffAscenso = insumos.ObtenerDiferenciaAscenso(rl.PersonaId);
 31254                if (diffAscenso is not null && diffAscenso.Monto > 0)
 255                {
 256                    const int Cod615 = 42615;
 9257                    var ci615 = insumos.CatalogoItems.FirstOrDefault(c => c.Codigo == Cod615);
 1258                    itemsHaberes.Add(new ItemCalculado
 1259                    {
 1260                        CodigoConcepto = Cod615,
 1261                        NombreConcepto = ci615?.Nombre ?? "Diferencia por Ascenso (042.615)",
 1262                        Importe = diffAscenso.Monto
 1263                    });
 2264                    if (ci615 is not null) acumuladores.AgregarItem(diffAscenso.Monto, ci615);
 265                }
 266
 267                // --- Paso 7: Aportes legales (Montepío, FONASA, FRL) ---
 268                // baseSdlAportes = WNominal calculador + beneficios grabados (base para %)
 269                // nominalBruto   = WNominal calculador + TODOS los beneficios (base para IRPF y déficit)
 31270                var baseSdlAportes = acumuladores.WNominal + resultadoBeneficios.Items
 23271                    .Where(i => i.Importe > 0m
 23272                                && catalogoPorCodigo.TryGetValue(i.CodigoConcepto, out var ci)
 23273                                && (ci.EsGrabado || ci.ContribuyeWNominal))
 47274                    .Sum(i => i.Importe);
 31275                var nominalBruto = acumuladores.WNominal + resultadoBeneficios.Items
 73276                    .Where(i => i.Importe > 0m).Sum(i => i.Importe);
 277
 31278                var aportes = await CalcularAportesLegalesAsync(rl.RegimenId, baseSdlAportes, insumos);
 31279                var itemsAportesLegales = CrearItemsAportesLegales(periodoId, cedula, version, aportes, catalogoPorCodig
 31280                itemsToSave.AddRange(itemsAportesLegales);
 281
 282                // --- Paso 8: IRPF ---
 31283                var resultadoIrpf = await CalcularIrpfAsync(
 31284                    periodoId, rl.PersonaId, cedula, version,
 31285                    nominalBruto, aportes, insumos, catalogoPorCodigo);
 286
 287                // Descartar entidades IrpfMensual trackeadas si la persistencia falló,
 288                // para que no se reintentan en el SaveChangesAsync final.
 62289                foreach (var entry in _context.ChangeTracker.Entries<IrpfMensual>()
 0290                             .Where(e => e.State == Microsoft.EntityFrameworkCore.EntityState.Added)
 31291                             .ToList())
 0292                    entry.State = Microsoft.EntityFrameworkCore.EntityState.Detached;
 293
 31294                if (resultadoIrpf.Item is not null)
 3295                    itemsToSave.Add(resultadoIrpf.Item);
 296
 297                // --- Paso 9: Descuentos personales con déficit ---
 298                // Excluir códigos ya emitidos como aportes para no contarlos dos veces en el déficit
 31299                var codigosAportesEmitidos = itemsAportesLegales
 75300                    .Where(i => !string.IsNullOrWhiteSpace(i.CodigoConcepto))
 75301                    .Select(i => i.CodigoConcepto)
 31302                    .ToHashSet(StringComparer.OrdinalIgnoreCase);
 31303                var descuentosLegalesEnBeneficios = resultadoBeneficios.Items
 23304                    .Where(i => i.Importe < 0m
 23305                                && catalogoPorCodigo.TryGetValue(i.CodigoConcepto, out var ci)
 23306                                && string.Equals(ci.Tipo, "descuento_legal", StringComparison.OrdinalIgnoreCase)
 23307                                && !codigosAportesEmitidos.Contains(i.CodigoConcepto.ToString()))
 34308                    .Sum(i => Math.Abs(i.Importe));
 31309                var totalDescuentosLegales = aportes.Total + resultadoIrpf.TotalIrpf + descuentosLegalesEnBeneficios;
 31310                var itemsDescuentosPersonales = await CrearItemsDescuentosPersonalesAsync(
 31311                    periodoId, rl.PersonaId, cedula, version,
 31312                    nominalBruto, totalDescuentosLegales);
 31313                itemsToSave.AddRange(itemsDescuentosPersonales);
 314
 315                // --- Paso 10: Estado ---
 31316                var tieneError = incidenciasToSave.Any(i =>
 47317                    i.PersonaId == rl.PersonaId && i.Tipo == TipoIncidenciaCalculo.ErrorCalculo);
 31318                var liquido = nominalBruto - totalDescuentosLegales
 41319                    - itemsDescuentosPersonales.Sum(i => Math.Abs(i.Importe));
 31320                var estado = tieneError ? "ERROR" : (liquido < 0 ? "REVISAR" : "CALCULADO");
 31321                estadosPersonal[cedula] = estado;
 322
 323                // --- Paso 11: Persistir ítems de haberes y beneficios ---
 224324                foreach (var item in itemsHaberes)
 325                {
 81326                    itemsToSave.Add(new LiquidacionItem
 81327                    {
 81328                        PeriodoId = periodoId,
 81329                        Cedula = cedula,
 81330                        Version = version,
 81331                        CodigoConcepto = item.CodigoConcepto.ToString(),
 81332                        NombreConcepto = item.NombreConcepto,
 81333                        Importe = item.Importe,
 81334                        RubroContable = string.Empty,
 81335                        Rubro083 = false,
 81336                        FechaCreacion = DateTime.UtcNow
 81337                    });
 338                }
 108339                foreach (var item in itemsBeneficios)
 340                {
 23341                    itemsToSave.Add(new LiquidacionItem
 23342                    {
 23343                        PeriodoId = periodoId,
 23344                        Cedula = cedula,
 23345                        Version = version,
 23346                        CodigoConcepto = item.CodigoConcepto.ToString(),
 23347                        NombreConcepto = item.NombreConcepto,
 23348                        Importe = item.Importe,
 23349                        RubroContable = item.RubroContable,
 23350                        Rubro083 = false,
 23351                        ClaveEvento = item.ClaveEvento,
 23352                        FechaCreacion = DateTime.UtcNow
 23353                    });
 354                }
 31355            }
 0356            catch (Exception ex)
 357            {
 0358                estadosPersonal[cedula] = "ERROR";
 0359                incidenciasToSave.Add(new IncidenciaCalculo
 0360                {
 0361                    PeriodoId = periodoId,
 0362                    Version = version,
 0363                    PersonaId = rl.PersonaId,
 0364                    Tipo = TipoIncidenciaCalculo.ErrorCalculo,
 0365                    Descripcion = $"Error en pipeline de liquidación: {ex.Message}",
 0366                    CreadoEn = DateTime.UtcNow
 0367                });
 0368            }
 31369        }
 370
 61371        var estados = relacionesLaborales.Select(rl => new LiquidacionEstadoPersonal
 61372        {
 61373            PeriodoId = periodoId,
 61374            Cedula = rl.Persona.Cedula,
 61375            Version = version,
 61376            Estado = estadosPersonal.TryGetValue(rl.Persona.Cedula, out var e) ? e : "PENDIENTE",
 61377            FechaActualizacion = DateTime.UtcNow
 61378        }).ToList();
 379
 30380        await _context.LiquidacionEstadosPersonal.AddRangeAsync(estados);
 381
 30382        if (itemsToSave.Count > 0)
 30383            await _context.LiquidacionItems.AddRangeAsync(itemsToSave);
 384
 30385        if (incidenciasToSave.Count > 0)
 16386            await _context.IncidenciasCalculo.AddRangeAsync(incidenciasToSave);
 387
 222388        var totalImporte = itemsToSave.Sum(i => i.Importe);
 30389        var resumen = new LiquidacionResumen
 30390        {
 30391            PeriodoId = periodoId,
 30392            Version = version,
 30393            TotalImporte = totalImporte,
 30394            Rubro083 = false,
 30395            Descripcion = "Liquidación generada"
 30396        };
 30397        await _context.LiquidacionResumenes.AddAsync(resumen);
 398
 30399        await _context.SaveChangesAsync();
 400
 30401        await _auditoriaService.LogAuditoriaAsync(
 30402            _currentUser.UserId ?? 0,
 30403            AccionEnum.GenerarLiquidacion,
 30404            ContextoEnum.Liquidaciones,
 30405            _currentUser.Host,
 30406            entidad: nameof(LiquidacionEstadoPersonal),
 30407            entidadId: periodoId,
 30408            detalle: new { periodoId, version, registros = estados.Count, items = itemsToSave.Count, incidencias = incid
 409
 30410        return (true, null);
 34411    }
 412
 413    public async Task<IEnumerable<CalculoEstadoDto>> ObtenerEstadosAsync(long periodoId, int version)
 414    {
 0415        return await _context.LiquidacionEstadosPersonal
 0416            .AsNoTracking()
 0417            .Where(e => e.PeriodoId == periodoId && e.Version == version)
 0418            .Select(e => new CalculoEstadoDto
 0419            {
 0420                Cedula = e.Cedula,
 0421                Version = e.Version,
 0422                Estado = e.Estado,
 0423                MensajeError = e.MensajeError
 0424            })
 0425            .ToListAsync();
 0426    }
 427
 428    public async Task<IEnumerable<CalculoItemDto>> ObtenerItemsAsync(long periodoId, int version)
 429    {
 0430        return await _context.LiquidacionItems
 0431            .AsNoTracking()
 0432            .Where(i => i.PeriodoId == periodoId && i.Version == version)
 0433            .Select(i => new CalculoItemDto
 0434            {
 0435                Cedula = i.Cedula,
 0436                Version = i.Version,
 0437                CodigoConcepto = i.CodigoConcepto,
 0438                NombreConcepto = i.NombreConcepto,
 0439                Importe = i.Importe,
 0440                RubroContable = i.RubroContable,
 0441                ObjetoGasto = i.ObjetoGasto
 0442            })
 0443            .ToListAsync();
 0444    }
 445
 446    public async Task<IEnumerable<LiquidacionResumenDto>> ObtenerResumenAsync(long periodoId, int version)
 447    {
 0448        return await _context.LiquidacionResumenes
 0449            .AsNoTracking()
 0450            .Where(r => r.PeriodoId == periodoId && r.Version == version)
 0451            .Select(r => new LiquidacionResumenDto
 0452            {
 0453                Version = r.Version,
 0454                ProgramaId = r.ProgramaId,
 0455                RegimenId = r.RegimenId,
 0456                Rubro083 = r.Rubro083,
 0457                TotalImporte = r.TotalImporte,
 0458                Descripcion = r.Descripcion
 0459            })
 0460            .ToListAsync();
 0461    }
 462
 463    private async Task<AportesLegales> CalcularAportesLegalesAsync(long regimenId, decimal baseSdl, InsumosPeriodo insum
 464    {
 31465        var regimen = await _context.Regimenes.AsNoTracking().FirstOrDefaultAsync(r => r.Id == regimenId);
 31466        var tasaMontepio = regimen is not null && regimen.EsLeyVieja
 31467            ? insumos.ParametroPeriodo.TasaMontepioLeyVieja
 31468            : insumos.ParametroPeriodo.TasaMontepioLeyNueva;
 469
 31470        return new AportesLegales(
 31471            _descuentosLegalesCalculator.CalcularMontepio(baseSdl, tasaMontepio),
 31472            _descuentosLegalesCalculator.CalcularFonasa(baseSdl, insumos.ParametroPeriodo.TasaFonasa),
 31473            _descuentosLegalesCalculator.CalcularFrl(baseSdl, insumos.ParametroPeriodo.TasaFrl));
 31474    }
 475
 476    private async Task<ResultadoIrpfLiquidacion> CalcularIrpfAsync(
 477        long periodoId,
 478        long personaId,
 479        string cedula,
 480        int version,
 481        decimal nominalBruto,
 482        AportesLegales aportes,
 483        InsumosPeriodo insumos,
 484        IReadOnlyDictionary<int, CatalogoItem> catalogoPorCodigo)
 485    {
 31486        var request = new CalcularIrpfRequest
 31487        {
 31488            IngresoGravados = nominalBruto,
 31489            Montepio = aportes.Montepio,
 31490            Fonasa = aportes.Fonasa,
 31491            Frl = aportes.Frl,
 31492            Deducciones = await CrearDeduccionesIrpfAsync(personaId),
 31493            Parametros = new ParametrosIrpfDto
 31494            {
 31495                BpcMensual = insumos.ParametroPeriodo.BpcAnual / 12m,
 31496                UmbralDeduccionBpc = insumos.ParametroPeriodo.UmbralDeduccionBpc,
 31497                TasaDeduccionAlta = insumos.ParametroPeriodo.TasaDeduccionAlta,
 31498                TasaDeduccionBaja = insumos.ParametroPeriodo.TasaDeduccionBaja
 31499            },
 31500            Franjas = insumos.FranjasIrpf
 72501                .Select(f => new FranjaIrpfCalculoDto
 72502                {
 72503                    FranjaId = f.Id,
 72504                    NumeroFranja = f.NumeroFranja,
 72505                    DesdeBpc = f.DesdeBpc,
 72506                    HastaBpc = f.HastaBpc,
 72507                    Tasa = f.Tasa
 72508                })
 31509                .ToList()
 31510        };
 511
 31512        var (exito, idIrpf, _) = await _irpfService.CalcularYPersistirAsync(periodoId, personaId, request);
 31513        if (!exito || !idIrpf.HasValue)
 27514            return new ResultadoIrpfLiquidacion(0m, null);
 515
 4516        var irpf = await _irpfService.ObtenerPorIdAsync(idIrpf.Value);
 4517        if (irpf is null)
 0518            return new ResultadoIrpfLiquidacion(0m, null);
 519
 4520        var item = catalogoPorCodigo.TryGetValue(CodigoIrpf, out var catalogoIrpf)
 4521            ? CrearItemLiquidacion(periodoId, cedula, version, catalogoIrpf, -irpf.TotalIrpf)
 4522            : null;
 523
 4524        return new ResultadoIrpfLiquidacion(irpf.TotalIrpf, item);
 31525    }
 526
 527    private async Task<List<DeduccionDto>> CrearDeduccionesIrpfAsync(long personaId)
 528    {
 31529        var dependientes = await _dependienteRepository.GetDependientesByPersonaAsync(personaId);
 530
 31531        return dependientes
 3532            .Where(d => d.Activo)
 31533            .SelectMany(d =>
 31534            {
 3535                if (string.Equals(d.Tipo, "conyuge_sin_ingresos", StringComparison.OrdinalIgnoreCase))
 1536                    return [new DeduccionDto { Tipo = "conyuge_sin_ingresos", Cantidad = 1 }];
 31537
 2538                if (string.Equals(d.Tipo, "hijo", StringComparison.OrdinalIgnoreCase))
 31539                {
 2540                    var tipoHijo = d.Discapacitado ? "hijo_discapacidad" : "hijo_menor";
 2541                    return [new DeduccionDto { Tipo = tipoHijo, Cantidad = 1 }];
 31542                }
 31543
 0544                return Array.Empty<DeduccionDto>();
 31545            })
 31546            .ToList();
 31547    }
 548
 549    private async Task<IReadOnlyList<LiquidacionItem>> CrearItemsDescuentosPersonalesAsync(
 550        long periodoId,
 551        long personaId,
 552        string cedula,
 553        int version,
 554        decimal nominalBruto,
 555        decimal descuentosLegales)
 556    {
 31557        var descuentosConfirmados = (await _descuentoPersonalRepository
 31558                .GetConfirmadosPorPersonaAsync(periodoId, personaId))
 10559            .Where(d => d.CatalogoItem is not null)
 31560            .ToList();
 561
 31562        if (descuentosConfirmados.Count == 0)
 22563            return Array.Empty<LiquidacionItem>();
 564
 565        // Filtrar descuentos sin acreedor asignado (datos previos a la migración de acreedor_id)
 9566        var descuentosConAcreedor = descuentosConfirmados
 10567            .Where(d => d.AcreedorId is not null && d.Acreedor is not null)
 9568            .ToList();
 569
 9570        if (descuentosConAcreedor.Count == 0)
 0571            return Array.Empty<LiquidacionItem>();
 572
 9573        var entradasDeficit = descuentosConAcreedor
 10574            .Select(d => CrearEntradaDeficit(d, personaId))
 9575            .ToList();
 576
 9577        var resultadosDeficit = await _deficitCalculator.AplicarAsync(
 9578            nominalBruto,
 9579            descuentosLegales,
 9580            entradasDeficit);
 581
 19582        var descuentosPorId = descuentosConAcreedor.ToDictionary(d => d.Id);
 9583        var items = new List<LiquidacionItem>();
 584
 38585        foreach (var resultadoDeficit in resultadosDeficit)
 586        {
 10587            if (resultadoDeficit.DescuentoPeriodoId is null || resultadoDeficit.MontoAplicado <= 0m)
 588                continue;
 589
 10590            if (!descuentosPorId.TryGetValue(resultadoDeficit.DescuentoPeriodoId.Value, out var descuento))
 591                continue;
 592
 10593            items.Add(CrearItemLiquidacion(
 10594                periodoId,
 10595                cedula,
 10596                version,
 10597                descuento.CatalogoItem,
 10598                -resultadoDeficit.MontoAplicado));
 599        }
 600
 9601        return items;
 31602    }
 603
 604    private static DescuentoPersonalInput CrearEntradaDeficit(DescuentoPersonalPeriodo descuento, long personaId)
 605    {
 10606        if (descuento.AcreedorId is null)
 0607            throw new InvalidOperationException($"El descuento personal {descuento.Id} no tiene acreedor asignado.");
 608
 10609        if (descuento.Acreedor is null)
 0610            throw new InvalidOperationException($"El descuento personal {descuento.Id} no tiene acreedor cargado.");
 611
 10612        return new DescuentoPersonalInput(
 10613            descuento.Id,
 10614            personaId,
 10615            descuento.CatalogoItem.Codigo,
 10616            descuento.Importe,
 10617            descuento.AcreedorId.Value,
 10618            descuento.Acreedor.PrioridadDeficit);
 619    }
 620
 621    private static IReadOnlyList<LiquidacionItem> CrearItemsAportesLegales(
 622        long periodoId,
 623        string cedula,
 624        int version,
 625        AportesLegales aportes,
 626        IReadOnlyDictionary<int, CatalogoItem> catalogoPorCodigo)
 627    {
 31628        var items = new List<LiquidacionItem>();
 629
 31630        AgregarItemAporteLegal(
 31631            items, periodoId, cedula, version, aportes.Montepio,
 31632            BuscarCatalogoItem(catalogoPorCodigo, CodigoMontepioSubalterno, 545, 555),
 31633            codigoFallback: CodigoMontepioSubalterno.ToString(),
 31634            nombreFallback: "MONTEPIO",
 31635            rubroFallback: "5.04");
 636
 31637        AgregarItemAporteLegal(
 31638            items, periodoId, cedula, version, aportes.Fonasa,
 31639            BuscarCatalogoItem(catalogoPorCodigo, CodigoSanidad, 521, 522, 523, 524, 525, 526, 527, 528),
 31640            codigoFallback: CodigoSanidad.ToString(),
 31641            nombreFallback: "FONASA",
 31642            rubroFallback: "5.010");
 643
 31644        AgregarItemAporteLegal(
 31645            items, periodoId, cedula, version, aportes.Frl,
 31646            null,
 31647            codigoFallback: CodigoFrl,
 31648            nombreFallback: "FRL",
 31649            rubroFallback: "000.000");
 650
 31651        return items;
 652    }
 653
 654    private static void AgregarItemAporteLegal(
 655        ICollection<LiquidacionItem> items,
 656        long periodoId,
 657        string cedula,
 658        int version,
 659        decimal importe,
 660        CatalogoItem? catalogoItem,
 661        string codigoFallback,
 662        string nombreFallback,
 663        string rubroFallback)
 664    {
 93665        if (importe <= 0m)
 18666            return;
 667
 75668        if (catalogoItem is not null)
 669        {
 7670            items.Add(CrearItemLiquidacion(periodoId, cedula, version, catalogoItem, -importe));
 7671            return;
 672        }
 673
 68674        items.Add(CrearItemLiquidacion(periodoId, cedula, version, codigoFallback, nombreFallback, -importe, rubroFallba
 68675    }
 676
 677    private static CatalogoItem? BuscarCatalogoItem(
 678        IReadOnlyDictionary<int, CatalogoItem> catalogoPorCodigo,
 679        params int[] codigos)
 680    {
 805681        foreach (var codigo in codigos)
 682        {
 344683            if (catalogoPorCodigo.TryGetValue(codigo, out var catalogoItem))
 7684                return catalogoItem;
 685        }
 686
 55687        return null;
 688    }
 689
 690    private static LiquidacionItem CrearItemLiquidacion(
 691        long periodoId,
 692        string cedula,
 693        int version,
 694        CatalogoItem catalogoItem,
 695        decimal importe)
 696    {
 20697        return CrearItemLiquidacion(
 20698            periodoId, cedula, version,
 20699            catalogoItem.Codigo.ToString(),
 20700            catalogoItem.Nombre,
 20701            importe,
 20702            catalogoItem.ClasificacionSiif ?? "000.000");
 703    }
 704
 705    private static LiquidacionItem CrearItemLiquidacion(
 706        long periodoId,
 707        string cedula,
 708        int version,
 709        string codigoConcepto,
 710        string nombreConcepto,
 711        decimal importe,
 712        string rubroContable)
 713    {
 88714        return new LiquidacionItem
 88715        {
 88716            PeriodoId = periodoId,
 88717            Cedula = cedula,
 88718            Version = version,
 88719            CodigoConcepto = codigoConcepto,
 88720            NombreConcepto = nombreConcepto,
 88721            Importe = importe,
 88722            RubroContable = rubroContable,
 88723            Rubro083 = false,
 88724            FechaCreacion = DateTime.UtcNow
 88725        };
 726    }
 727
 728    private static decimal CalcularSueldoBaseParaTraslado(InsumosPeriodo insumos, long gradoId)
 729    {
 730        const int CodigoSueldoBase     = 11000;
 731        const int CodigoCompOficial    = 42012;
 732        const int CodigoCompSubalterno = 42022;
 733
 31734        var sueldoBase     = insumos.ObtenerRemuneracion(gradoId, CodigoSueldoBase)?.Monto ?? 0m;
 31735        var compOficial    = insumos.ObtenerRemuneracion(gradoId, CodigoCompOficial)?.Monto ?? 0m;
 31736        var compSubalterno = insumos.ObtenerRemuneracion(gradoId, CodigoCompSubalterno)?.Monto ?? 0m;
 737
 31738        return sueldoBase + compOficial + compSubalterno;
 739    }
 740
 310741    private sealed record AportesLegales(decimal Montepio, decimal Fonasa, decimal Frl)
 742    {
 31743        public decimal Total => Montepio + Fonasa + Frl;
 744    }
 745
 96746    private sealed record ResultadoIrpfLiquidacion(decimal TotalIrpf, LiquidacionItem? Item);
 747}

Methods/Properties

.cctor()
.ctor(FAU.DataAccess.ApplicationDbContext,FAU.Logica.Services.IAuditoriaService,FAU.Logica.Services.ICurrentUserContext,FAU.Logica.Services.IInsumosPeriodoLoader,FAU.Logica.Services.IProcesadorBeneficiosService,FAU.Logica.Calculadores.ICalculadorFactory,FAU.Logica.Services.ICalcularDiferencia615Service,FAU.DataAccess.Repositories.IDescuentoPersonalRepository,FAU.DataAccess.Repositories.IDependienteRepository,FAU.Logica.Calculators.IDeficitCalculator,FAU.Logica.Services.IDescuentosLegalesCalculator,FAU.Logica.Services.IIrpfService)
GenerarLiquidacionAsync()
ObtenerEstadosAsync()
ObtenerItemsAsync()
ObtenerResumenAsync()
CalcularAportesLegalesAsync()
CalcularIrpfAsync()
CrearDeduccionesIrpfAsync()
CrearItemsDescuentosPersonalesAsync()
CrearEntradaDeficit(FAU.Entidades.DescuentoPersonalPeriodo,System.Int64)
CrearItemsAportesLegales(System.Int64,System.String,System.Int32,FAU.Logica.Services.LiquidacionService/AportesLegales,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,FAU.Entidades.CatalogoItem>)
AgregarItemAporteLegal(System.Collections.Generic.ICollection`1<FAU.Entidades.LiquidacionItem>,System.Int64,System.String,System.Int32,System.Decimal,FAU.Entidades.CatalogoItem,System.String,System.String,System.String)
BuscarCatalogoItem(System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,FAU.Entidades.CatalogoItem>,System.Int32[])
CrearItemLiquidacion(System.Int64,System.String,System.Int32,FAU.Entidades.CatalogoItem,System.Decimal)
CrearItemLiquidacion(System.Int64,System.String,System.Int32,System.String,System.String,System.Decimal,System.String)
CalcularSueldoBaseParaTraslado(FAU.Logica.DTOs.InsumosPeriodo,System.Int64)
get_Montepio()
get_Total()
get_TotalIrpf()