< Summary

Information
Class: FAU.Logica.Services.ParametroLiquidacionService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/ParametroLiquidacionService.cs
Line coverage
91%
Covered lines: 144
Uncovered lines: 13
Coverable lines: 157
Total lines: 241
Line coverage: 91.7%
Branch coverage
68%
Covered branches: 34
Total branches: 50
Branch coverage: 68%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ObtenerActualAsync()50%22100%
GuardarAsync()100%44100%
ObtenerFranjasAsync()0%620%
GuardarFranjasAsync()75%8893.75%
ValidarParametro(...)70%101084.21%
ValidarCoberturaFranjas(...)68.18%352270%
MapToDto(...)50%22100%
MapFranjas(...)100%11100%

File(s)

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

#LineLine coverage
 1using FAU.DataAccess.Repositories;
 2using FAU.Entidades;
 3using FAU.Logica.DTOs;
 4using Microsoft.Extensions.Logging;
 5
 6namespace FAU.Logica.Services;
 7
 8public class ParametroLiquidacionService : IParametroLiquidacionService
 9{
 10    private readonly IParametroLiquidacionRepository _parametroRepository;
 11    private readonly IIrpfRepository _irpfRepository;
 12    private readonly IAuditoriaService _auditoriaService;
 13    private readonly ICurrentUserContext _currentUser;
 14    private readonly ILogger<ParametroLiquidacionService> _logger;
 15
 616    public ParametroLiquidacionService(
 617        IParametroLiquidacionRepository parametroRepository,
 618        IIrpfRepository irpfRepository,
 619        IAuditoriaService auditoriaService,
 620        ICurrentUserContext currentUser,
 621        ILogger<ParametroLiquidacionService> logger)
 22    {
 623        _parametroRepository = parametroRepository;
 624        _irpfRepository = irpfRepository;
 625        _auditoriaService = auditoriaService;
 626        _currentUser = currentUser;
 627        _logger = logger;
 628    }
 29
 30    public async Task<ParametroLiquidacionDto?> ObtenerActualAsync()
 31    {
 132        var parametro = await _parametroRepository.ObtenerActualAsync();
 133        return parametro == null ? null : MapToDto(parametro);
 134    }
 35
 36    public async Task<(bool Success, ParametroLiquidacionDto? Parametro, string? Error)> GuardarAsync(
 37        long usuarioId, ConfigurarParametrosLiquidacionRequest request)
 38    {
 339        var error = ValidarParametro(request);
 340        if (error != null)
 141            return (false, null, error);
 42
 243        var existente = await _parametroRepository.ObtenerActualAsync();
 44
 45        ParametroPeriodo resultado;
 246        if (existente != null)
 47        {
 148            existente.BpcAnual = request.BpcAnual;
 149            existente.TasaMontepioLeyVieja = request.TasaMontepioLeyVieja;
 150            existente.TasaMontepioLeyNueva = request.TasaMontepioLeyNueva;
 151            existente.TasaFonasa = request.TasaFonasa;
 152            existente.TasaFrl = request.TasaFrl;
 153            existente.UmbralDeduccionBpc = request.UmbralDeduccionBpc;
 154            existente.TasaDeduccionAlta = request.TasaDeduccionAlta;
 155            existente.TasaDeduccionBaja = request.TasaDeduccionBaja;
 156            existente.PorcentajeMinimoDeficit = request.PorcentajeMinimoDeficit;
 157            existente.CreadoPor = usuarioId;
 158            existente.CreadoEn = DateTime.UtcNow;
 159            resultado = await _parametroRepository.ActualizarAsync(existente);
 60        }
 61        else
 62        {
 163            var nuevo = new ParametroPeriodo
 164            {
 165                Anio = (short)DateTime.UtcNow.Year,
 166                Mes = (short)DateTime.UtcNow.Month,
 167                BpcAnual = request.BpcAnual,
 168                TasaMontepioLeyVieja = request.TasaMontepioLeyVieja,
 169                TasaMontepioLeyNueva = request.TasaMontepioLeyNueva,
 170                TasaFonasa = request.TasaFonasa,
 171                TasaFrl = request.TasaFrl,
 172                UmbralDeduccionBpc = request.UmbralDeduccionBpc,
 173                TasaDeduccionAlta = request.TasaDeduccionAlta,
 174                TasaDeduccionBaja = request.TasaDeduccionBaja,
 175                PorcentajeMinimoDeficit = request.PorcentajeMinimoDeficit,
 176                CreadoPor = usuarioId,
 177                CreadoEn = DateTime.UtcNow
 178            };
 179            resultado = await _parametroRepository.CrearAsync(nuevo);
 80        }
 81
 282        _logger.LogInformation("Parámetros de liquidación actualizados por usuario {UsuarioId}", usuarioId);
 283        await _auditoriaService.LogAuditoriaAsync(
 284            usuarioId, AccionEnum.ActualizarCompensacion, ContextoEnum.Liquidaciones,
 285            _currentUser.Host, entidad: "ParametroPeriodo", entidadId: resultado.Id,
 286            detalle: new { bpcAnual = resultado.BpcAnual, tasaFonasa = resultado.TasaFonasa });
 87
 288        return (true, MapToDto(resultado), null);
 389    }
 90
 91    public async Task<ConfiguracionFranjasIrpfDto?> ObtenerFranjasAsync()
 92    {
 093        var franjas = (await _irpfRepository.GetFranjasVigentesAsync(null)).ToList();
 094        return franjas.Count == 0 ? null : MapFranjas(franjas);
 095    }
 96
 97    public async Task<(bool Success, ConfiguracionFranjasIrpfDto? Configuracion, string? Error)> GuardarFranjasAsync(
 98        ConfigurarFranjasIrpfRequest request)
 99    {
 2100        if (request.Franjas == null || request.Franjas.Count == 0)
 0101            return (false, null, "Debe proporcionar al menos una franja IRPF");
 102
 6103        var franjasOrdenadas = request.Franjas.OrderBy(f => f.NumeroFranja).ToList();
 104
 2105        var error = ValidarCoberturaFranjas(franjasOrdenadas);
 2106        if (error != null)
 1107            return (false, null, error);
 108
 109        // Eliminar todas las franjas existentes
 1110        var existentes = (await _irpfRepository.GetHistorialFranjasAsync()).ToList();
 6111        foreach (var franja in existentes)
 2112            await _irpfRepository.DeleteFranjaAsync(franja.Id);
 113
 114        // Crear las nuevas
 3115        var nuevasFranjas = franjasOrdenadas.Select(f => new FranjaIrpf
 3116        {
 3117            NumeroFranja = f.NumeroFranja,
 3118            DesdeBpc = f.DesdeBpc,
 3119            HastaBpc = f.HastaBpc,
 3120            Tasa = f.Tasa,
 3121            VigenteDesde = DateTime.UtcNow.Date
 3122        }).ToList();
 123
 1124        await _irpfRepository.CreateFranjasAsync(nuevasFranjas);
 1125        _logger.LogInformation("Franjas IRPF reemplazadas: {Count} franjas", nuevasFranjas.Count);
 126
 1127        await _auditoriaService.LogAuditoriaAsync(
 1128            _currentUser.UserId ?? 0, AccionEnum.ActualizarFranja, ContextoEnum.IRPF,
 1129            _currentUser.Host, entidad: "FranjaIrpf", entidadId: 0,
 1130            detalle: new { cantidadFranjas = nuevasFranjas.Count });
 131
 1132        return (true, MapFranjas(nuevasFranjas), null);
 2133    }
 134
 135    // ── Validaciones ────────────────────────────────────────────────────────
 136
 137    private static string? ValidarParametro(ConfigurarParametrosLiquidacionRequest request)
 138    {
 3139        if (request.BpcAnual <= 0)
 1140            return "La BPC anual debe ser mayor a cero";
 141
 2142        var tasas = new Dictionary<string, decimal>
 2143        {
 2144            ["tasa_montepio_ley_vieja"] = request.TasaMontepioLeyVieja,
 2145            ["tasa_montepio_ley_nueva"] = request.TasaMontepioLeyNueva,
 2146            ["tasa_fonasa"] = request.TasaFonasa,
 2147            ["tasa_frl"] = request.TasaFrl,
 2148            ["tasa_deduccion_alta"] = request.TasaDeduccionAlta,
 2149            ["tasa_deduccion_baja"] = request.TasaDeduccionBaja,
 2150            ["porcentaje_minimo_deficit"] = request.PorcentajeMinimoDeficit
 2151        };
 152
 32153        foreach (var tasa in tasas)
 154        {
 14155            if (tasa.Value < 0 || tasa.Value > 1)
 0156                return $"El valor de {tasa.Key} debe estar entre 0 y 1";
 157        }
 158
 2159        if (request.UmbralDeduccionBpc < 0)
 0160            return "El umbral de deducción en BPC no puede ser negativo";
 161
 2162        return null;
 0163    }
 164
 165    private static string? ValidarCoberturaFranjas(IReadOnlyList<FranjaIrpfConfiguradaRequest> franjas)
 166    {
 10167        var duplicados = franjas.GroupBy(f => f.NumeroFranja).FirstOrDefault(g => g.Count() > 1);
 2168        if (duplicados != null)
 0169            return $"La franja {duplicados.Key} está duplicada";
 170
 2171        if (franjas[0].DesdeBpc != 0)
 0172            return "La primera franja debe comenzar en 0 BPC";
 173
 8174        for (var i = 0; i < franjas.Count; i++)
 175        {
 3176            var franja = franjas[i];
 177
 3178            if (franja.HastaBpc.HasValue && franja.DesdeBpc >= franja.HastaBpc.Value)
 0179                return $"La franja {franja.NumeroFranja} tiene un rango inválido";
 180
 3181            if (franja.Tasa < 0 || franja.Tasa > 1)
 0182                return $"La tasa de la franja {franja.NumeroFranja} debe estar entre 0 y 1";
 183
 3184            if (i < franjas.Count - 1)
 185            {
 2186                var siguiente = franjas[i + 1];
 2187                if (!franja.HastaBpc.HasValue)
 0188                    return $"La franja {franja.NumeroFranja} solo puede quedar abierta si es la última";
 2189                if (franja.HastaBpc.Value != siguiente.DesdeBpc)
 1190                    return $"Las franjas {franja.NumeroFranja} y {siguiente.NumeroFranja} no cubren el rango de forma co
 191            }
 1192            else if (franja.HastaBpc.HasValue)
 193            {
 0194                return "La última franja debe quedar abierta sin límite superior";
 195            }
 196        }
 197
 1198        return null;
 199    }
 200
 201    // ── Mappers ─────────────────────────────────────────────────────────────
 202
 3203    private static ParametroLiquidacionDto MapToDto(ParametroPeriodo p) => new()
 3204    {
 3205        Id = p.Id,
 3206        Anio = p.Anio,
 3207        Mes = p.Mes,
 3208        BpcAnual = p.BpcAnual,
 3209        TasaMontepioLeyVieja = p.TasaMontepioLeyVieja,
 3210        TasaMontepioLeyNueva = p.TasaMontepioLeyNueva,
 3211        TasaFonasa = p.TasaFonasa,
 3212        TasaFrl = p.TasaFrl,
 3213        UmbralDeduccionBpc = p.UmbralDeduccionBpc,
 3214        TasaDeduccionAlta = p.TasaDeduccionAlta,
 3215        TasaDeduccionBaja = p.TasaDeduccionBaja,
 3216        PorcentajeMinimoDeficit = p.PorcentajeMinimoDeficit,
 3217        CreadoEn = p.CreadoEn,
 3218        CreadoPor = p.CreadoPor,
 3219        UsuarioCreador = p.UsuarioCreador?.Username
 3220    };
 221
 222    private static ConfiguracionFranjasIrpfDto MapFranjas(IEnumerable<FranjaIrpf> franjas)
 223    {
 3224        var lista = franjas.OrderBy(f => f.NumeroFranja).ToList();
 1225        return new ConfiguracionFranjasIrpfDto
 1226        {
 1227            FechaVigencia = lista[0].VigenteDesde,
 1228            FechaFinVigencia = lista[0].VigenteHasta,
 2229            Franjas = lista.Select(f => new FranjaIrpfDto
 2230            {
 2231                Id = f.Id,
 2232                NumeroFranja = f.NumeroFranja,
 2233                DesdeBpc = f.DesdeBpc,
 2234                HastaBpc = f.HastaBpc,
 2235                Tasa = f.Tasa,
 2236                VigenteDesde = f.VigenteDesde,
 2237                VigenteHasta = f.VigenteHasta
 2238            }).ToList()
 1239        };
 240    }
 241}