< Summary

Information
Class: FAU.Logica.Services.FranjasIrpfService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/FranjasIrpfService.cs
Line coverage
86%
Covered lines: 127
Uncovered lines: 19
Coverable lines: 146
Total lines: 261
Line coverage: 86.9%
Branch coverage
63%
Covered branches: 23
Total branches: 36
Branch coverage: 63.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ObtenerFranjasVigentesAsync()100%3240%
ObtenerPorIdAsync()100%3250%
CrearAsync()100%6692.85%
ActualizarAsync()45.83%262484.09%
EliminarAsync()100%4486.36%
MapearADto(...)100%11100%
SerializarFranja(...)100%11100%

File(s)

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

#LineLine coverage
 1using System.Text;
 2using FAU.Entidades;
 3using FAU.Logica.DTOs;
 4using FAU.Logica.Validadores;
 5using FAU.DataAccess.Repositories;
 6using Microsoft.Extensions.Logging;
 7
 8namespace FAU.Logica.Services;
 9
 10/// <summary>
 11/// Implementación del servicio de gestión CRUD de franjas IRPF
 12/// </summary>
 13public class FranjasIrpfService : IFranjasIrpfService
 14{
 15    private readonly IIrpfRepository _irpfRepository;
 16    private readonly IAuditoriaService _auditoriaService;
 17    private readonly ILogger<FranjasIrpfService> _logger;
 18
 1619    public FranjasIrpfService(
 1620        IIrpfRepository irpfRepository,
 1621        IAuditoriaService auditoriaService,
 1622        ILogger<FranjasIrpfService> logger)
 23    {
 1624        _irpfRepository = irpfRepository;
 1625        _auditoriaService = auditoriaService;
 1626        _logger = logger;
 1627    }
 28
 29    public async Task<IEnumerable<FranjaIrpfDto>> ObtenerFranjasVigentesAsync(DateTime fecha)
 30    {
 31        try
 32        {
 233            var franjas = await _irpfRepository.GetFranjasVigentesAsync(fecha);
 434            return franjas.Select(MapearADto).OrderBy(f => f.NumeroFranja).ToList();
 35        }
 036        catch (Exception ex)
 37        {
 038            _logger.LogError(ex, "Error al obtener franjas IRPF vigentes para {Fecha}", fecha);
 039            throw;
 40        }
 241    }
 42
 43    public async Task<FranjaIrpfDto?> ObtenerPorIdAsync(long id)
 44    {
 45        try
 46        {
 247            var franja = await _irpfRepository.GetFranjaByIdAsync(id);
 248            return franja != null ? MapearADto(franja) : null;
 49        }
 050        catch (Exception ex)
 51        {
 052            _logger.LogError(ex, "Error al obtener franja IRPF por ID {FranjaId}", id);
 053            throw;
 54        }
 255    }
 56
 57    public async Task<(bool Success, FranjaIrpfDto? Resultado, string? Error)> CrearAsync(long usuarioId, CrearFranjaIrp
 58    {
 59        try
 60        {
 61            // Validar datos de entrada usando validador
 662            var (esValido, mensajeError) = ValidadorFranjaIrpf.ValidarFranja(
 663                request.NumeroFranja,
 664                request.DesdeBpc,
 665                request.HastaBpc,
 666                request.Tasa
 667            );
 68
 669            if (!esValido)
 370                return (false, null, mensajeError);
 71
 72            // Validar que no exista ya una franja con mismo número y vigencia
 373            var existe = await _irpfRepository.ExisteFranjaAsync(request.NumeroFranja, request.VigenteDesde);
 374            if (existe)
 175                return (false, null, $"Ya existe una franja número {request.NumeroFranja} vigente desde {request.Vigente
 76
 77            // Cerrar franja anterior del mismo número si existe
 278            var franjaAnterior = await _irpfRepository.GetFranjaActivaByNumeroAsync(request.NumeroFranja);
 279            if (franjaAnterior != null)
 80            {
 181                franjaAnterior.VigenteHasta = request.VigenteDesde.AddDays(-1);
 182                await _irpfRepository.UpdateFranjaAsync(franjaAnterior);
 183                _logger.LogInformation("Cerrada franja IRPF anterior: número={NumeroFranja}, vigente_hasta={VigenteHasta
 184                    franjaAnterior.NumeroFranja, franjaAnterior.VigenteHasta);
 85            }
 86
 87            // Crear nueva franja
 288            var nuevaFranja = new FranjaIrpf
 289            {
 290                NumeroFranja = request.NumeroFranja,
 291                DesdeBpc = request.DesdeBpc,
 292                HastaBpc = request.HastaBpc,
 293                Tasa = request.Tasa,
 294                VigenteDesde = request.VigenteDesde,
 295                VigenteHasta = null
 296            };
 97
 298            var franjaCreada = await _irpfRepository.CreateFranjaAsync(nuevaFranja);
 99
 100            // Auditoría
 2101            await _auditoriaService.LogAuditoriaAsync(
 2102                usuarioId,
 2103                AccionEnum.CrearFranja,
 2104                ContextoEnum.IRPF,
 2105                entidad: "franjas_irpf",
 2106                entidadId: franjaCreada.Id,
 2107                detalle: SerializarFranja(franjaCreada)
 2108            );
 109
 2110            _logger.LogInformation("Franja IRPF creada: id={FranjaId}, número={NumeroFranja}, vigente_desde={VigenteDesd
 2111                franjaCreada.Id, franjaCreada.NumeroFranja, franjaCreada.VigenteDesde);
 112
 2113            return (true, MapearADto(franjaCreada), null);
 114        }
 0115        catch (Exception ex)
 116        {
 0117            _logger.LogError(ex, "Error al crear franja IRPF");
 0118            return (false, null, "Error interno al crear franja IRPF");
 119        }
 6120    }
 121
 122    public async Task<(bool Success, FranjaIrpfDto? Resultado, string? Error)> ActualizarAsync(long id, long usuarioId, 
 123    {
 124        try
 125        {
 3126            var franja = await _irpfRepository.GetFranjaByIdAsync(id);
 3127            if (franja == null)
 1128                return (false, null, "Franja IRPF no encontrada");
 129
 130            // Validar datos de entrada usando validador
 2131            var (esValido, mensajeError) = ValidadorFranjaIrpf.ValidarFranja(
 2132                request.NumeroFranja,
 2133                request.DesdeBpc,
 2134                request.HastaBpc,
 2135                request.Tasa
 2136            );
 137
 2138            if (!esValido)
 0139                return (false, null, mensajeError);
 140
 2141            var tieneUsoIrpf = await _irpfRepository.EstaFranjaUsadaEnIrpfMensualAsync(id);
 2142            if (tieneUsoIrpf)
 143            {
 1144                var cambiaVigencia = franja.VigenteDesde.Date != request.VigenteDesde.Date
 1145                    || franja.VigenteHasta?.Date != request.VigenteHasta?.Date;
 146
 1147                if (cambiaVigencia)
 148                {
 1149                    return (false, null, "No se puede modificar la vigencia de una franja ya utilizada en cálculos IRPF"
 150                }
 151            }
 152
 153            // Validar unicidad si cambió numero o vigencia
 1154            if (franja.NumeroFranja != request.NumeroFranja || franja.VigenteDesde != request.VigenteDesde)
 155            {
 0156                var existe = await _irpfRepository.ExisteFranjaAsync(request.NumeroFranja, request.VigenteDesde);
 0157                if (existe)
 0158                    return (false, null, $"Ya existe una franja número {request.NumeroFranja} vigente desde {request.Vig
 159            }
 160
 161            // Guardar estado anterior para auditoría
 1162            var estadoAnterior = SerializarFranja(franja);
 163
 164            // Actualizar propiedades
 1165            franja.NumeroFranja = request.NumeroFranja;
 1166            franja.DesdeBpc = request.DesdeBpc;
 1167            franja.HastaBpc = request.HastaBpc;
 1168            franja.Tasa = request.Tasa;
 1169            franja.VigenteDesde = request.VigenteDesde;
 1170            franja.VigenteHasta = request.VigenteHasta;
 171
 1172            var franjaActualizada = await _irpfRepository.UpdateFranjaAsync(franja);
 173
 174            // Auditoría
 1175            await _auditoriaService.LogAuditoriaAsync(
 1176                usuarioId,
 1177                AccionEnum.ActualizarFranja,
 1178                ContextoEnum.IRPF,
 1179                entidad: "franjas_irpf",
 1180                entidadId: franjaActualizada.Id,
 1181                detalle: $"Cambios: {estadoAnterior} -> {SerializarFranja(franjaActualizada)}"
 1182            );
 183
 1184            _logger.LogInformation("Franja IRPF actualizada: id={FranjaId}, número={NumeroFranja}",
 1185                franjaActualizada.Id, franjaActualizada.NumeroFranja);
 186
 1187            return (true, MapearADto(franjaActualizada), null);
 188        }
 0189        catch (Exception ex)
 190        {
 0191            _logger.LogError(ex, "Error al actualizar franja IRPF {FranjaId}", id);
 0192            return (false, null, "Error interno al actualizar franja IRPF");
 193        }
 3194    }
 195
 196    public async Task<(bool Success, string? Error)> EliminarAsync(long id, long usuarioId)
 197    {
 198        try
 199        {
 3200            var franja = await _irpfRepository.GetFranjaByIdAsync(id);
 3201            if (franja == null)
 1202                return (false, "Franja IRPF no encontrada");
 203
 2204            var usadaEnCerradas = await _irpfRepository.EstaFranjaUsadaEnLiquidacionesCerradasAsync(id);
 2205            if (usadaEnCerradas)
 206            {
 1207                return (false, "No se puede eliminar la franja porque está usada en liquidaciones cerradas");
 208            }
 209
 210            // Auditoría
 1211            await _auditoriaService.LogAuditoriaAsync(
 1212                usuarioId,
 1213                AccionEnum.EliminarFranja,
 1214                ContextoEnum.IRPF,
 1215                entidad: "franjas_irpf",
 1216                entidadId: franja.Id,
 1217                detalle: SerializarFranja(franja)
 1218            );
 219
 1220            await _irpfRepository.DeleteFranjaAsync(id);
 221
 1222            _logger.LogInformation("Franja IRPF eliminada: id={FranjaId}, número={NumeroFranja}",
 1223                franja.Id, franja.NumeroFranja);
 224
 1225            return (true, null);
 226        }
 0227        catch (Exception ex)
 228        {
 0229            _logger.LogError(ex, "Error al eliminar franja IRPF {FranjaId}", id);
 0230            return (false, "Error interno al eliminar franja IRPF");
 231        }
 3232    }
 233
 234    // ── Helpers ───────────────────────────────────────────────────────────────
 235
 236    private static FranjaIrpfDto MapearADto(FranjaIrpf franja)
 237    {
 6238        return new FranjaIrpfDto
 6239        {
 6240            Id = franja.Id,
 6241            NumeroFranja = franja.NumeroFranja,
 6242            DesdeBpc = franja.DesdeBpc,
 6243            HastaBpc = franja.HastaBpc,
 6244            Tasa = franja.Tasa,
 6245            VigenteDesde = franja.VigenteDesde,
 6246            VigenteHasta = franja.VigenteHasta
 6247        };
 248    }
 249
 250    private static string SerializarFranja(FranjaIrpf franja)
 251    {
 5252        var sb = new StringBuilder();
 5253        sb.Append($"NumeroFranja={franja.NumeroFranja}, ");
 5254        sb.Append($"DesdeBpc={franja.DesdeBpc}, ");
 5255        sb.Append($"HastaBpc={franja.HastaBpc}, ");
 5256        sb.Append($"Tasa={franja.Tasa}, ");
 5257        sb.Append($"VigenteDesde={franja.VigenteDesde:yyyy-MM-dd}, ");
 5258        sb.Append($"VigenteHasta={franja.VigenteHasta:yyyy-MM-dd}");
 5259        return sb.ToString();
 260    }
 261}