< 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
92%
Covered lines: 169
Uncovered lines: 13
Coverable lines: 182
Total lines: 267
Line coverage: 92.8%
Branch coverage
66%
Covered branches: 48
Total branches: 72
Branch coverage: 66.6%
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()69.23%2626100%
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
 716    public ParametroLiquidacionService(
 717        IParametroLiquidacionRepository parametroRepository,
 718        IIrpfRepository irpfRepository,
 719        IAuditoriaService auditoriaService,
 720        ICurrentUserContext currentUser,
 721        ILogger<ParametroLiquidacionService> logger)
 22    {
 723        _parametroRepository = parametroRepository;
 724        _irpfRepository = irpfRepository;
 725        _auditoriaService = auditoriaService;
 726        _currentUser = currentUser;
 727        _logger = logger;
 728    }
 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    {
 439        var error = ValidarParametro(request);
 440        if (error != null)
 141            return (false, null, error);
 42
 343        var existente = await _parametroRepository.ObtenerActualAsync();
 344        var ahora = DateTime.Now;
 45
 46        ParametroPeriodo resultado;
 47        object detalleAuditoria;
 348        if (existente != null && existente.Anio == ahora.Year && existente.Mes == ahora.Month)
 49        {
 150            var cambios = new Dictionary<string, object>();
 251            if (existente.BpcAnual != request.BpcAnual)                           cambios["bpcAnual"]               = ne
 152            if (existente.TasaMontepioLeyVieja != request.TasaMontepioLeyVieja)   cambios["tasaMontepioLeyVieja"]   = ne
 153            if (existente.TasaMontepioLeyNueva != request.TasaMontepioLeyNueva)   cambios["tasaMontepioLeyNueva"]   = ne
 154            if (existente.TasaFonasa != request.TasaFonasa)                       cambios["tasaFonasa"]             = ne
 155            if (existente.TasaFrl != request.TasaFrl)                             cambios["tasaFrl"]                = ne
 156            if (existente.UmbralDeduccionBpc != request.UmbralDeduccionBpc)       cambios["umbralDeduccionBpc"]     = ne
 157            if (existente.TasaDeduccionAlta != request.TasaDeduccionAlta)         cambios["tasaDeduccionAlta"]      = ne
 158            if (existente.TasaDeduccionBaja != request.TasaDeduccionBaja)         cambios["tasaDeduccionBaja"]      = ne
 159            if (existente.PorcentajeMinimoDeficit != request.PorcentajeMinimoDeficit) cambios["porcentajeMinimoDeficit"]
 60
 161            existente.BpcAnual = request.BpcAnual;
 162            existente.TasaMontepioLeyVieja = request.TasaMontepioLeyVieja;
 163            existente.TasaMontepioLeyNueva = request.TasaMontepioLeyNueva;
 164            existente.TasaFonasa = request.TasaFonasa;
 165            existente.TasaFrl = request.TasaFrl;
 166            existente.UmbralDeduccionBpc = request.UmbralDeduccionBpc;
 167            existente.TasaDeduccionAlta = request.TasaDeduccionAlta;
 168            existente.TasaDeduccionBaja = request.TasaDeduccionBaja;
 169            existente.PorcentajeMinimoDeficit = request.PorcentajeMinimoDeficit;
 170            existente.CreadoPor = usuarioId;
 171            existente.CreadoEn = ahora;
 172            resultado = await _parametroRepository.ActualizarAsync(existente);
 173            detalleAuditoria = new { accion = "actualizar", cambios };
 174        }
 75        else
 76        {
 277            var nuevo = new ParametroPeriodo
 278            {
 279                Anio = (short)ahora.Year,
 280                Mes = (short)ahora.Month,
 281                BpcAnual = request.BpcAnual,
 282                TasaMontepioLeyVieja = request.TasaMontepioLeyVieja,
 283                TasaMontepioLeyNueva = request.TasaMontepioLeyNueva,
 284                TasaFonasa = request.TasaFonasa,
 285                TasaFrl = request.TasaFrl,
 286                UmbralDeduccionBpc = request.UmbralDeduccionBpc,
 287                TasaDeduccionAlta = request.TasaDeduccionAlta,
 288                TasaDeduccionBaja = request.TasaDeduccionBaja,
 289                PorcentajeMinimoDeficit = request.PorcentajeMinimoDeficit,
 290                CreadoPor = usuarioId,
 291                CreadoEn = ahora
 292            };
 293            resultado = await _parametroRepository.CrearAsync(nuevo);
 294            detalleAuditoria = new {
 295                accion = "crear",
 296                bpcAnual = resultado.BpcAnual,
 297                tasaMontepioLeyVieja = resultado.TasaMontepioLeyVieja,
 298                tasaMontepioLeyNueva = resultado.TasaMontepioLeyNueva,
 299                tasaFonasa = resultado.TasaFonasa,
 2100                tasaFrl = resultado.TasaFrl,
 2101                umbralDeduccionBpc = resultado.UmbralDeduccionBpc,
 2102                tasaDeduccionAlta = resultado.TasaDeduccionAlta,
 2103                tasaDeduccionBaja = resultado.TasaDeduccionBaja,
 2104                porcentajeMinimoDeficit = resultado.PorcentajeMinimoDeficit
 2105            };
 106        }
 107
 3108        _logger.LogInformation("Parámetros de liquidación actualizados por usuario {UsuarioId}", usuarioId);
 3109        await _auditoriaService.LogAuditoriaAsync(
 3110            usuarioId, AccionEnum.ActualizarParametrosPeriodo, ContextoEnum.Liquidaciones,
 3111            _currentUser.Host, entidad: "ParametroPeriodo", entidadId: resultado.Id,
 3112            detalle: detalleAuditoria);
 113
 3114        return (true, MapToDto(resultado), null);
 4115    }
 116
 117    public async Task<ConfiguracionFranjasIrpfDto?> ObtenerFranjasAsync()
 118    {
 0119        var franjas = (await _irpfRepository.GetFranjasVigentesAsync(null)).ToList();
 0120        return franjas.Count == 0 ? null : MapFranjas(franjas);
 0121    }
 122
 123    public async Task<(bool Success, ConfiguracionFranjasIrpfDto? Configuracion, string? Error)> GuardarFranjasAsync(
 124        ConfigurarFranjasIrpfRequest request)
 125    {
 2126        if (request.Franjas == null || request.Franjas.Count == 0)
 0127            return (false, null, "Debe proporcionar al menos una franja IRPF");
 128
 6129        var franjasOrdenadas = request.Franjas.OrderBy(f => f.NumeroFranja).ToList();
 130
 2131        var error = ValidarCoberturaFranjas(franjasOrdenadas);
 2132        if (error != null)
 1133            return (false, null, error);
 134
 135        // Eliminar todas las franjas existentes
 1136        var existentes = (await _irpfRepository.GetHistorialFranjasAsync()).ToList();
 6137        foreach (var franja in existentes)
 2138            await _irpfRepository.DeleteFranjaAsync(franja.Id);
 139
 140        // Crear las nuevas
 3141        var nuevasFranjas = franjasOrdenadas.Select(f => new FranjaIrpf
 3142        {
 3143            NumeroFranja = f.NumeroFranja,
 3144            DesdeBpc = f.DesdeBpc,
 3145            HastaBpc = f.HastaBpc,
 3146            Tasa = f.Tasa,
 3147            VigenteDesde = DateTime.Now.Date
 3148        }).ToList();
 149
 1150        await _irpfRepository.CreateFranjasAsync(nuevasFranjas);
 1151        _logger.LogInformation("Franjas IRPF reemplazadas: {Count} franjas", nuevasFranjas.Count);
 152
 1153        await _auditoriaService.LogAuditoriaAsync(
 1154            _currentUser.UserId ?? 0, AccionEnum.ActualizarFranja, ContextoEnum.IRPF,
 1155            _currentUser.Host, entidad: "FranjaIrpf", entidadId: 0,
 1156            detalle: new { cantidadFranjas = nuevasFranjas.Count });
 157
 1158        return (true, MapFranjas(nuevasFranjas), null);
 2159    }
 160
 161    // ── Validaciones ────────────────────────────────────────────────────────
 162
 163    private static string? ValidarParametro(ConfigurarParametrosLiquidacionRequest request)
 164    {
 4165        if (request.BpcAnual <= 0)
 1166            return "La BPC anual debe ser mayor a cero";
 167
 3168        var tasas = new Dictionary<string, decimal>
 3169        {
 3170            ["tasa_montepio_ley_vieja"] = request.TasaMontepioLeyVieja,
 3171            ["tasa_montepio_ley_nueva"] = request.TasaMontepioLeyNueva,
 3172            ["tasa_fonasa"] = request.TasaFonasa,
 3173            ["tasa_frl"] = request.TasaFrl,
 3174            ["tasa_deduccion_alta"] = request.TasaDeduccionAlta,
 3175            ["tasa_deduccion_baja"] = request.TasaDeduccionBaja,
 3176            ["porcentaje_minimo_deficit"] = request.PorcentajeMinimoDeficit
 3177        };
 178
 48179        foreach (var tasa in tasas)
 180        {
 21181            if (tasa.Value < 0 || tasa.Value > 1)
 0182                return $"El valor de {tasa.Key} debe estar entre 0 y 1";
 183        }
 184
 3185        if (request.UmbralDeduccionBpc < 0)
 0186            return "El umbral de deducción en BPC no puede ser negativo";
 187
 3188        return null;
 0189    }
 190
 191    private static string? ValidarCoberturaFranjas(IReadOnlyList<FranjaIrpfConfiguradaRequest> franjas)
 192    {
 10193        var duplicados = franjas.GroupBy(f => f.NumeroFranja).FirstOrDefault(g => g.Count() > 1);
 2194        if (duplicados != null)
 0195            return $"La franja {duplicados.Key} está duplicada";
 196
 2197        if (franjas[0].DesdeBpc != 0)
 0198            return "La primera franja debe comenzar en 0 BPC";
 199
 8200        for (var i = 0; i < franjas.Count; i++)
 201        {
 3202            var franja = franjas[i];
 203
 3204            if (franja.HastaBpc.HasValue && franja.DesdeBpc >= franja.HastaBpc.Value)
 0205                return $"La franja {franja.NumeroFranja} tiene un rango inválido";
 206
 3207            if (franja.Tasa < 0 || franja.Tasa > 1)
 0208                return $"La tasa de la franja {franja.NumeroFranja} debe estar entre 0 y 1";
 209
 3210            if (i < franjas.Count - 1)
 211            {
 2212                var siguiente = franjas[i + 1];
 2213                if (!franja.HastaBpc.HasValue)
 0214                    return $"La franja {franja.NumeroFranja} solo puede quedar abierta si es la última";
 2215                if (franja.HastaBpc.Value != siguiente.DesdeBpc)
 1216                    return $"Las franjas {franja.NumeroFranja} y {siguiente.NumeroFranja} no cubren el rango de forma co
 217            }
 1218            else if (franja.HastaBpc.HasValue)
 219            {
 0220                return "La última franja debe quedar abierta sin límite superior";
 221            }
 222        }
 223
 1224        return null;
 225    }
 226
 227    // ── Mappers ─────────────────────────────────────────────────────────────
 228
 4229    private static ParametroLiquidacionDto MapToDto(ParametroPeriodo p) => new()
 4230    {
 4231        Id = p.Id,
 4232        Anio = p.Anio,
 4233        Mes = p.Mes,
 4234        BpcAnual = p.BpcAnual,
 4235        TasaMontepioLeyVieja = p.TasaMontepioLeyVieja,
 4236        TasaMontepioLeyNueva = p.TasaMontepioLeyNueva,
 4237        TasaFonasa = p.TasaFonasa,
 4238        TasaFrl = p.TasaFrl,
 4239        UmbralDeduccionBpc = p.UmbralDeduccionBpc,
 4240        TasaDeduccionAlta = p.TasaDeduccionAlta,
 4241        TasaDeduccionBaja = p.TasaDeduccionBaja,
 4242        PorcentajeMinimoDeficit = p.PorcentajeMinimoDeficit,
 4243        CreadoEn = p.CreadoEn,
 4244        CreadoPor = p.CreadoPor,
 4245        UsuarioCreador = p.UsuarioCreador?.Username
 4246    };
 247
 248    private static ConfiguracionFranjasIrpfDto MapFranjas(IEnumerable<FranjaIrpf> franjas)
 249    {
 3250        var lista = franjas.OrderBy(f => f.NumeroFranja).ToList();
 1251        return new ConfiguracionFranjasIrpfDto
 1252        {
 1253            FechaVigencia = lista[0].VigenteDesde,
 1254            FechaFinVigencia = lista[0].VigenteHasta,
 2255            Franjas = lista.Select(f => new FranjaIrpfDto
 2256            {
 2257                Id = f.Id,
 2258                NumeroFranja = f.NumeroFranja,
 2259                DesdeBpc = f.DesdeBpc,
 2260                HastaBpc = f.HastaBpc,
 2261                Tasa = f.Tasa,
 2262                VigenteDesde = f.VigenteDesde,
 2263                VigenteHasta = f.VigenteHasta
 2264            }).ToList()
 1265        };
 266    }
 267}