< Summary

Information
Class: FAU.Logica.Services.AguinaldoBajaService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/AguinaldoBajaService.cs
Line coverage
91%
Covered lines: 262
Uncovered lines: 24
Coverable lines: 286
Total lines: 433
Line coverage: 91.6%
Branch coverage
77%
Covered branches: 54
Total branches: 70
Branch coverage: 77.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ValidarHistoricoAsync()87.5%161688.75%
CalcularAguinaldoBajaAsync()77.27%222298.01%
ObtenerPorPeriodoAsync()100%88100%
ObtenerBajasParaPeriodoAsync()100%11100%
EsMesProcesable(...)100%22100%
CalcularSemestre(...)50%22100%
CalcularMesesComputados(...)50%6688.88%
ObtenerTotalHaberesGravadosAsync()50%2261.53%
ObtenerParametrosPeriodoAsync()100%11100%
CargarHistoricoSemestreAsync()75%4472.72%
ObtenerMesesEsperados(...)62.5%8892.85%
FormatearMesAnio(...)100%11100%

File(s)

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

#LineLine coverage
 1using FAU.DataAccess;
 2using FAU.DataAccess.Repositories;
 3using FAU.Entidades;
 4using FAU.Logica.DTOs;
 5using Microsoft.EntityFrameworkCore;
 6
 7namespace FAU.Logica.Services;
 8
 9public class AguinaldoBajaService : IAguinaldoBajaService
 10{
 11    private readonly IAguinaldoBajaRepository _aguinaldoBajaRepository;
 12    private readonly ApplicationDbContext _context;
 13    private readonly AguinaldoBajaCalculator _calculator;
 14    private readonly IAuditoriaService _auditoriaService;
 15    private readonly ICurrentUserContext _currentUser;
 16
 1617    public AguinaldoBajaService(
 1618        IAguinaldoBajaRepository aguinaldoBajaRepository,
 1619        ApplicationDbContext context,
 1620        AguinaldoBajaCalculator calculator,
 1621        IAuditoriaService auditoriaService,
 1622        ICurrentUserContext currentUser)
 23    {
 1624        _aguinaldoBajaRepository = aguinaldoBajaRepository;
 1625        _context = context;
 1626        _calculator = calculator;
 1627        _auditoriaService = auditoriaService;
 1628        _currentUser = currentUser;
 1629    }
 30
 31    public async Task<AguinaldoValidacionDto> ValidarHistoricoAsync(CalcularAguinaldoBajaRequest request)
 32    {
 833        var resultado = new AguinaldoValidacionDto();
 34
 835        if (!EsMesProcesable(request.Mes))
 36        {
 237            resultado.PersonasConHistoricoIncompleto = 1;
 238            resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 239            {
 240                Tipo = "MES_INVALIDO",
 241                Cedula = string.Empty,
 242                NombreCompleto = string.Empty,
 243                Descripcion = "El cálculo de aguinaldo por baja no aplica en los meses 6 o 12 (cubiertos por el proceso 
 244            });
 245            return resultado;
 46        }
 47
 648        var parametros = await ObtenerParametrosPeriodoAsync(request);
 649        if (parametros is null)
 50        {
 151            resultado.PersonasConHistoricoIncompleto = 1;
 152            resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 153            {
 154                Tipo = "PARAMETROS_INEXISTENTES",
 155                Cedula = string.Empty,
 156                NombreCompleto = string.Empty,
 157                Descripcion = "No existe configuración de parámetros para el período solicitado."
 158            });
 159            return resultado;
 60        }
 61
 562        var bajas = (await ObtenerBajasParaPeriodoAsync(request)).ToList();
 563        resultado.TotalPersonasActivas = bajas.Count;
 64
 565        var semestre = CalcularSemestre(request.Mes);
 966        var personaIds = bajas.Select(b => b.RelacionLaboral.PersonaId).ToList();
 67
 68        // Pre-cargar historico de todo el período para evitar N+1
 569        var historicosPorPersona = await CargarHistoricoSemestreAsync(request, personaIds);
 70
 1871        foreach (var baja in bajas)
 72        {
 473            var personaId = baja.RelacionLaboral.PersonaId;
 74
 475            var yaProcesado = await _aguinaldoBajaRepository.GetByPersonaFechaBajaAsync(
 476                personaId, baja.FechaMovimiento);
 77
 478            if (yaProcesado is not null)
 79            {
 180                resultado.PersonasConHistoricoIncompleto++;
 181                resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 182                {
 183                    Tipo = "YA_PROCESADO",
 184                    Cedula = baja.RelacionLaboral.Persona.Cedula,
 185                    NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 186                    Descripcion = "Ya existe un aguinaldo por baja para esta fecha de baja."
 187                });
 188                continue;
 89            }
 90
 391            var cubiertoPorSemestral = await _context.Set<Aguinaldo>()
 392                .AnyAsync(a => a.PersonaId == personaId
 393                    && a.Anio == request.Anio
 394                    && a.Semestre == semestre);
 95
 396            if (cubiertoPorSemestral)
 97            {
 198                resultado.PersonasConHistoricoIncompleto++;
 199                resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 1100                {
 1101                    Tipo = "CUBIERTO_POR_SEMESTRAL",
 1102                    Cedula = baja.RelacionLaboral.Persona.Cedula,
 1103                    NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 1104                    Descripcion = $"El semestre {semestre}/{request.Anio} ya fue cubierto por el proceso de aguinaldo se
 1105                });
 1106                continue;
 107            }
 108
 2109            var mesesComputados = CalcularMesesComputados(request, baja.RelacionLaboral.FechaInicio);
 2110            if (mesesComputados <= 0)
 111            {
 0112                resultado.PersonasConHistoricoIncompleto++;
 0113                resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 0114                {
 0115                    Tipo = "MESES_INVALIDOS",
 0116                    Cedula = baja.RelacionLaboral.Persona.Cedula,
 0117                    NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 0118                    Descripcion = "No se pudo calcular el período de meses válidos para la baja."
 0119                });
 0120                continue;
 121            }
 122
 123            // Verificar meses faltantes en historico_liquidaciones
 2124            var mesesEsperados = ObtenerMesesEsperados(request, baja.RelacionLaboral.FechaInicio);
 2125            var encontrados = historicosPorPersona.GetValueOrDefault(personaId) ?? new HashSet<(short, short)>();
 8126            var faltantes = mesesEsperados.Where(m => !encontrados.Contains(m)).ToList();
 127
 2128            if (faltantes.Any())
 129            {
 1130                resultado.PersonasConHistoricoIncompleto++;
 1131                resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 1132                {
 1133                    Tipo = "HistoricoIncompleto",
 1134                    Cedula = baja.RelacionLaboral.Persona.Cedula,
 1135                    NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 1136                    MesesFaltantes = faltantes.Select(m => FormatearMesAnio(m.Anio, m.Mes)).ToList(),
 1137                    Descripcion = "Faltan meses en el histórico de liquidaciones para el período de baja."
 1138                });
 139            }
 140            else
 141            {
 1142                resultado.PersonasListasParaCalcular++;
 143            }
 2144        }
 145
 5146        return resultado;
 8147    }
 148
 149    public async Task<(bool Success, IEnumerable<AguinaldoBajaDto> Resultados, string? Error)> CalcularAguinaldoBajaAsyn
 150    {
 5151        if (!EsMesProcesable(request.Mes))
 152        {
 1153            return (false, Array.Empty<AguinaldoBajaDto>(), "El cálculo de aguinaldo por baja no aplica en los meses 6 o
 154        }
 155
 4156        var parametros = await ObtenerParametrosPeriodoAsync(request);
 4157        if (parametros is null)
 158        {
 1159            return (false, Array.Empty<AguinaldoBajaDto>(), "No existe configuración de parámetros para el período solic
 160        }
 161
 3162        var bajas = await ObtenerBajasParaPeriodoAsync(request);
 3163        var resultados = new List<AguinaldoBajaDto>();
 164
 3165        var semestre = CalcularSemestre(request.Mes);
 3166        var esPrimerSemestre = request.Mes <= 6;
 3167        var mesInicioPeriodo = esPrimerSemestre ? (short)12 : (short)6;
 3168        var mesFinPeriodo = (short)request.Mes;
 3169        var mesLiquidacion = (short)request.Mes;
 170
 12171        foreach (var baja in bajas)
 172        {
 3173            var existe = await _aguinaldoBajaRepository.GetByPersonaFechaBajaAsync(
 3174                baja.RelacionLaboral.PersonaId,
 3175                baja.FechaMovimiento);
 176
 3177            if (existe is not null)
 178                continue;
 179
 180            // D6: No procesar si el semestre ya fue cubierto por aguinaldo semestral
 2181            var cubiertoPorSemestral = await _context.Set<Aguinaldo>()
 2182                .AnyAsync(a => a.PersonaId == baja.RelacionLaboral.PersonaId
 2183                    && a.Anio == request.Anio
 2184                    && a.Semestre == semestre);
 185
 2186            if (cubiertoPorSemestral)
 187                continue;
 188
 1189            var mesesComputados = CalcularMesesComputados(request, baja.RelacionLaboral.FechaInicio);
 1190            if (mesesComputados <= 0)
 191                continue;
 192
 1193            var totalHaberesGravados = await ObtenerTotalHaberesGravadosAsync(request, baja.RelacionLaboral.PersonaId);
 1194            var tasaMontepio = baja.RelacionLaboral.Regimen.EsLeyVieja
 1195                ? parametros.TasaMontepioLeyVieja
 1196                : parametros.TasaMontepioLeyNueva;
 1197            var tasaSanidad = parametros.TasaFonasa;
 198
 1199            var resultadoCalculo = _calculator.Calcular(
 1200                totalHaberesGravados,
 1201                mesesComputados,
 1202                tasaMontepio,
 1203                tasaSanidad);
 204
 1205            var estado = totalHaberesGravados == 0m
 1206                ? "SIN_DERECHO"
 1207                : resultadoCalculo.Liquido <= 0m
 1208                    ? "INCIDENCIA"
 1209                    : "VALIDO";
 210
 1211            var aguinaldo = new AguinaldoBaja
 1212            {
 1213                PersonaId = baja.RelacionLaboral.PersonaId,
 1214                Cedula = baja.RelacionLaboral.Persona.Cedula,
 1215                NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 1216                FechaBaja = baja.FechaMovimiento,
 1217                MotivoBajaCodigo = baja.RelacionLaboral.MotivoBaja?.Codigo,
 1218                Anio = (short)request.Anio,
 1219                Mes = (short)request.Mes,
 1220                Semestre = semestre,
 1221                TotalHaberesGravados = totalHaberesGravados,
 1222                MesesComputados = mesesComputados,
 1223                MesInicioPeriodo = mesInicioPeriodo,
 1224                MesFinPeriodo = mesFinPeriodo,
 1225                MesLiquidacion = mesLiquidacion,
 1226                Nominal = resultadoCalculo.Nominal,
 1227                Montepio = resultadoCalculo.Montepio,
 1228                Sanidad = resultadoCalculo.Sanidad,
 1229                TotalDescuentos = resultadoCalculo.TotalDescuentos,
 1230                Liquido = resultadoCalculo.Liquido,
 1231                Redondeo = resultadoCalculo.Redondeo,
 1232                Estado = estado,
 1233                Items = new List<AguinaldoBajaItem>
 1234                {
 1235                    new AguinaldoBajaItem
 1236                    {
 1237                        CodigoConcepto = "AGUINALDO_NOMINAL",
 1238                        NombreConcepto = "Nominal aguinaldo por baja",
 1239                        Importe = resultadoCalculo.Nominal,
 1240                        Tipo = "haber"
 1241                    },
 1242                    new AguinaldoBajaItem
 1243                    {
 1244                        CodigoConcepto = "AGUINALDO_MONTEPIO",
 1245                        NombreConcepto = "Montepío aguinaldo por baja",
 1246                        Importe = -resultadoCalculo.Montepio,
 1247                        Tipo = "descuento"
 1248                    },
 1249                    new AguinaldoBajaItem
 1250                    {
 1251                        CodigoConcepto = "AGUINALDO_DNS",
 1252                        NombreConcepto = "D.N.S.FF.AA. aguinaldo por baja",
 1253                        Importe = -resultadoCalculo.Sanidad,
 1254                        Tipo = "descuento"
 1255                    }
 1256                }
 1257            };
 258
 1259            var creado = await _aguinaldoBajaRepository.CreateAsync(aguinaldo);
 1260            resultados.Add(AguinaldoBajaDto.FromEntity(creado));
 261
 262            try
 263            {
 1264                await _auditoriaService.LogAuditoriaAsync(
 1265                    _currentUser.UserId ?? 0,
 1266                    AccionEnum.GenerarLiquidacion,
 1267                    ContextoEnum.Liquidaciones,
 1268                    entidad: "aguinaldo_baja",
 1269                    entidadId: creado.Id,
 1270                    detalle: new { Resultado = resultadoCalculo, Estado = estado });
 1271            }
 0272            catch
 273            {
 274                // No bloquear el proceso por fallos en auditoría
 0275            }
 1276        }
 277
 3278        return (true, resultados, null);
 5279    }
 280
 281    public async Task<IEnumerable<AguinaldoBajaDto>> ObtenerPorPeriodoAsync(int anio, int mes)
 282    {
 3283        if (anio <= 0 || mes < 1 || mes > 12)
 2284            return Enumerable.Empty<AguinaldoBajaDto>();
 285
 1286        var registros = await _aguinaldoBajaRepository.GetByPeriodoAsync((short)anio, (short)mes);
 1287        return registros.Select(AguinaldoBajaDto.FromEntity).ToList();
 3288    }
 289
 290    private async Task<IEnumerable<MovimientoLaboral>> ObtenerBajasParaPeriodoAsync(CalcularAguinaldoBajaRequest request
 291    {
 8292        return await _context.MovimientosLaborales
 8293            .Include(m => m.RelacionLaboral)
 8294                .ThenInclude(rl => rl.Persona)
 8295            .Include(m => m.RelacionLaboral)
 8296                .ThenInclude(rl => rl.Regimen)
 8297            .Include(m => m.RelacionLaboral)
 8298                .ThenInclude(rl => rl.Situacion)
 8299            .Include(m => m.RelacionLaboral)
 8300                .ThenInclude(rl => rl.MotivoBaja)
 8301            .Include(m => m.TipoMovimiento)
 8302            .Where(m => m.FechaMovimiento.Year == request.Anio
 8303                && m.FechaMovimiento.Month == request.Mes
 8304                && !m.TipoMovimiento.EsAlta)
 8305            .ToListAsync();
 8306    }
 307
 308    // D3: el cálculo de baja aplica en todos los meses EXCEPTO junio y diciembre,
 309    // que quedan cubiertos por el proceso semestral.
 310    private static bool EsMesProcesable(int mes)
 311    {
 13312        return mes != 6 && mes != 12;
 313    }
 314
 315    private static short CalcularSemestre(int mes)
 316    {
 8317        return mes <= 6 ? (short)1 : (short)2;
 318    }
 319
 320    private static short CalcularMesesComputados(CalcularAguinaldoBajaRequest request, DateTime fechaInicio)
 321    {
 322        // S1: comienza en diciembre del año anterior
 323        // S2: comienza en junio del año actual
 3324        var esPrimerSemestre = request.Mes <= 6;
 3325        var inicioSemestre = esPrimerSemestre
 3326            ? new DateTime(request.Anio - 1, 12, 1)
 3327            : new DateTime(request.Anio, 6, 1);
 328
 3329        var fechaInicioValida = fechaInicio > inicioSemestre ? fechaInicio : inicioSemestre;
 3330        var fechaFin = new DateTime(request.Anio, request.Mes, 1);
 331
 3332        if (fechaInicioValida > fechaFin)
 0333            return 0;
 334
 3335        return (short)((fechaFin.Year - fechaInicioValida.Year) * 12 + fechaFin.Month - fechaInicioValida.Month + 1);
 336    }
 337
 338    private async Task<decimal> ObtenerTotalHaberesGravadosAsync(CalcularAguinaldoBajaRequest request, long personaId)
 339    {
 1340        var esPrimerSemestre = request.Mes <= 6;
 341
 1342        if (esPrimerSemestre)
 343        {
 1344            return await _context.HistoricoLiquidaciones
 1345                .Where(h => h.PersonaId == personaId
 1346                    && ((h.Anio == request.Anio - 1 && h.Mes == 12)
 1347                        || (h.Anio == request.Anio && h.Mes >= 1 && h.Mes <= request.Mes)))
 1348                .SumAsync(h => (decimal?)h.HaberesGravados) ?? 0m;
 349        }
 350
 0351        return await _context.HistoricoLiquidaciones
 0352            .Where(h => h.PersonaId == personaId
 0353                && h.Anio == request.Anio
 0354                && h.Mes >= 6 && h.Mes <= request.Mes)
 0355            .SumAsync(h => (decimal?)h.HaberesGravados) ?? 0m;
 1356    }
 357
 358    private async Task<ParametroPeriodo?> ObtenerParametrosPeriodoAsync(CalcularAguinaldoBajaRequest request)
 359    {
 10360        return await _context.ParametrosPeriodo
 10361            .Where(p => p.Anio < request.Anio || (p.Anio == request.Anio && p.Mes <= request.Mes))
 10362            .OrderByDescending(p => p.Anio)
 10363            .ThenByDescending(p => p.Mes)
 10364            .FirstOrDefaultAsync();
 10365    }
 366
 367    private async Task<Dictionary<long, HashSet<(short Anio, short Mes)>>> CargarHistoricoSemestreAsync(
 368        CalcularAguinaldoBajaRequest request, List<long> personaIds)
 369    {
 5370        if (personaIds.Count == 0)
 1371            return new Dictionary<long, HashSet<(short Anio, short Mes)>>();
 372
 373        IQueryable<HistoricoLiquidacion> query;
 374
 4375        if (request.Mes <= 6)
 376        {
 377            // 1er semestre: Dic año anterior + Ene hasta mes de baja del año actual
 4378            var anioAnterior = (short)(request.Anio - 1);
 4379            var anioActual = (short)request.Anio;
 4380            var mesHasta = (short)request.Mes;
 4381            query = _context.HistoricoLiquidaciones
 4382                .Where(h => personaIds.Contains(h.PersonaId)
 4383                    && ((h.Anio == anioAnterior && h.Mes == 12)
 4384                        || (h.Anio == anioActual && h.Mes >= 1 && h.Mes <= mesHasta)));
 385        }
 386        else
 387        {
 388            // 2do semestre: Jun hasta mes de baja del año actual
 0389            var anioActual = (short)request.Anio;
 0390            var mesHasta = (short)request.Mes;
 0391            query = _context.HistoricoLiquidaciones
 0392                .Where(h => personaIds.Contains(h.PersonaId)
 0393                    && h.Anio == anioActual
 0394                    && h.Mes >= 6 && h.Mes <= mesHasta);
 395        }
 396
 4397        var registros = await query
 4398            .Select(h => new { h.PersonaId, h.Anio, h.Mes })
 4399            .ToListAsync();
 400
 4401        return registros
 11402            .GroupBy(h => h.PersonaId)
 4403            .ToDictionary(
 4404                g => g.Key,
 19405                g => g.Select(h => (h.Anio, h.Mes)).ToHashSet());
 5406    }
 407
 408    private static List<(short Anio, short Mes)> ObtenerMesesEsperados(
 409        CalcularAguinaldoBajaRequest request, DateTime fechaInicio)
 410    {
 2411        var esPrimerSemestre = request.Mes <= 6;
 2412        var inicioSemestre = esPrimerSemestre
 2413            ? new DateTime(request.Anio - 1, 12, 1)
 2414            : new DateTime(request.Anio, 6, 1);
 415
 2416        var fechaInicioValida = fechaInicio > inicioSemestre ? fechaInicio : inicioSemestre;
 2417        var fechaFin = new DateTime(request.Anio, request.Mes, 1);
 418
 2419        if (fechaInicioValida > fechaFin)
 0420            return new List<(short Anio, short Mes)>();
 421
 2422        var meses = new List<(short Anio, short Mes)>();
 2423        var current = new DateTime(fechaInicioValida.Year, fechaInicioValida.Month, 1);
 8424        while (current <= fechaFin)
 425        {
 6426            meses.Add(((short)current.Year, (short)current.Month));
 6427            current = current.AddMonths(1);
 428        }
 2429        return meses;
 430    }
 431
 1432    private static string FormatearMesAnio(short anio, short mes) => $"{mes:D2}/{anio:D4}";
 433}