< Summary

Information
Class: FAU.Logica.Services.IrpfService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/IrpfService.cs
Line coverage
97%
Covered lines: 70
Uncovered lines: 2
Coverable lines: 72
Total lines: 162
Line coverage: 97.2%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
CalcularYPersistirAsync()100%2291.3%
ObtenerPorIdAsync()100%11100%
ActualizarAsync()100%22100%
ObtenerPaginadoAsync()100%11100%
MapearDetallesFranjas(...)100%11100%

File(s)

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

#LineLine coverage
 1namespace FAU.Logica.Services;
 2
 3using FAU.DataAccess.Repositories;
 4using FAU.Entidades;
 5using FAU.Logica.DTOs;
 6
 7/// <summary>
 8/// Servicio IRPF - orquesta cálculo + persistencia + auditoría.
 9/// </summary>
 10public class IrpfService : IIrpfService
 11{
 12    private readonly IrpfCalculator _calculator;
 13    private readonly IIrpfRepository _repository;
 14    private readonly IAuditoriaService _auditoriaService;
 15    private readonly ICurrentUserContext _currentUser;
 16
 917    public IrpfService(
 918        IrpfCalculator calculator,
 919        IIrpfRepository repository,
 920        IAuditoriaService auditoriaService,
 921        ICurrentUserContext currentUser)
 22    {
 923        _calculator = calculator;
 924        _repository = repository;
 925        _auditoriaService = auditoriaService;
 926        _currentUser = currentUser;
 927    }
 28
 29    /// <summary>
 30    /// Calcula IRPF y lo persiste en BD con auditoría.
 31    /// </summary>
 32    public async Task<(bool Exito, long? IdIrpfMensual, string? Error)> CalcularYPersistirAsync(
 33        long liquidacionId,
 34        long personaId,
 35        CalcularIrpfRequest request)
 36    {
 37        try
 38        {
 39            // 1. Verificar si ya existe
 340            if (await _repository.ExisteIrpfMensualAsync(liquidacionId, personaId))
 141                return (false, null, "Ya existe un IRPF calculado para esta liquidación y persona");
 42
 43            // 2. Calcular
 244            var resultadoCalculo = _calculator.Calcular(request);
 45
 46            // 3. Mapear resultado a entidad IrpfMensual
 247            var irpfMensual = new IrpfMensual
 248            {
 249                LiquidacionId = liquidacionId,
 250                PersonaId = personaId,
 251                IngresosGravados = resultadoCalculo.IngresoGravado,
 252                DeduccionAportes = resultadoCalculo.Deducciones.Aportes,
 253                DeduccionHijos = resultadoCalculo.Deducciones.Hijos,
 254                DeduccionConyuge = resultadoCalculo.Deducciones.Conyuge,
 255                DeduccionProfesional = resultadoCalculo.Deducciones.Profesional,
 256                TasaDeduccionProfesional = resultadoCalculo.TasaCredito,
 257                TotalDeducciones = resultadoCalculo.TotalDeducciones,
 258                BaseImponibleNeta = resultadoCalculo.IrpfBruto,
 259                TotalIrpf = resultadoCalculo.TotalIrpf
 260            };
 61
 62            // 4. Mapear detalles por franja
 263            var detalleFranjas = MapearDetallesFranjas(resultadoCalculo.DetallePorFranja);
 64
 65            // 5. Persistir
 266            var irpfCreado = await _repository.CrearIrpfMensualAsync(irpfMensual, detalleFranjas);
 67
 268            return (true, irpfCreado.Id, null);
 69        }
 070        catch (Exception ex)
 71        {
 072            return (false, null, $"Error al calcular IRPF: {ex.Message}");
 73        }
 374    }
 75
 76    /// <summary>
 77    /// Obtiene un IRPF por ID con sus detalles.
 78    /// </summary>
 79    public async Task<IrpfMensual?> ObtenerPorIdAsync(long id)
 80    {
 281        return await _repository.ObtenerIrpfMensualPorIdAsync(id);
 282    }
 83
 84    /// <summary>
 85    /// Actualiza un IRPF (retroactividad, etc).
 86    /// Recalcula y reemplaza valores y detalles por franja.
 87    /// </summary>
 88    public async Task<(bool Exito, long? IdIrpfMensual, string? Error)> ActualizarAsync(
 89        long id,
 90        CalcularIrpfRequest request)
 91    {
 92        try
 93        {
 94            // 1. Obtener IRPF existente
 395            var irpfExistente = await _repository.ObtenerIrpfMensualPorIdAsync(id);
 396            if (irpfExistente == null)
 97            {
 198                return (false, null, "IRPF no encontrado");
 99            }
 100
 101            // 2. Guardar datos antiguos para auditoría
 2102            var datosAntiguos = $"TotalIrpf={irpfExistente.TotalIrpf}, BaseImponibleNeta={irpfExistente.BaseImponibleNet
 103
 104            // 3. Recalcular
 2105            var resultadoCalculo = _calculator.Calcular(request);
 106
 107            // 4. Actualizar entidad IrpfMensual
 2108            irpfExistente.IngresosGravados = resultadoCalculo.IngresoGravado;
 2109            irpfExistente.DeduccionAportes = resultadoCalculo.Deducciones.Aportes;
 2110            irpfExistente.DeduccionHijos = resultadoCalculo.Deducciones.Hijos;
 2111            irpfExistente.DeduccionConyuge = resultadoCalculo.Deducciones.Conyuge;
 2112            irpfExistente.DeduccionProfesional = resultadoCalculo.Deducciones.Profesional;
 2113            irpfExistente.TasaDeduccionProfesional = resultadoCalculo.TasaCredito;
 2114            irpfExistente.TotalDeducciones = resultadoCalculo.TotalDeducciones;
 2115            irpfExistente.BaseImponibleNeta = resultadoCalculo.IrpfBruto;
 2116            irpfExistente.TotalIrpf = resultadoCalculo.TotalIrpf;
 117
 118            // 5. Mapear nuevos detalles
 2119            var nuevoDetalle = MapearDetallesFranjas(resultadoCalculo.DetallePorFranja);
 120
 121            // 6. Persistir
 2122            var irpfActualizado = await _repository.ActualizarIrpfMensualAsync(irpfExistente, nuevoDetalle);
 123
 124            // 7. Auditar
 1125            var datosNuevos = $"TotalIrpf={irpfActualizado.TotalIrpf}, BaseImponibleNeta={irpfActualizado.BaseImponibleN
 1126            await _auditoriaService.LogAuditoriaAsync(
 1127                usuarioId: _currentUser.UserId ?? 0,
 1128                accion: AccionEnum.ActualizarIrpf,
 1129                contexto: ContextoEnum.IRPF,
 1130                entidad: "irpf_mensual",
 1131                entidadId: irpfActualizado.Id,
 1132                detalle: new { Anterior = datosAntiguos, Nuevo = datosNuevos });
 133
 1134            return (true, irpfActualizado.Id, null);
 135        }
 1136        catch (Exception ex)
 137        {
 1138            return (false, null, $"Error al actualizar IRPF: {ex.Message}");
 139        }
 3140    }
 141
 142    /// <summary>
 143    /// Obtiene IRPF paginado para una liquidación.
 144    /// </summary>
 145    public async Task<(IEnumerable<IrpfMensual> Items, int TotalCount)> ObtenerPaginadoAsync(
 146        long liquidacionId,
 147        int pagina,
 148        int tamano)
 149    {
 1150        return await _repository.ObtenerIrpfsMensualPorLiquidacionAsync(liquidacionId, pagina, tamano);
 1151    }
 152
 153    private static List<IrpfDetalleFranjas> MapearDetallesFranjas(IEnumerable<DetalleFranjaResultado> detalles) =>
 12154        detalles.Select(d => new IrpfDetalleFranjas
 12155        {
 12156            FranjaIrpfId = d.FranjaId,
 12157            BaseEnFranja = d.BaseFranja,
 12158            Tasa = d.Tasa,
 12159            Impuesto = d.Impuesto
 12160        }).ToList();
 161}
 162