< 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
0%
Covered lines: 0
Uncovered lines: 182
Coverable lines: 182
Total lines: 263
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 24
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GenerarLiquidacionAsync()0%342180%
ObtenerEstadosAsync()100%210%
ObtenerItemsAsync()100%210%
ObtenerResumenAsync()100%210%
CalcularSueldoBaseParaTraslado(...)0%4260%

File(s)

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

#LineLine coverage
 1using FAU.DataAccess;
 2using FAU.Entidades;
 3using FAU.Logica.DTOs;
 4using Microsoft.EntityFrameworkCore;
 5
 6namespace FAU.Logica.Services;
 7
 8public class LiquidacionService : ILiquidacionService
 9{
 10    private readonly ApplicationDbContext _context;
 11    private readonly IAuditoriaService _auditoriaService;
 12    private readonly ICurrentUserContext _currentUser;
 13    private readonly IInsumosPeriodoLoader _insumosLoader;
 14    private readonly IProcesadorBeneficiosService _procesadorBeneficios;
 15
 016    public LiquidacionService(
 017        ApplicationDbContext context,
 018        IAuditoriaService auditoriaService,
 019        ICurrentUserContext currentUser,
 020        IInsumosPeriodoLoader insumosLoader,
 021        IProcesadorBeneficiosService procesadorBeneficios)
 22    {
 023        _context = context;
 024        _auditoriaService = auditoriaService;
 025        _currentUser = currentUser;
 026        _insumosLoader = insumosLoader;
 027        _procesadorBeneficios = procesadorBeneficios;
 028    }
 29
 30    public async Task<(bool Exito, string? Error)> GenerarLiquidacionAsync(long periodoId, int version)
 31    {
 032        var periodo = await _context.PeriodosLiquidacion
 033            .AsNoTracking()
 034            .FirstOrDefaultAsync(p => p.Id == periodoId && p.Activo);
 35
 036        if (periodo is null)
 037            return (false, "Período de liquidación no encontrado o no está activo");
 38
 039        if (version <= 0)
 040            return (false, "La versión debe ser mayor a cero");
 41
 042        var existItems = await _context.LiquidacionItems
 043            .AnyAsync(li => li.PeriodoId == periodoId && li.Version == version);
 44
 045        if (existItems)
 046            return (false, "Ya existe una liquidación generada para ese período y versión");
 47
 48        InsumosPeriodo insumos;
 49        try
 50        {
 051            insumos = await _insumosLoader.CargarAsync(periodoId);
 052        }
 053        catch (InvalidOperationException ex) when (ex.Message.Contains("arámetro"))
 54        {
 055            return (false, $"No se encontraron parámetros para el período {periodo.Anio}/{periodo.Mes}");
 56        }
 57
 058        var personas = await _context.RelacionesLaborales
 059            .AsNoTracking()
 060            .Where(r => r.Estado == EstadoRelacion.Activo)
 061            .Join(
 062                _context.Personas.AsNoTracking(),
 063                r => r.PersonaId,
 064                p => p.Id,
 065                (r, p) => new { p.Id, p.Cedula, r.GradoId }
 066            )
 067            .Distinct()
 068            .ToListAsync();
 69
 070        if (!personas.Any())
 071            return (false, "No se encontraron relaciones laborales activas para generar la liquidación");
 72
 073        var primerDiaPeriodo = new DateOnly(periodo.Anio, periodo.Mes, 1);
 074        var itemsToSave = new List<LiquidacionItem>();
 075        var incidenciasToSave = new List<IncidenciaCalculo>();
 76
 77        // Pre-load historico de primas únicas para todas las personas en una sola consulta
 078        var cedulasPersonas = personas.Select(p => p.Cedula).ToHashSet();
 079        var itemsHistoricosGlobal = await _context.LiquidacionItems
 080            .AsNoTracking()
 081            .Where(li => cedulasPersonas.Contains(li.Cedula) &&
 082                         (li.CodigoConcepto == "340" || li.CodigoConcepto == "350" || li.CodigoConcepto == "370"))
 083            .ToListAsync();
 84
 085        foreach (var persona in personas)
 86        {
 87            try
 88            {
 089                var beneficios = insumos.ObtenerBeneficios(persona.Id);
 090                var itemsHistoricoPersona = itemsHistoricosGlobal
 091                    .Where(h => h.Cedula == persona.Cedula)
 092                    .ToList();
 93
 94                // Calcular sueldo base para Prima Traslado:
 95                // sueldo base (código 11000) + compensación de cargo/tropa (42012 oficial, 42022 subalterno)
 096                var sueldoBasePersona = CalcularSueldoBaseParaTraslado(insumos, persona.GradoId);
 97
 098                var resultado = _procesadorBeneficios.ProcesarBeneficios(
 099                    persona.Id,
 0100                    beneficios,
 0101                    insumos.ParametroPeriodo,
 0102                    insumos.CatalogoItems,
 0103                    itemsHistoricoPersona,
 0104                    primerDiaPeriodo,
 0105                    insumos.BeneficiariosPorBeneficio,
 0106                    insumos.InstructivoAsigFamiliar,
 0107                    insumos.InstructivoPsf,
 0108                    insumos.TablaPorHogar,
 0109                    sueldoBaseParaTraslado: sueldoBasePersona);
 110
 0111                foreach (var item in resultado.Items)
 112                {
 0113                    itemsToSave.Add(new LiquidacionItem
 0114                    {
 0115                        PeriodoId = periodoId,
 0116                        Cedula = persona.Cedula,
 0117                        Version = version,
 0118                        CodigoConcepto = item.CodigoConcepto.ToString(),
 0119                        NombreConcepto = item.NombreConcepto,
 0120                        Importe = item.Importe,
 0121                        RubroContable = item.RubroContable,
 0122                        Rubro083 = false,
 0123                        ClaveEvento = item.ClaveEvento,
 0124                        FechaCreacion = DateTime.UtcNow
 0125                    });
 126                }
 127
 0128                foreach (var incidencia in resultado.Incidencias)
 129                {
 0130                    incidenciasToSave.Add(new IncidenciaCalculo
 0131                    {
 0132                        PeriodoId = periodoId,
 0133                        Version = version,
 0134                        PersonaId = persona.Id,
 0135                        Tipo = incidencia.Tipo,
 0136                        Descripcion = incidencia.Descripcion,
 0137                        CreadoEn = DateTime.UtcNow
 0138                    });
 139                }
 0140            }
 0141            catch (Exception ex)
 142            {
 0143                incidenciasToSave.Add(new IncidenciaCalculo
 0144                {
 0145                    PeriodoId = periodoId,
 0146                    Version = version,
 0147                    PersonaId = persona.Id,
 0148                    Tipo = TipoIncidenciaCalculo.ErrorCalculo,
 0149                    Descripcion = $"Error al calcular beneficios: {ex.Message}",
 0150                    CreadoEn = DateTime.UtcNow
 0151                });
 0152            }
 153        }
 154
 0155        var estados = personas.Select(p => new LiquidacionEstadoPersonal
 0156        {
 0157            PeriodoId = periodoId,
 0158            Cedula = p.Cedula,
 0159            Version = version,
 0160            Estado = "PENDIENTE",
 0161            FechaActualizacion = DateTime.UtcNow
 0162        }).ToList();
 163
 0164        await _context.LiquidacionEstadosPersonal.AddRangeAsync(estados);
 165
 0166        if (itemsToSave.Count > 0)
 0167            await _context.LiquidacionItems.AddRangeAsync(itemsToSave);
 168
 0169        if (incidenciasToSave.Count > 0)
 0170            await _context.IncidenciasCalculo.AddRangeAsync(incidenciasToSave);
 171
 0172        var totalImporte = itemsToSave.Sum(i => i.Importe);
 0173        var resumen = new LiquidacionResumen
 0174        {
 0175            PeriodoId = periodoId,
 0176            Version = version,
 0177            TotalImporte = totalImporte,
 0178            Rubro083 = false,
 0179            Descripcion = "Liquidación generada"
 0180        };
 0181        await _context.LiquidacionResumenes.AddAsync(resumen);
 182
 0183        await _context.SaveChangesAsync();
 184
 0185        await _auditoriaService.LogAuditoriaAsync(
 0186            _currentUser.UserId ?? 0,
 0187            AccionEnum.GenerarLiquidacion,
 0188            ContextoEnum.Liquidaciones,
 0189            _currentUser.Host,
 0190            entidad: nameof(LiquidacionEstadoPersonal),
 0191            entidadId: periodoId,
 0192            detalle: new { periodoId, version, registros = estados.Count, items = itemsToSave.Count, incidencias = incid
 193
 0194        return (true, null);
 0195    }
 196
 197    public async Task<IEnumerable<CalculoEstadoDto>> ObtenerEstadosAsync(long periodoId, int version)
 198    {
 0199        return await _context.LiquidacionEstadosPersonal
 0200            .AsNoTracking()
 0201            .Where(e => e.PeriodoId == periodoId && e.Version == version)
 0202            .Select(e => new CalculoEstadoDto
 0203            {
 0204                Cedula = e.Cedula,
 0205                Version = e.Version,
 0206                Estado = e.Estado,
 0207                MensajeError = e.MensajeError
 0208            })
 0209            .ToListAsync();
 0210    }
 211
 212    public async Task<IEnumerable<CalculoItemDto>> ObtenerItemsAsync(long periodoId, int version)
 213    {
 0214        return await _context.LiquidacionItems
 0215            .AsNoTracking()
 0216            .Where(i => i.PeriodoId == periodoId && i.Version == version)
 0217            .Select(i => new CalculoItemDto
 0218            {
 0219                Cedula = i.Cedula,
 0220                Version = i.Version,
 0221                CodigoConcepto = i.CodigoConcepto,
 0222                NombreConcepto = i.NombreConcepto,
 0223                Importe = i.Importe,
 0224                RubroContable = i.RubroContable,
 0225                ObjetoGasto = i.ObjetoGasto
 0226            })
 0227            .ToListAsync();
 0228    }
 229
 230    public async Task<IEnumerable<LiquidacionResumenDto>> ObtenerResumenAsync(long periodoId, int version)
 231    {
 0232        return await _context.LiquidacionResumenes
 0233            .AsNoTracking()
 0234            .Where(r => r.PeriodoId == periodoId && r.Version == version)
 0235            .Select(r => new LiquidacionResumenDto
 0236            {
 0237                Version = r.Version,
 0238                ProgramaId = r.ProgramaId,
 0239                RegimenId = r.RegimenId,
 0240                Rubro083 = r.Rubro083,
 0241                TotalImporte = r.TotalImporte,
 0242                Descripcion = r.Descripcion
 0243            })
 0244            .ToListAsync();
 0245    }
 246
 247    // Sueldo Base + Compensación para Prima Traslado
 248    // Oficial (grados 01-09): sueldo 11000 + compensación cargo 42012
 249    // Subalterno (grados 10-17): sueldo 11000 + compensación tropa 42022
 250    private static decimal CalcularSueldoBaseParaTraslado(InsumosPeriodo insumos, long gradoId)
 251    {
 252        const int CodigoSueldoBase    = 11000;
 253        const int CodigoCompOficial    = 42012;
 254        const int CodigoCompSubalterno = 42022;
 255
 0256        var sueldoBase = insumos.ObtenerRemuneracion(gradoId, CodigoSueldoBase)?.Monto ?? 0m;
 0257        var compOficial    = insumos.ObtenerRemuneracion(gradoId, CodigoCompOficial)?.Monto ?? 0m;
 0258        var compSubalterno = insumos.ObtenerRemuneracion(gradoId, CodigoCompSubalterno)?.Monto ?? 0m;
 259
 260        // Toma la compensación que corresponda según el grado (solo uno de los dos tendrá valor)
 0261        return sueldoBase + compOficial + compSubalterno;
 262    }
 263}