< 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
67%
Covered lines: 177
Uncovered lines: 84
Coverable lines: 261
Total lines: 376
Line coverage: 67.8%
Branch coverage
42%
Covered branches: 23
Total branches: 54
Branch coverage: 42.5%
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()0%156120%
CalcularAguinaldoBajaAsync()72.72%222297.52%
ObtenerPorPeriodoAsync()0%7280%
ObtenerBajasParaPeriodoAsync()100%11100%
EsMesProcesable(...)100%22100%
CalcularSemestre(...)50%22100%
CalcularMesesComputados(...)50%6688.88%
ObtenerTotalHaberesGravadosAsync()50%2261.53%
ObtenerParametrosPeriodoAsync()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
 217    public AguinaldoBajaService(
 218        IAguinaldoBajaRepository aguinaldoBajaRepository,
 219        ApplicationDbContext context,
 220        AguinaldoBajaCalculator calculator,
 221        IAuditoriaService auditoriaService,
 222        ICurrentUserContext currentUser)
 23    {
 224        _aguinaldoBajaRepository = aguinaldoBajaRepository;
 225        _context = context;
 226        _calculator = calculator;
 227        _auditoriaService = auditoriaService;
 228        _currentUser = currentUser;
 229    }
 30
 31    public async Task<AguinaldoValidacionDto> ValidarHistoricoAsync(CalcularAguinaldoBajaRequest request)
 32    {
 033        var resultado = new AguinaldoValidacionDto();
 34
 035        if (!EsMesProcesable(request.Mes))
 36        {
 037            resultado.PersonasConHistoricoIncompleto = 1;
 038            resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 039            {
 040                Tipo = "MES_INVALIDO",
 041                Cedula = string.Empty,
 042                NombreCompleto = string.Empty,
 043                Descripcion = "El cálculo de aguinaldo por baja no aplica en los meses 6 o 12 (cubiertos por el proceso 
 044            });
 45
 046            return resultado;
 47        }
 48
 049        var parametros = await ObtenerParametrosPeriodoAsync(request);
 050        if (parametros is null)
 51        {
 052            resultado.PersonasConHistoricoIncompleto = 1;
 053            resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 054            {
 055                Tipo = "PARAMETROS_INEXISTENTES",
 056                Cedula = string.Empty,
 057                NombreCompleto = string.Empty,
 058                Descripcion = "No existe configuración de parámetros para el período solicitado."
 059            });
 60
 061            return resultado;
 62        }
 63
 064        var bajas = await ObtenerBajasParaPeriodoAsync(request);
 065        resultado.TotalPersonasActivas = bajas.Count();
 066        resultado.PersonasListasParaCalcular = bajas.Count();
 67
 068        var semestre = CalcularSemestre(request.Mes);
 69
 070        foreach (var baja in bajas)
 71        {
 072            var yaProcesado = await _aguinaldoBajaRepository.GetByPersonaFechaBajaAsync(
 073                baja.RelacionLaboral.PersonaId,
 074                baja.FechaMovimiento);
 75
 076            if (yaProcesado is not null)
 77            {
 078                resultado.PersonasConHistoricoIncompleto++;
 079                resultado.PersonasListasParaCalcular--;
 080                resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 081                {
 082                    Tipo = "YA_PROCESADO",
 083                    Cedula = baja.RelacionLaboral.Persona.Cedula,
 084                    NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 085                    Descripcion = "Ya existe un aguinaldo por baja para esta fecha de baja."
 086                });
 087                continue;
 88            }
 89
 90            // D6: Verificar si el semestre ya fue cubierto por aguinaldo semestral
 091            var cubiertoPorSemestral = await _context.Set<Aguinaldo>()
 092                .AnyAsync(a => a.PersonaId == baja.RelacionLaboral.PersonaId
 093                    && a.Anio == request.Anio
 094                    && a.Semestre == semestre);
 95
 096            if (cubiertoPorSemestral)
 97            {
 098                resultado.PersonasConHistoricoIncompleto++;
 099                resultado.PersonasListasParaCalcular--;
 0100                resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 0101                {
 0102                    Tipo = "CUBIERTO_POR_SEMESTRAL",
 0103                    Cedula = baja.RelacionLaboral.Persona.Cedula,
 0104                    NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 0105                    Descripcion = $"El semestre {semestre}/{request.Anio} ya fue cubierto por el proceso de aguinaldo se
 0106                });
 0107                continue;
 108            }
 109
 0110            var mesesComputados = CalcularMesesComputados(request, baja.RelacionLaboral.FechaInicio);
 0111            if (mesesComputados <= 0)
 112            {
 0113                resultado.PersonasConHistoricoIncompleto++;
 0114                resultado.PersonasListasParaCalcular--;
 0115                resultado.Incidencias.Add(new AguinaldoIncidenciaDto
 0116                {
 0117                    Tipo = "MESES_INVALIDOS",
 0118                    Cedula = baja.RelacionLaboral.Persona.Cedula,
 0119                    NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 0120                    Descripcion = "No se pudo calcular el período de meses válidos para la baja."
 0121                });
 122            }
 0123        }
 124
 0125        return resultado;
 0126    }
 127
 128    public async Task<(bool Success, IEnumerable<AguinaldoBajaDto> Resultados, string? Error)> CalcularAguinaldoBajaAsyn
 129    {
 2130        if (!EsMesProcesable(request.Mes))
 131        {
 1132            return (false, Array.Empty<AguinaldoBajaDto>(), "El cálculo de aguinaldo por baja no aplica en los meses 6 o
 133        }
 134
 1135        var parametros = await ObtenerParametrosPeriodoAsync(request);
 1136        if (parametros is null)
 137        {
 0138            return (false, Array.Empty<AguinaldoBajaDto>(), "No existe configuración de parámetros para el período solic
 139        }
 140
 1141        var franjasIrpf = await _context.FranjasIrpf
 1142            .OrderBy(f => f.NumeroFranja)
 1143            .ToListAsync();
 144
 1145        var franjasCalculo = franjasIrpf.Select(f => new FranjaIrpfCalculoDto
 1146        {
 1147            FranjaId = f.Id,
 1148            NumeroFranja = f.NumeroFranja,
 1149            DesdeBpc = f.DesdeBpc,
 1150            HastaBpc = f.HastaBpc,
 1151            Tasa = f.Tasa
 1152        }).ToList();
 153
 1154        var parametrosIrpf = new ParametrosIrpfDto
 1155        {
 1156            BpcMensual = parametros.BpcAnual / 12m,
 1157            UmbralDeduccionBpc = parametros.UmbralDeduccionBpc,
 1158            TasaDeduccionAlta = parametros.TasaDeduccionAlta,
 1159            TasaDeduccionBaja = parametros.TasaDeduccionBaja
 1160        };
 161
 1162        var bajas = await ObtenerBajasParaPeriodoAsync(request);
 1163        var resultados = new List<AguinaldoBajaDto>();
 164
 1165        var semestre = CalcularSemestre(request.Mes);
 1166        var esPrimerSemestre = request.Mes <= 6;
 1167        var mesInicioPeriodo = esPrimerSemestre ? (short)12 : (short)6;
 1168        var mesFinPeriodo = (short)request.Mes;
 1169        var mesLiquidacion = (short)request.Mes;
 170
 4171        foreach (var baja in bajas)
 172        {
 1173            var existe = await _aguinaldoBajaRepository.GetByPersonaFechaBajaAsync(
 1174                baja.RelacionLaboral.PersonaId,
 1175                baja.FechaMovimiento);
 176
 1177            if (existe is not null)
 178                continue;
 179
 180            // D6: No procesar si el semestre ya fue cubierto por aguinaldo semestral
 1181            var cubiertoPorSemestral = await _context.Set<Aguinaldo>()
 1182                .AnyAsync(a => a.PersonaId == baja.RelacionLaboral.PersonaId
 1183                    && a.Anio == request.Anio
 1184                    && a.Semestre == semestre);
 185
 1186            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                franjasCalculo,
 1203                parametrosIrpf,
 1204                tasaMontepio,
 1205                tasaSanidad);
 206
 1207            var estado = totalHaberesGravados == 0m
 1208                ? "SIN_DERECHO"
 1209                : resultadoCalculo.Liquido <= 0m
 1210                    ? "INCIDENCIA"
 1211                    : "VALIDO";
 212
 1213            var aguinaldo = new AguinaldoBaja
 1214            {
 1215                PersonaId = baja.RelacionLaboral.PersonaId,
 1216                Cedula = baja.RelacionLaboral.Persona.Cedula,
 1217                NombreCompleto = baja.RelacionLaboral.Persona.NombreCompleto,
 1218                FechaBaja = baja.FechaMovimiento,
 1219                MotivoBajaCodigo = baja.RelacionLaboral.MotivoBaja?.Codigo,
 1220                Anio = (short)request.Anio,
 1221                Mes = (short)request.Mes,
 1222                Semestre = semestre,
 1223                TotalHaberesGravados = totalHaberesGravados,
 1224                MesesComputados = mesesComputados,
 1225                MesInicioPeriodo = mesInicioPeriodo,
 1226                MesFinPeriodo = mesFinPeriodo,
 1227                MesLiquidacion = mesLiquidacion,
 1228                Nominal = resultadoCalculo.Nominal,
 1229                Irpf = resultadoCalculo.Irpf,
 1230                Montepio = resultadoCalculo.Montepio,
 1231                Sanidad = resultadoCalculo.Sanidad,
 1232                TotalDescuentos = resultadoCalculo.TotalDescuentos,
 1233                Liquido = resultadoCalculo.Liquido,
 1234                Redondeo = resultadoCalculo.Redondeo,
 1235                Estado = estado,
 1236                Items = new List<AguinaldoBajaItem>
 1237                {
 1238                    new AguinaldoBajaItem
 1239                    {
 1240                        CodigoConcepto = "AGUINALDO_NOMINAL",
 1241                        NombreConcepto = "Nominal aguinaldo por baja",
 1242                        Importe = resultadoCalculo.Nominal,
 1243                        Tipo = "haber"
 1244                    },
 1245                    new AguinaldoBajaItem
 1246                    {
 1247                        CodigoConcepto = "AGUINALDO_IRPF",
 1248                        NombreConcepto = "IRPF aguinaldo por baja",
 1249                        Importe = -resultadoCalculo.Irpf,
 1250                        Tipo = "descuento"
 1251                    },
 1252                    new AguinaldoBajaItem
 1253                    {
 1254                        CodigoConcepto = "AGUINALDO_MONTEPIO",
 1255                        NombreConcepto = "Montepío aguinaldo por baja",
 1256                        Importe = -resultadoCalculo.Montepio,
 1257                        Tipo = "descuento"
 1258                    },
 1259                    new AguinaldoBajaItem
 1260                    {
 1261                        CodigoConcepto = "AGUINALDO_DNS",
 1262                        NombreConcepto = "D.N.S.FF.AA. aguinaldo por baja",
 1263                        Importe = -resultadoCalculo.Sanidad,
 1264                        Tipo = "descuento"
 1265                    }
 1266                }
 1267            };
 268
 1269            var creado = await _aguinaldoBajaRepository.CreateAsync(aguinaldo);
 1270            resultados.Add(AguinaldoBajaDto.FromEntity(creado));
 271
 272            try
 273            {
 1274                await _auditoriaService.LogAuditoriaAsync(
 1275                    _currentUser.UserId ?? 0,
 1276                    AccionEnum.GenerarLiquidacion,
 1277                    ContextoEnum.Liquidaciones,
 1278                    entidad: "aguinaldo_baja",
 1279                    entidadId: creado.Id,
 1280                    detalle: new { Resultado = resultadoCalculo, Estado = estado });
 1281            }
 0282            catch
 283            {
 284                // No bloquear el proceso por fallos en auditoría
 0285            }
 1286        }
 287
 1288        return (true, resultados, null);
 2289    }
 290
 291    public async Task<IEnumerable<AguinaldoBajaDto>> ObtenerPorPeriodoAsync(int anio, int mes)
 292    {
 0293        if (anio <= 0 || mes < 1 || mes > 12)
 0294            return Enumerable.Empty<AguinaldoBajaDto>();
 295
 0296        var registros = await _aguinaldoBajaRepository.GetByPeriodoAsync((short)anio, (short)mes);
 0297        return registros.Select(AguinaldoBajaDto.FromEntity).ToList();
 0298    }
 299
 300    private async Task<IEnumerable<MovimientoLaboral>> ObtenerBajasParaPeriodoAsync(CalcularAguinaldoBajaRequest request
 301    {
 1302        return await _context.MovimientosLaborales
 1303            .Include(m => m.RelacionLaboral)
 1304                .ThenInclude(rl => rl.Persona)
 1305            .Include(m => m.RelacionLaboral)
 1306                .ThenInclude(rl => rl.Regimen)
 1307            .Include(m => m.RelacionLaboral)
 1308                .ThenInclude(rl => rl.Situacion)
 1309            .Include(m => m.RelacionLaboral)
 1310                .ThenInclude(rl => rl.MotivoBaja)
 1311            .Include(m => m.TipoMovimiento)
 1312            .Where(m => m.FechaMovimiento.Year == request.Anio
 1313                && m.FechaMovimiento.Month == request.Mes
 1314                && !m.TipoMovimiento.EsAlta)
 1315            .ToListAsync();
 1316    }
 317
 318    // D3: el cálculo de baja aplica en todos los meses EXCEPTO junio y diciembre,
 319    // que quedan cubiertos por el proceso semestral.
 320    private static bool EsMesProcesable(int mes)
 321    {
 2322        return mes != 6 && mes != 12;
 323    }
 324
 325    private static short CalcularSemestre(int mes)
 326    {
 1327        return mes <= 6 ? (short)1 : (short)2;
 328    }
 329
 330    private static short CalcularMesesComputados(CalcularAguinaldoBajaRequest request, DateTime fechaInicio)
 331    {
 332        // S1: comienza en diciembre del año anterior
 333        // S2: comienza en junio del año actual
 1334        var esPrimerSemestre = request.Mes <= 6;
 1335        var inicioSemestre = esPrimerSemestre
 1336            ? new DateTime(request.Anio - 1, 12, 1)
 1337            : new DateTime(request.Anio, 6, 1);
 338
 1339        var fechaInicioValida = fechaInicio > inicioSemestre ? fechaInicio : inicioSemestre;
 1340        var fechaFin = new DateTime(request.Anio, request.Mes, 1);
 341
 1342        if (fechaInicioValida > fechaFin)
 0343            return 0;
 344
 1345        return (short)((fechaFin.Year - fechaInicioValida.Year) * 12 + fechaFin.Month - fechaInicioValida.Month + 1);
 346    }
 347
 348    private async Task<decimal> ObtenerTotalHaberesGravadosAsync(CalcularAguinaldoBajaRequest request, long personaId)
 349    {
 1350        var esPrimerSemestre = request.Mes <= 6;
 351
 1352        if (esPrimerSemestre)
 353        {
 1354            return await _context.HistoricoLiquidaciones
 1355                .Where(h => h.PersonaId == personaId
 1356                    && ((h.Anio == request.Anio - 1 && h.Mes == 12)
 1357                        || (h.Anio == request.Anio && h.Mes >= 1 && h.Mes <= request.Mes)))
 1358                .SumAsync(h => (decimal?)h.HaberesGravados) ?? 0m;
 359        }
 360
 0361        return await _context.HistoricoLiquidaciones
 0362            .Where(h => h.PersonaId == personaId
 0363                && h.Anio == request.Anio
 0364                && h.Mes >= 6 && h.Mes <= request.Mes)
 0365            .SumAsync(h => (decimal?)h.HaberesGravados) ?? 0m;
 1366    }
 367
 368    private async Task<ParametroPeriodo?> ObtenerParametrosPeriodoAsync(CalcularAguinaldoBajaRequest request)
 369    {
 1370        return await _context.ParametrosPeriodo
 1371            .Where(p => p.Anio < request.Anio || (p.Anio == request.Anio && p.Mes <= request.Mes))
 1372            .OrderByDescending(p => p.Anio)
 1373            .ThenByDescending(p => p.Mes)
 1374            .FirstOrDefaultAsync();
 1375    }
 376}