< 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
88%
Covered lines: 520
Uncovered lines: 69
Coverable lines: 589
Total lines: 941
Line coverage: 88.2%
Branch coverage
80%
Covered branches: 168
Total branches: 208
Branch coverage: 80.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

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

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using FAU.DataAccess;
 3using FAU.DataAccess.Repositories;
 4using FAU.Entidades;
 5using FAU.Logica.Calculadores;
 6using FAU.Logica.Calculators;
 7using FAU.Logica.DTOs;
 8using Microsoft.EntityFrameworkCore;
 9
 10[assembly: InternalsVisibleTo("FAU.Tests")]
 11
 12namespace FAU.Logica.Services;
 13
 14public class LiquidacionService : ILiquidacionService
 15{
 16    private const int CodigoIrpf = 517;
 17    private const int CodigoMontepioSubalterno = 550;
 18    private const int CodigoSanidad = 520;
 19    private const string CodigoFrl = "FRL";
 20    private const int CodigoTutelaSocial = 540;
 21    private const int CodigoAjusteRedondeo = 90000;
 22
 23    // El OG de cada compensación viene de ConfigCompensacion.CatalogoItem
 24    // cargado via InsumosPeriodoLoader (TipoCompensacion → Configuraciones → CatalogoItem)
 25    private static int? ObtenerOGCompensacion(ItemLoteCompensacion comp)
 26    {
 227        var codigo = comp.LoteCompensacion?.TipoCompensacion?.Configuraciones
 228            .FirstOrDefault(c => c.CatalogoItem is not null)
 229            ?.CatalogoItem?.Codigo;
 230        return codigo;
 31    }
 32
 33    private readonly ApplicationDbContext _context;
 34    private readonly IAuditoriaService _auditoriaService;
 35    private readonly ICurrentUserContext _currentUser;
 36    private readonly IInsumosPeriodoLoader _insumosLoader;
 37    private readonly IProcesadorBeneficiosService _procesadorBeneficios;
 38    // Calculadoras de haberes base
 39    private readonly ICalculadorFactory _calculadorFactory;
 40    private readonly ICalcularDiferencia615Service _diferencia615Service;
 41    // Descuentos legales, IRPF y déficit
 42    private readonly IDescuentoPersonalRepository _descuentoPersonalRepository;
 43    private readonly IDependienteRepository _dependienteRepository;
 44    private readonly IDeficitCalculator _deficitCalculator;
 45    private readonly IDescuentosLegalesCalculator _descuentosLegalesCalculator;
 46    private readonly IIrpfService _irpfService;
 47
 4348    public LiquidacionService(
 4349        ApplicationDbContext context,
 4350        IAuditoriaService auditoriaService,
 4351        ICurrentUserContext currentUser,
 4352        IInsumosPeriodoLoader insumosLoader,
 4353        IProcesadorBeneficiosService procesadorBeneficios,
 4354        ICalculadorFactory calculadorFactory,
 4355        ICalcularDiferencia615Service diferencia615Service,
 4356        IDescuentoPersonalRepository descuentoPersonalRepository,
 4357        IDependienteRepository dependienteRepository,
 4358        IDeficitCalculator deficitCalculator,
 4359        IDescuentosLegalesCalculator descuentosLegalesCalculator,
 4360        IIrpfService irpfService)
 61    {
 4362        _context = context;
 4363        _auditoriaService = auditoriaService;
 4364        _currentUser = currentUser;
 4365        _insumosLoader = insumosLoader;
 4366        _procesadorBeneficios = procesadorBeneficios;
 4367        _calculadorFactory = calculadorFactory;
 4368        _diferencia615Service = diferencia615Service;
 4369        _descuentoPersonalRepository = descuentoPersonalRepository;
 4370        _dependienteRepository = dependienteRepository;
 4371        _deficitCalculator = deficitCalculator;
 4372        _descuentosLegalesCalculator = descuentosLegalesCalculator;
 4373        _irpfService = irpfService;
 4374    }
 75
 76    /// <summary>
 77    /// Genera la liquidación para el período. La versión se determina automáticamente:
 78    /// max_version_existente + 1, lo que permite recalcular después de una reapertura
 79    /// sin perder el historial de versiones anteriores.
 80    /// </summary>
 81    public async Task<(bool Exito, string? Error, int Version)> GenerarLiquidacionAsync(long periodoId)
 82    {
 4083        var periodo = await _context.PeriodosLiquidacion
 4084            .AsNoTracking()
 4085            .FirstOrDefaultAsync(p => p.Id == periodoId && p.Activo);
 86
 4087        if (periodo is null)
 188            return (false, "Período de liquidación no encontrado o no está activo", 0);
 89
 90        // Versión = max existente + 1 (1 si es la primera vez)
 3991        var maxVersion = await _context.LiquidacionItems
 3992            .Where(li => li.PeriodoId == periodoId)
 3993            .MaxAsync(li => (int?)li.Version) ?? 0;
 3994        var version = maxVersion + 1;
 95
 96        InsumosPeriodo insumos;
 97        try
 98        {
 3999            insumos = await _insumosLoader.CargarAsync(periodoId);
 39100        }
 0101        catch (InvalidOperationException ex) when (ex.Message.Contains("arámetro"))
 102        {
 0103            return (false, $"No se encontraron parámetros para el período {periodo.Anio}/{periodo.Mes}", 0);
 104        }
 105
 106        // Usamos el mismo patrón de Join que funciona con el HasConversion de Estado enum→string
 39107        var rlRaw = await _context.RelacionesLaborales
 39108            .AsNoTracking()
 39109            .Where(r => r.Estado == EstadoRelacion.Activo)
 39110            .Join(
 39111                _context.Personas.AsNoTracking(),
 39112                r => r.PersonaId,
 39113                p => p.Id,
 39114                (r, p) => new { RL = r, Persona = p })
 39115            .ToListAsync();
 116
 39117        if (!rlRaw.Any())
 2118            return (false, "No se encontraron relaciones laborales activas para generar la liquidación", version);
 119
 120        // Cargar navigation properties necesarias para el pipeline de calculadoras
 75121        var situacionIds = rlRaw.Select(x => x.RL.SituacionId).Distinct().ToHashSet();
 75122        var regimenIds   = rlRaw.Select(x => x.RL.RegimenId).Distinct().ToHashSet();
 123
 37124        var situacionesDict = await _context.Situaciones.AsNoTracking()
 37125            .Where(s => situacionIds.Contains(s.Id))
 54126            .ToDictionaryAsync(s => s.Id);
 127
 37128        var regimenesDict = await _context.Regimenes.AsNoTracking()
 37129            .Where(r => regimenIds.Contains(r.Id))
 55130            .ToDictionaryAsync(r => r.Id);
 131
 132        // Materializar RelacionLaboral con navigation properties resueltas en memoria
 37133        var relacionesLaborales = rlRaw.Select(x =>
 37134        {
 38135            x.RL.Persona = x.Persona;
 38136            x.RL.Situacion = situacionesDict.GetValueOrDefault(x.RL.SituacionId)!;
 38137            x.RL.Regimen   = regimenesDict.GetValueOrDefault(x.RL.RegimenId)!;
 38138            return x.RL;
 37139        }).ToList();
 140
 37141        var primerDiaPeriodo = new DateOnly(periodo.Anio, periodo.Mes, 1);
 37142        var itemsToSave = new List<LiquidacionItem>();
 37143        var incidenciasToSave = new List<IncidenciaCalculo>();
 37144        var estadosPersonal = new Dictionary<string, string>();
 37145        var erroresPersonal = new Dictionary<string, string>();
 146
 147        // Pre-load historico de primas únicas en una sola consulta
 75148        var cedulasPersonas = relacionesLaborales.Select(r => r.Persona.Cedula).ToHashSet();
 37149        var itemsHistoricosGlobal = await _context.LiquidacionItems
 37150            .AsNoTracking()
 37151            .Where(li => cedulasPersonas.Contains(li.Cedula) &&
 37152                         (li.CodigoConcepto == "340" || li.CodigoConcepto == "350" || li.CodigoConcepto == "370"))
 37153            .ToListAsync();
 154
 155        // Índice de catálogo por código (usado por helpers de aportes legales)
 37156        var catalogoPorCodigo = insumos.CatalogoItems
 236157            .GroupBy(i => i.Codigo)
 455158            .ToDictionary(g => g.Key, g => g.First());
 159
 150160        foreach (var rl in relacionesLaborales)
 161        {
 38162            var cedula = rl.Persona.Cedula;
 163            try
 164            {
 38165                var acumuladores = new Acumuladores();
 38166                var itemsHaberes = new List<ItemCalculado>();
 38167                var itemsBeneficios = new List<ItemBeneficioCalculado>();
 168
 169                // --- Paso 1-3: Calculador de haberes base ---
 38170                var calculador = rl.Situacion?.Calculador is not null
 38171                    ? _calculadorFactory.Resolver(rl.Situacion.Calculador)
 38172                    : null;
 173
 38174                if (calculador is not null)
 175                {
 176                    // Prorrateo por novedad 'dias_eximidos': factor = dias/30.
 177                    // Endurecido: dias_afectados debe estar en [1,30]. Fuera de rango (incl. 0)
 178                    // NO se prorratea (factor 1) y se registra incidencia, para no anular
 179                    // indebidamente los haberes ni romper el cálculo de aportes/IRPF.
 17180                    var novedadEximidos = insumos.ObtenerNovedadEximidos(rl.PersonaId);
 17181                    var factorProrrateo = 1m;
 17182                    if (novedadEximidos is not null)
 183                    {
 3184                        var diasAfectados = novedadEximidos.DiasAfectados;
 3185                        if (diasAfectados is < 1 or > 30)
 186                        {
 1187                            incidenciasToSave.Add(new IncidenciaCalculo
 1188                            {
 1189                                PeriodoId = periodoId, Version = version, PersonaId = rl.PersonaId,
 1190                                Tipo = TipoIncidenciaCalculo.CalculoSimplificado,
 1191                                Descripcion = $"Novedad 'dias_eximidos' con dias_afectados fuera de rango [1,30]: {diasA
 1192                                CreadoEn = DateTime.Now
 1193                            });
 194                        }
 2195                        else if (diasAfectados < 30)
 196                        {
 2197                            factorProrrateo = diasAfectados / 30m;
 198                        }
 199                    }
 200
 17201                    var resultadoCalculo = await calculador.CalcularAsync(rl, insumos, acumuladores);
 202
 17203                    if (factorProrrateo < 1m)
 204                    {
 24205                        foreach (var item in resultadoCalculo.Items)
 10206                            item.Importe = Math.Round(item.Importe * factorProrrateo, 2);
 207
 2208                        acumuladores.Reiniciar();
 24209                        foreach (var item in resultadoCalculo.Items)
 210                        {
 58211                            var ci = insumos.CatalogoItems.FirstOrDefault(c => c.Codigo == item.CodigoConcepto);
 19212                            if (ci is not null) acumuladores.AgregarItem(item.Importe, ci);
 213                        }
 214                    }
 215
 17216                    itemsHaberes.AddRange(resultadoCalculo.Items);
 217
 34218                    foreach (var inc in resultadoCalculo.Incidencias)
 0219                        incidenciasToSave.Add(new IncidenciaCalculo
 0220                        {
 0221                            PeriodoId = periodoId, Version = version, PersonaId = rl.PersonaId,
 0222                            Tipo = TipoIncidenciaCalculo.CalculoSimplificado,
 0223                            Descripcion = inc, CreadoEn = DateTime.Now
 0224                        });
 225                }
 226                else
 227                {
 21228                    incidenciasToSave.Add(new IncidenciaCalculo
 21229                    {
 21230                        PeriodoId = periodoId, Version = version, PersonaId = rl.PersonaId,
 21231                        Tipo = TipoIncidenciaCalculo.ErrorCalculo,
 21232                        Descripcion = $"Calculador no registrado: {rl.Situacion?.Calculador}",
 21233                        CreadoEn = DateTime.Now
 21234                    });
 235                }
 236
 237                // --- Paso 4: Beneficios sociales ---
 38238                var beneficios = insumos.ObtenerBeneficios(rl.PersonaId);
 38239                var itemsHistoricoPersona = itemsHistoricosGlobal.Where(h => h.Cedula == cedula).ToList();
 38240                var sueldoBasePersona = CalcularSueldoBaseParaTraslado(insumos, rl.GradoId);
 38241                var resultadoBeneficios = _procesadorBeneficios.ProcesarBeneficios(
 38242                    rl.PersonaId, beneficios, insumos.ParametroPeriodo, insumos.CatalogoItems,
 38243                    itemsHistoricoPersona, primerDiaPeriodo,
 38244                    insumos.BeneficiariosPorBeneficio, insumos.InstructivoAsigFamiliar,
 38245                    insumos.InstructivoPsf, insumos.TablaPorHogar,
 38246                    sueldoBaseParaTraslado: sueldoBasePersona);
 37247                itemsBeneficios.AddRange(resultadoBeneficios.Items);
 248
 74249                foreach (var inc in resultadoBeneficios.Incidencias)
 0250                    incidenciasToSave.Add(new IncidenciaCalculo
 0251                    {
 0252                        PeriodoId = periodoId, Version = version, PersonaId = rl.PersonaId,
 0253                        Tipo = inc.Tipo, Descripcion = inc.Descripcion, CreadoEn = DateTime.Now
 0254                    });
 255
 256                // --- Paso 5: Compensaciones por lote ---
 257                // El OG viene de ConfigCompensacion.CatalogoItem (no hardcodeado)
 37258                var compensaciones = insumos.ObtenerCompensaciones(rl.PersonaId);
 78259                foreach (var comp in compensaciones)
 260                {
 2261                    var ogCode = ObtenerOGCompensacion(comp);
 2262                    if (ogCode is null) continue;   // sin config de OG → saltear
 263
 2264                    var monto = comp.Monto ?? 0m;
 2265                    if (monto <= 0) continue;
 266
 18267                    var ci = insumos.CatalogoItems.FirstOrDefault(c => c.Codigo == ogCode.Value);
 2268                    itemsHaberes.Add(new ItemCalculado
 2269                    {
 2270                        CodigoConcepto = ogCode.Value,
 2271                        NombreConcepto = ci?.Nombre ?? comp.LoteCompensacion?.TipoCompensacion?.Nombre ?? "",
 2272                        Importe = monto
 2273                    });
 4274                    if (ci is not null) acumuladores.AgregarItem(monto, ci);
 275                }
 276
 277                // --- Paso 6: Diferencia por ascenso 042.615 ---
 37278                var diffAscenso = insumos.ObtenerDiferenciaAscenso(rl.PersonaId);
 37279                if (diffAscenso is not null && diffAscenso.Monto > 0)
 280                {
 281                    const int Cod615 = 42615;
 9282                    var ci615 = insumos.CatalogoItems.FirstOrDefault(c => c.Codigo == Cod615);
 1283                    itemsHaberes.Add(new ItemCalculado
 1284                    {
 1285                        CodigoConcepto = Cod615,
 1286                        NombreConcepto = ci615?.Nombre ?? "Diferencia por Ascenso (042.615)",
 1287                        Importe = diffAscenso.Monto
 1288                    });
 2289                    if (ci615 is not null) acumuladores.AgregarItem(diffAscenso.Monto, ci615);
 290                }
 291
 292                // --- Paso 7: Aportes legales (Montepío, FONASA, FRL) ---
 293                // baseSdlAportes = todos haberes positivos + beneficios gravados
 294                // nominalBruto   = todos haberes positivos + todos beneficios positivos
 295                // Se suma directamente desde itemsHaberes porque contribuye_w_nominal
 296                // puede no estar configurado en el catálogo (en ese caso WNominal=0).
 209297                var totalHaberesPositivos = itemsHaberes.Where(i => i.Importe > 0m).Sum(i => i.Importe);
 37298                var totalBeneficiosGravados = resultadoBeneficios.Items
 29299                    .Where(i => i.Importe > 0m
 29300                                && catalogoPorCodigo.TryGetValue(i.CodigoConcepto, out var ciBen)
 29301                                && (ciBen.EsGravado || ciBen.ContribuyeWNominal))
 58302                    .Sum(i => i.Importe);
 37303                var baseSdlAportes = totalHaberesPositivos + totalBeneficiosGravados;
 37304                var nominalBruto = totalHaberesPositivos
 90305                    + resultadoBeneficios.Items.Where(i => i.Importe > 0m).Sum(i => i.Importe);
 306
 37307                var esMilitar = !CalculadoresCiviles.Contains(rl.Situacion?.Calculador ?? string.Empty);
 37308                var aportes = await CalcularAportesLegalesAsync(rl.RegimenId, baseSdlAportes, insumos, esMilitar);
 37309                var itemsAportesLegales = CrearItemsAportesLegales(periodoId, cedula, version, aportes, catalogoPorCodig
 37310                itemsToSave.AddRange(itemsAportesLegales);
 311
 312                // --- Paso 8: IRPF ---
 313                // Eliminar registro anterior para que CalcularYPersistirAsync siempre inserte limpio
 37314                var irpfAnterior = await _context.Set<IrpfMensual>()
 37315                    .FirstOrDefaultAsync(i => i.LiquidacionId == periodoId && i.PersonaId == rl.PersonaId);
 37316                if (irpfAnterior is not null)
 317                {
 0318                    var detallesAnteriores = await _context.Set<IrpfDetalleFranjas>()
 0319                        .Where(d => d.IrpfMensualId == irpfAnterior.Id)
 0320                        .ToListAsync();
 0321                    _context.Set<IrpfDetalleFranjas>().RemoveRange(detallesAnteriores);
 0322                    _context.Set<IrpfMensual>().Remove(irpfAnterior);
 0323                    await _context.SaveChangesAsync();
 324                }
 325
 37326                var resultadoIrpf = await CalcularIrpfAsync(
 37327                    periodoId, rl.PersonaId, cedula, version,
 37328                    nominalBruto, aportes, insumos, catalogoPorCodigo);
 329
 330                // Descartar entidades IrpfMensual trackeadas si la persistencia falló,
 331                // para que no se reintentan en el SaveChangesAsync final.
 74332                foreach (var entry in _context.ChangeTracker.Entries<IrpfMensual>()
 0333                             .Where(e => e.State == Microsoft.EntityFrameworkCore.EntityState.Added)
 37334                             .ToList())
 0335                    entry.State = Microsoft.EntityFrameworkCore.EntityState.Detached;
 336
 37337                if (resultadoIrpf.Item is not null && resultadoIrpf.TotalIrpf != 0)
 3338                    itemsToSave.Add(resultadoIrpf.Item);
 339                else
 340                {
 341                    // Diagnóstico: registrar siempre el resultado del cálculo IRPF para debugging
 34342                    var tieneCatalogo = catalogoPorCodigo.ContainsKey(CodigoIrpf);
 34343                    incidenciasToSave.Add(new IncidenciaCalculo
 34344                    {
 34345                        PeriodoId = periodoId, Version = version, PersonaId = rl.PersonaId,
 34346                        Tipo = TipoIncidenciaCalculo.CalculoSimplificado,
 34347                        Descripcion = $"IRPF diag: TotalIrpf={resultadoIrpf.TotalIrpf:F2} ItemNull={resultadoIrpf.Item i
 34348                        CreadoEn = DateTime.Now
 34349                    });
 350                }
 351
 352                // --- Paso 9: Descuentos personales con déficit ---
 353                // Excluir códigos ya emitidos como aportes para no contarlos dos veces en el déficit
 37354                var codigosAportesEmitidos = itemsAportesLegales
 102355                    .Where(i => !string.IsNullOrWhiteSpace(i.CodigoConcepto))
 102356                    .Select(i => i.CodigoConcepto)
 37357                    .ToHashSet(StringComparer.OrdinalIgnoreCase);
 37358                var descuentosLegalesEnBeneficios = resultadoBeneficios.Items
 29359                    .Where(i => i.Importe < 0m
 29360                                && catalogoPorCodigo.TryGetValue(i.CodigoConcepto, out var ci)
 29361                                && string.Equals(ci.Tipo, "descuento_legal", StringComparison.OrdinalIgnoreCase)
 29362                                && !codigosAportesEmitidos.Contains(i.CodigoConcepto.ToString()))
 41363                    .Sum(i => Math.Abs(i.Importe));
 37364                var totalDescuentosLegales = aportes.Total + resultadoIrpf.TotalIrpf + descuentosLegalesEnBeneficios;
 37365                var fechaPeriodo = new DateTime(insumos.ParametroPeriodo.Anio, insumos.ParametroPeriodo.Mes, 1);
 37366                var itemsDescuentosPersonales = await CrearItemsDescuentosPersonalesAsync(
 37367                    periodoId, rl.PersonaId, cedula, version,
 37368                    nominalBruto, totalDescuentosLegales, fechaPeriodo);
 37369                itemsToSave.AddRange(itemsDescuentosPersonales);
 370
 371                // --- Paso 9.5: Ajuste de redondeo ---
 372                // El liquido real (lo que el funcionario ve en el recibo) es la suma de
 373                // todos los items que se van a persistir, no `nominalBruto` (que filtra
 374                // solo positivos y se usa como base de aportes/IRPF).
 123375                var totalSinAjuste = itemsHaberes.Sum(i => i.Importe)
 29376                    + itemsBeneficios.Sum(i => i.Importe)
 102377                    + itemsAportesLegales.Sum(i => i.Importe)
 37378                    + (resultadoIrpf.Item?.Importe ?? 0m)
 47379                    + itemsDescuentosPersonales.Sum(i => i.Importe);
 380
 37381                var ajusteRedondeo = CalcularAjusteRedondeo(totalSinAjuste);
 37382                if (ajusteRedondeo != 0m && catalogoPorCodigo.TryGetValue(CodigoAjusteRedondeo, out var ciAjuste))
 4383                    itemsToSave.Add(CrearItemLiquidacion(periodoId, cedula, version, ciAjuste, ajusteRedondeo));
 384
 385                // --- Paso 10: Estado ---
 37386                var tieneError = incidenciasToSave.Any(i =>
 76387                    i.PersonaId == rl.PersonaId && i.Tipo == TipoIncidenciaCalculo.ErrorCalculo);
 37388                var liquido = nominalBruto - totalDescuentosLegales
 47389                    - itemsDescuentosPersonales.Sum(i => Math.Abs(i.Importe));
 37390                var estado = tieneError ? "ERROR" : (liquido < 0 ? "REVISAR" : "CALCULADO");
 37391                estadosPersonal[cedula] = estado;
 392
 393                // --- Paso 11: Persistir ítems de haberes y beneficios ---
 246394                foreach (var item in itemsHaberes)
 395                {
 86396                    catalogoPorCodigo.TryGetValue(item.CodigoConcepto, out var catalogoItem);
 86397                    itemsToSave.Add(new LiquidacionItem
 86398                    {
 86399                        PeriodoId = periodoId,
 86400                        Cedula = cedula,
 86401                        Version = version,
 86402                        CodigoConcepto = item.CodigoConcepto.ToString(),
 86403                        NombreConcepto = item.NombreConcepto,
 86404                        Importe = item.Importe,
 86405                        RubroContable = catalogoItem?.ClasificacionSiif ?? string.Empty,
 86406                        Rubro083 = false,
 86407                        FechaCreacion = DateTime.Now
 86408                    });
 409                }
 132410                foreach (var item in itemsBeneficios)
 411                {
 29412                    itemsToSave.Add(new LiquidacionItem
 29413                    {
 29414                        PeriodoId = periodoId,
 29415                        Cedula = cedula,
 29416                        Version = version,
 29417                        CodigoConcepto = item.CodigoConcepto.ToString(),
 29418                        NombreConcepto = item.NombreConcepto,
 29419                        Importe = item.Importe,
 29420                        RubroContable = item.RubroContable,
 29421                        Rubro083 = false,
 29422                        ClaveEvento = item.ClaveEvento,
 29423                        FechaCreacion = DateTime.Now
 29424                    });
 425                }
 37426            }
 1427            catch (Exception ex)
 428            {
 1429                estadosPersonal[cedula] = "ERROR";
 1430                erroresPersonal[cedula] = ex.Message;
 1431                incidenciasToSave.Add(new IncidenciaCalculo
 1432                {
 1433                    PeriodoId = periodoId,
 1434                    Version = version,
 1435                    PersonaId = rl.PersonaId,
 1436                    Tipo = TipoIncidenciaCalculo.ErrorCalculo,
 1437                    Descripcion = $"Error en pipeline de liquidación: {ex.Message}",
 1438                    CreadoEn = DateTime.Now
 1439                });
 1440            }
 38441        }
 442
 75443        var estados = relacionesLaborales.Select(rl => new LiquidacionEstadoPersonal
 75444        {
 75445            PeriodoId = periodoId,
 75446            Cedula = rl.Persona.Cedula,
 75447            Version = version,
 75448            Estado = estadosPersonal.TryGetValue(rl.Persona.Cedula, out var e) ? e : "PENDIENTE",
 75449            MensajeError = erroresPersonal.TryGetValue(rl.Persona.Cedula, out var err) ? err : null,
 75450            FechaActualizacion = DateTime.Now
 75451        }).ToList();
 452
 37453        await _context.LiquidacionEstadosPersonal.AddRangeAsync(estados);
 454
 37455        if (itemsToSave.Count > 0)
 36456            await _context.LiquidacionItems.AddRangeAsync(itemsToSave);
 457
 37458        if (incidenciasToSave.Count > 0)
 37459            await _context.IncidenciasCalculo.AddRangeAsync(incidenciasToSave);
 460
 271461        var totalImporte = itemsToSave.Sum(i => i.Importe);
 37462        var resumen = new LiquidacionResumen
 37463        {
 37464            PeriodoId = periodoId,
 37465            Version = version,
 37466            TotalImporte = totalImporte,
 37467            Rubro083 = false,
 37468            Descripcion = "Liquidación generada"
 37469        };
 37470        await _context.LiquidacionResumenes.AddAsync(resumen);
 471
 37472        await _context.SaveChangesAsync();
 473
 37474        await _auditoriaService.LogAuditoriaAsync(
 37475            _currentUser.UserId ?? 0,
 37476            AccionEnum.GenerarLiquidacion,
 37477            ContextoEnum.Liquidaciones,
 37478            _currentUser.Host,
 37479            entidad: nameof(LiquidacionEstadoPersonal),
 37480            entidadId: periodoId,
 37481            detalle: new { periodoId, version, registros = estados.Count, items = itemsToSave.Count, incidencias = incid
 482
 37483        return (true, null, version);
 40484    }
 485
 486    private async Task<int> ObtenerVersionMaximaAsync(long periodoId)
 487    {
 1488        return await _context.LiquidacionItems
 1489            .AsNoTracking()
 1490            .Where(i => i.PeriodoId == periodoId)
 1491            .MaxAsync(i => (int?)i.Version) ?? 0;
 1492    }
 493
 494    public async Task<IEnumerable<CalculoEstadoDto>> ObtenerEstadosAsync(long periodoId, int? version = null)
 495    {
 2496        var v = version ?? await ObtenerVersionMaximaAsync(periodoId);
 497
 2498        var totalesPorCedula = await _context.LiquidacionItems
 2499            .AsNoTracking()
 2500            .Where(i => i.PeriodoId == periodoId && i.Version == v)
 2501            .GroupBy(i => i.Cedula)
 2502            .Select(g => new
 2503            {
 2504                Cedula = g.Key,
 2505                Nominal    = g.Where(i => i.Importe > 0).Sum(i => i.Importe),
 2506                Descuentos = g.Where(i => i.Importe < 0).Sum(i => i.Importe),
 2507            })
 3508            .ToDictionaryAsync(x => x.Cedula);
 509
 2510        var estados = await _context.LiquidacionEstadosPersonal
 2511            .AsNoTracking()
 2512            .Where(e => e.PeriodoId == periodoId && e.Version == v)
 2513            .Select(e => new CalculoEstadoDto
 2514            {
 2515                Cedula       = e.Cedula,
 2516                Version      = e.Version,
 2517                Estado       = e.Estado,
 2518                MensajeError = e.MensajeError
 2519            })
 2520            .ToListAsync();
 521
 8522        foreach (var dto in estados)
 2523            if (totalesPorCedula.TryGetValue(dto.Cedula, out var tot))
 524            {
 1525                dto.Nominal    = tot.Nominal;
 1526                dto.Descuentos = tot.Descuentos;
 1527                dto.Liquido    = tot.Nominal + tot.Descuentos;
 528            }
 529
 2530        return estados;
 2531    }
 532
 533    public async Task<IEnumerable<CalculoItemDto>> ObtenerItemsAsync(long periodoId, int? version = null, string? cedula
 534    {
 0535        var v = version ?? await ObtenerVersionMaximaAsync(periodoId);
 0536        var query = _context.LiquidacionItems
 0537            .AsNoTracking()
 0538            .Where(i => i.PeriodoId == periodoId && i.Version == v);
 539
 0540        if (!string.IsNullOrEmpty(cedula))
 0541            query = query.Where(i => i.Cedula == cedula);
 542
 0543        return await query
 0544            .Select(i => new CalculoItemDto
 0545            {
 0546                Cedula         = i.Cedula,
 0547                Version        = i.Version,
 0548                CodigoConcepto = i.CodigoConcepto,
 0549                NombreConcepto = i.NombreConcepto,
 0550                Importe        = i.Importe,
 0551                RubroContable  = i.RubroContable,
 0552                ObjetoGasto    = i.ObjetoGasto
 0553            })
 0554            .ToListAsync();
 0555    }
 556
 557    public async Task<IEnumerable<LiquidacionResumenDto>> ObtenerResumenAsync(long periodoId, int? version = null)
 558    {
 0559        var v = version ?? await ObtenerVersionMaximaAsync(periodoId);
 0560        return await _context.LiquidacionResumenes
 0561            .AsNoTracking()
 0562            .Where(r => r.PeriodoId == periodoId && r.Version == v)
 0563            .Select(r => new LiquidacionResumenDto
 0564            {
 0565                Version = r.Version,
 0566                ProgramaId = r.ProgramaId,
 0567                RegimenId = r.RegimenId,
 0568                Rubro083 = r.Rubro083,
 0569                TotalImporte = r.TotalImporte,
 0570                Descripcion = r.Descripcion
 0571            })
 0572            .ToListAsync();
 0573    }
 574
 575    public async Task<IEnumerable<int>> ObtenerVersionesAsync(long periodoId)
 576    {
 2577        return await _context.LiquidacionItems
 2578            .AsNoTracking()
 2579            .Where(i => i.PeriodoId == periodoId)
 2580            .Select(i => i.Version)
 2581            .Distinct()
 2582            .OrderByDescending(v => v)
 2583            .ToListAsync();
 2584    }
 585
 586    // Civiles aportan FONASA por tasa única; los militares aportan Sanidad Militar por tramos.
 1587    private static readonly HashSet<string> CalculadoresCiviles =
 1588        new(StringComparer.OrdinalIgnoreCase) { "Sit70Calculator", "CivilCalculator" };
 589
 590    private async Task<AportesLegales> CalcularAportesLegalesAsync(
 591        long regimenId, decimal baseSdl, InsumosPeriodo insumos, bool esMilitar)
 592    {
 37593        var regimen = await _context.Regimenes.AsNoTracking().FirstOrDefaultAsync(r => r.Id == regimenId);
 37594        var tasaMontepio = regimen is not null && regimen.EsLeyVieja
 37595            ? insumos.ParametroPeriodo.TasaMontepioLeyVieja
 37596            : insumos.ParametroPeriodo.TasaMontepioLeyNueva;
 597
 37598        var montepio = _descuentosLegalesCalculator.CalcularMontepio(baseSdl, tasaMontepio);
 37599        var frl = _descuentosLegalesCalculator.CalcularFrl(baseSdl, insumos.ParametroPeriodo.TasaFrl);
 37600        var tutelaSocial = _descuentosLegalesCalculator.CalcularTutelaSocial(baseSdl, insumos.ParametroPeriodo.TasaTutel
 601
 602        // Sanidad Militar (Art. 94 Ley 18.834) reemplaza a FONASA para militares.
 603        // Base = misma que el montepío (baseSdl). Civiles mantienen FONASA por tasa única.
 37604        var tramo = esMilitar ? insumos.ObtenerTramoSanidad(baseSdl) : null;
 37605        if (tramo is not null)
 606        {
 0607            var sanidad = _descuentosLegalesCalculator.CalcularSanidad(baseSdl, tramo.Porcentaje);
 0608            return new AportesLegales(montepio, sanidad, frl, tutelaSocial, tramo.CodigoRetencion);
 609        }
 610
 37611        var fonasa = _descuentosLegalesCalculator.CalcularFonasa(baseSdl, insumos.ParametroPeriodo.TasaFonasa);
 37612        return new AportesLegales(montepio, fonasa, frl, tutelaSocial, SanidadCodigo: null);
 37613    }
 614
 615    private async Task<ResultadoIrpfLiquidacion> CalcularIrpfAsync(
 616        long periodoId,
 617        long personaId,
 618        string cedula,
 619        int version,
 620        decimal nominalBruto,
 621        AportesLegales aportes,
 622        InsumosPeriodo insumos,
 623        IReadOnlyDictionary<int, CatalogoItem> catalogoPorCodigo)
 624    {
 37625        var request = new CalcularIrpfRequest
 37626        {
 37627            IngresoGravados = nominalBruto,
 37628            Montepio = aportes.Montepio,
 37629            Fonasa = aportes.Fonasa,
 37630            Frl = aportes.Frl,
 37631            Deducciones = await CrearDeduccionesIrpfAsync(personaId),
 37632            Parametros = new ParametrosIrpfDto
 37633            {
 37634                BpcMensual = insumos.ParametroPeriodo.BpcAnual / 12m,
 37635                UmbralDeduccionBpc = insumos.ParametroPeriodo.UmbralDeduccionBpc,
 37636                TasaDeduccionAlta = insumos.ParametroPeriodo.TasaDeduccionAlta,
 37637                TasaDeduccionBaja = insumos.ParametroPeriodo.TasaDeduccionBaja
 37638            },
 37639            Franjas = insumos.FranjasIrpf
 75640                .Select(f => new FranjaIrpfCalculoDto
 75641                {
 75642                    FranjaId = f.Id,
 75643                    NumeroFranja = f.NumeroFranja,
 75644                    DesdeBpc = f.DesdeBpc,
 75645                    HastaBpc = f.HastaBpc,
 75646                    Tasa = f.Tasa
 75647                })
 37648                .ToList()
 37649        };
 650
 37651        var (exito, idIrpf, _) = await _irpfService.CalcularYPersistirAsync(periodoId, personaId, request);
 37652        if (!exito || !idIrpf.HasValue)
 33653            return new ResultadoIrpfLiquidacion(0m, null);
 654
 4655        var irpf = await _irpfService.ObtenerPorIdAsync(idIrpf.Value);
 4656        if (irpf is null)
 0657            return new ResultadoIrpfLiquidacion(0m, null);
 658
 4659        var item = catalogoPorCodigo.TryGetValue(CodigoIrpf, out var catalogoIrpf)
 4660            ? CrearItemLiquidacion(periodoId, cedula, version, catalogoIrpf, -irpf.TotalIrpf)
 4661            : null;
 662
 4663        return new ResultadoIrpfLiquidacion(irpf.TotalIrpf, item);
 37664    }
 665
 666    private async Task<List<DeduccionDto>> CrearDeduccionesIrpfAsync(long personaId)
 667    {
 37668        var dependientes = await _dependienteRepository.GetDependientesByPersonaAsync(personaId);
 669
 37670        return dependientes
 3671            .Where(d => d.Activo)
 37672            .SelectMany(d =>
 37673            {
 3674                if (string.Equals(d.Tipo, "conyuge_sin_ingresos", StringComparison.OrdinalIgnoreCase))
 1675                    return [new DeduccionDto { Tipo = "conyuge_sin_ingresos", Cantidad = 1 }];
 37676
 2677                if (string.Equals(d.Tipo, "hijo", StringComparison.OrdinalIgnoreCase))
 37678                {
 2679                    var tipoHijo = d.Discapacitado ? "hijo_discapacidad" : "hijo_menor";
 2680                    return [new DeduccionDto { Tipo = tipoHijo, Cantidad = 1 }];
 37681                }
 37682
 0683                return Array.Empty<DeduccionDto>();
 37684            })
 37685            .ToList();
 37686    }
 687
 688    private async Task<IReadOnlyList<LiquidacionItem>> CrearItemsDescuentosPersonalesAsync(
 689        long periodoId,
 690        long personaId,
 691        string cedula,
 692        int version,
 693        decimal nominalBruto,
 694        decimal descuentosLegales,
 695        DateTime fechaPeriodo)
 696    {
 37697        var descuentosConfirmados = (await _descuentoPersonalRepository
 37698                .GetConfirmadosPorPersonaAsync(periodoId, personaId))
 10699            .Where(d => d.CatalogoItem is not null)
 37700            // Vigencia (ej. CUOTA_ING): se aplica solo si no venció antes del período
 10701            .Where(d => d.FechaHasta is null || d.FechaHasta >= fechaPeriodo)
 37702            .ToList();
 703
 37704        if (descuentosConfirmados.Count == 0)
 28705            return Array.Empty<LiquidacionItem>();
 706
 707        // Filtrar descuentos sin acreedor asignado (datos previos a la migración de acreedor_id)
 9708        var descuentosConAcreedor = descuentosConfirmados
 10709            .Where(d => d.AcreedorId is not null && d.Acreedor is not null)
 9710            .ToList();
 711
 9712        if (descuentosConAcreedor.Count == 0)
 0713            return Array.Empty<LiquidacionItem>();
 714
 9715        var entradasDeficit = descuentosConAcreedor
 10716            .Select(d => CrearEntradaDeficit(d, personaId))
 9717            .ToList();
 718
 9719        var resultadosDeficit = (await _deficitCalculator.AplicarAsync(
 9720            nominalBruto,
 9721            descuentosLegales,
 9722            entradasDeficit)).ToList();
 723
 19724        var descuentosPorId = descuentosConAcreedor.ToDictionary(d => d.Id);
 9725        var items = new List<LiquidacionItem>();
 726
 38727        foreach (var resultadoDeficit in resultadosDeficit)
 728        {
 10729            if (resultadoDeficit.DescuentoPeriodoId is null)
 730                continue;
 731
 10732            if (!descuentosPorId.TryGetValue(resultadoDeficit.DescuentoPeriodoId.Value, out var descuento))
 733                continue;
 734
 10735            descuento.MontoAplicado = resultadoDeficit.MontoAplicado;
 10736            descuento.MontoNoAplicado = resultadoDeficit.MontoNoAplicado;
 10737            descuento.Estado = resultadoDeficit.Estado;
 738
 10739            if (!string.IsNullOrWhiteSpace(resultadoDeficit.MotivoNoAplicado) &&
 10740                string.IsNullOrWhiteSpace(descuento.Observaciones))
 741            {
 0742                descuento.Observaciones = resultadoDeficit.MotivoNoAplicado;
 743            }
 744
 10745            if (resultadoDeficit.MontoAplicado <= 0m)
 746                continue;
 747
 10748            items.Add(CrearItemLiquidacion(
 10749                periodoId,
 10750                cedula,
 10751                version,
 10752                descuento.CatalogoItem,
 10753                -resultadoDeficit.MontoAplicado));
 754        }
 755
 9756        return items;
 37757    }
 758
 759    private static DescuentoPersonalInput CrearEntradaDeficit(DescuentoPersonalPeriodo descuento, long personaId)
 760    {
 10761        if (descuento.AcreedorId is null)
 0762            throw new InvalidOperationException($"El descuento personal {descuento.Id} no tiene acreedor asignado.");
 763
 10764        if (descuento.Acreedor is null)
 0765            throw new InvalidOperationException($"El descuento personal {descuento.Id} no tiene acreedor cargado.");
 766
 10767        return new DescuentoPersonalInput(
 10768            descuento.Id,
 10769            personaId,
 10770            descuento.CatalogoItem.Codigo,
 10771            descuento.Importe,
 10772            descuento.AcreedorId.Value,
 10773            descuento.Acreedor.CategoriaDeficit,
 10774            descuento.Acreedor.OrdenLegalDeficit,
 10775            descuento.FechaComunicacion ?? descuento.FechaCreacion);
 776    }
 777
 778    private static IReadOnlyList<LiquidacionItem> CrearItemsAportesLegales(
 779        long periodoId,
 780        string cedula,
 781        int version,
 782        AportesLegales aportes,
 783        IReadOnlyDictionary<int, CatalogoItem> catalogoPorCodigo)
 784    {
 37785        var items = new List<LiquidacionItem>();
 786
 37787        AgregarItemAporteLegal(
 37788            items, periodoId, cedula, version, aportes.Montepio,
 37789            BuscarCatalogoItem(catalogoPorCodigo, CodigoMontepioSubalterno, 545, 555),
 37790            codigoFallback: CodigoMontepioSubalterno.ToString(),
 37791            nombreFallback: "MONTEPIO",
 37792            rubroFallback: "5.04");
 793
 794        // Salud: Sanidad Militar por tramos (código 5010/5011/5012) o FONASA por tasa única.
 37795        if (aportes.SanidadCodigo is int sanidadCodigo)
 796        {
 0797            AgregarItemAporteLegal(
 0798                items, periodoId, cedula, version, aportes.Fonasa,
 0799                BuscarCatalogoItem(catalogoPorCodigo, sanidadCodigo),
 0800                codigoFallback: sanidadCodigo.ToString(),
 0801                nombreFallback: "SANIDAD",
 0802                rubroFallback: $"5.{sanidadCodigo % 100:D2}");
 803        }
 804        else
 805        {
 37806            AgregarItemAporteLegal(
 37807                items, periodoId, cedula, version, aportes.Fonasa,
 37808                BuscarCatalogoItem(catalogoPorCodigo, CodigoSanidad, 521, 522, 523, 524, 525, 526, 527, 528),
 37809                codigoFallback: CodigoSanidad.ToString(),
 37810                nombreFallback: "FONASA",
 37811                rubroFallback: "5.010");
 812        }
 813
 37814        AgregarItemAporteLegal(
 37815            items, periodoId, cedula, version, aportes.Frl,
 37816            null,
 37817            codigoFallback: CodigoFrl,
 37818            nombreFallback: "FRL",
 37819            rubroFallback: "000.000");
 820
 37821        AgregarItemAporteLegal(
 37822            items, periodoId, cedula, version, aportes.TutelaSocial,
 37823            BuscarCatalogoItem(catalogoPorCodigo, CodigoTutelaSocial),
 37824            codigoFallback: CodigoTutelaSocial.ToString(),
 37825            nombreFallback: "SERV. ESP. TUT. SOC.",
 37826            rubroFallback: "5.08");
 827
 37828        return items;
 829    }
 830
 831    private static void AgregarItemAporteLegal(
 832        ICollection<LiquidacionItem> items,
 833        long periodoId,
 834        string cedula,
 835        int version,
 836        decimal importe,
 837        CatalogoItem? catalogoItem,
 838        string codigoFallback,
 839        string nombreFallback,
 840        string rubroFallback)
 841    {
 148842        if (importe <= 0m)
 46843            return;
 844
 102845        if (catalogoItem is not null)
 846        {
 7847            items.Add(CrearItemLiquidacion(periodoId, cedula, version, catalogoItem, -importe));
 7848            return;
 849        }
 850
 95851        items.Add(CrearItemLiquidacion(periodoId, cedula, version, codigoFallback, nombreFallback, -importe, rubroFallba
 95852    }
 853
 854    private static CatalogoItem? BuscarCatalogoItem(
 855        IReadOnlyDictionary<int, CatalogoItem> catalogoPorCodigo,
 856        params int[] codigos)
 857    {
 1121858        foreach (var codigo in codigos)
 859        {
 453860            if (catalogoPorCodigo.TryGetValue(codigo, out var catalogoItem))
 7861                return catalogoItem;
 862        }
 863
 104864        return null;
 865    }
 866
 867    private static LiquidacionItem CrearItemLiquidacion(
 868        long periodoId,
 869        string cedula,
 870        int version,
 871        CatalogoItem catalogoItem,
 872        decimal importe)
 873    {
 24874        return CrearItemLiquidacion(
 24875            periodoId, cedula, version,
 24876            catalogoItem.Codigo.ToString(),
 24877            catalogoItem.Nombre,
 24878            importe,
 24879            catalogoItem.ClasificacionSiif ?? "000.000");
 880    }
 881
 882    private static LiquidacionItem CrearItemLiquidacion(
 883        long periodoId,
 884        string cedula,
 885        int version,
 886        string codigoConcepto,
 887        string nombreConcepto,
 888        decimal importe,
 889        string rubroContable)
 890    {
 119891        return new LiquidacionItem
 119892        {
 119893            PeriodoId = periodoId,
 119894            Cedula = cedula,
 119895            Version = version,
 119896            CodigoConcepto = codigoConcepto,
 119897            NombreConcepto = nombreConcepto,
 119898            Importe = importe,
 119899            RubroContable = rubroContable,
 119900            Rubro083 = false,
 119901            FechaCreacion = DateTime.Now
 119902        };
 903    }
 904
 905    /// <summary>
 906    /// Diferencia entre el redondeo al entero más cercano y <paramref name="totalSinAjuste"/>.
 907    /// Centavos &lt;= 0.50 redondean hacia abajo; centavos &gt; 0.50 redondean hacia arriba.
 908    /// La parte fraccionaria (<c>frac</c>) siempre queda en [0, 1) vía <see cref="Math.Floor(decimal)"/>,
 909    /// por lo que la regla es válida también para totales negativos.
 910    /// </summary>
 911    internal static decimal CalcularAjusteRedondeo(decimal totalSinAjuste)
 912    {
 43913        var piso = Math.Floor(totalSinAjuste);
 43914        var frac = totalSinAjuste - piso;
 43915        var redondeado = frac <= 0.50m ? piso : piso + 1m;
 43916        return redondeado - totalSinAjuste;
 917    }
 918
 919    private static decimal CalcularSueldoBaseParaTraslado(InsumosPeriodo insumos, long gradoId)
 920    {
 921        const int CodigoSueldoBase     = 11000;
 922        const int CodigoCompOficial    = 42012;
 923        const int CodigoCompSubalterno = 42022;
 924
 38925        var sueldoBase     = insumos.ObtenerRemuneracion(gradoId, CodigoSueldoBase)?.Monto ?? 0m;
 38926        var compOficial    = insumos.ObtenerRemuneracion(gradoId, CodigoCompOficial)?.Monto ?? 0m;
 38927        var compSubalterno = insumos.ObtenerRemuneracion(gradoId, CodigoCompSubalterno)?.Monto ?? 0m;
 928
 38929        return sueldoBase + compOficial + compSubalterno;
 930    }
 931
 932    // Fonasa carries el monto del aporte de salud: FONASA por tasa única (civiles) o
 933    // Sanidad Militar por tramos (militares). SanidadCodigo indica el código del tramo
 934    // aplicado (5010/5011/5012) cuando corresponde a Sanidad Militar; null para FONASA.
 481935    private sealed record AportesLegales(decimal Montepio, decimal Fonasa, decimal Frl, decimal TutelaSocial = 0m, int? 
 936    {
 37937        public decimal Total => Montepio + Fonasa + Frl + TutelaSocial;
 938    }
 939
 222940    private sealed record ResultadoIrpfLiquidacion(decimal TotalIrpf, LiquidacionItem? Item);
 941}

Methods/Properties

ObtenerOGCompensacion(FAU.Entidades.ItemLoteCompensacion)
.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()
ObtenerVersionMaximaAsync()
ObtenerEstadosAsync()
ObtenerItemsAsync()
ObtenerResumenAsync()
ObtenerVersionesAsync()
.cctor()
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)
CalcularAjusteRedondeo(System.Decimal)
CalcularSueldoBaseParaTraslado(FAU.Logica.DTOs.InsumosPeriodo,System.Int64)
get_Montepio()
get_Total()
get_TotalIrpf()