< 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
58%
Covered lines: 48
Uncovered lines: 34
Coverable lines: 82
Total lines: 178
Line coverage: 58.5%
Branch coverage
50%
Covered branches: 2
Total branches: 4
Branch coverage: 50%
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%2293.93%
ObtenerPorIdAsync()100%210%
ActualizarAsync()0%620%
ObtenerPaginadoAsync()100%210%
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
 317    public IrpfService(
 318        IrpfCalculator calculator,
 319        IIrpfRepository repository,
 320        IAuditoriaService auditoriaService,
 321        ICurrentUserContext currentUser)
 22    {
 323        _calculator = calculator;
 324        _repository = repository;
 325        _auditoriaService = auditoriaService;
 326        _currentUser = currentUser;
 327    }
 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
 68            // 6. Auditar (crear entidad BitacoraAuditoria)
 69            try
 70            {
 271                await _auditoriaService.LogAuditoriaAsync(
 272                    usuarioId: _currentUser.UserId ?? 0,
 273                    accion: AccionEnum.CrearIrpf,
 274                    contexto: ContextoEnum.IRPF,
 275                    entidad: "irpf_mensual",
 276                    entidadId: irpfCreado.Id,
 277                    detalle: new { liquidacionId, personaId, totalIrpf = irpfCreado.TotalIrpf });
 178            }
 179            catch
 80            {
 81                // El IRPF ya fue persistido; la falla de auditoria no debe omitir el descuento del recibo.
 182            }
 83
 284            return (true, irpfCreado.Id, null);
 85        }
 086        catch (Exception ex)
 87        {
 088            return (false, null, $"Error al calcular IRPF: {ex.Message}");
 89        }
 390    }
 91
 92    /// <summary>
 93    /// Obtiene un IRPF por ID con sus detalles.
 94    /// </summary>
 95    public async Task<IrpfMensual?> ObtenerPorIdAsync(long id)
 96    {
 097        return await _repository.ObtenerIrpfMensualPorIdAsync(id);
 098    }
 99
 100    /// <summary>
 101    /// Actualiza un IRPF (retroactividad, etc).
 102    /// Recalcula y reemplaza valores y detalles por franja.
 103    /// </summary>
 104    public async Task<(bool Exito, long? IdIrpfMensual, string? Error)> ActualizarAsync(
 105        long id,
 106        CalcularIrpfRequest request)
 107    {
 108        try
 109        {
 110            // 1. Obtener IRPF existente
 0111            var irpfExistente = await _repository.ObtenerIrpfMensualPorIdAsync(id);
 0112            if (irpfExistente == null)
 113            {
 0114                return (false, null, "IRPF no encontrado");
 115            }
 116
 117            // 2. Guardar datos antiguos para auditoría
 0118            var datosAntiguos = $"TotalIrpf={irpfExistente.TotalIrpf}, BaseImponibleNeta={irpfExistente.BaseImponibleNet
 119
 120            // 3. Recalcular
 0121            var resultadoCalculo = _calculator.Calcular(request);
 122
 123            // 4. Actualizar entidad IrpfMensual
 0124            irpfExistente.IngresosGravados = resultadoCalculo.IngresoGravado;
 0125            irpfExistente.DeduccionAportes = resultadoCalculo.Deducciones.Aportes;
 0126            irpfExistente.DeduccionHijos = resultadoCalculo.Deducciones.Hijos;
 0127            irpfExistente.DeduccionConyuge = resultadoCalculo.Deducciones.Conyuge;
 0128            irpfExistente.DeduccionProfesional = resultadoCalculo.Deducciones.Profesional;
 0129            irpfExistente.TasaDeduccionProfesional = resultadoCalculo.TasaCredito;
 0130            irpfExistente.TotalDeducciones = resultadoCalculo.TotalDeducciones;
 0131            irpfExistente.BaseImponibleNeta = resultadoCalculo.IrpfBruto;
 0132            irpfExistente.TotalIrpf = resultadoCalculo.TotalIrpf;
 133
 134            // 5. Mapear nuevos detalles
 0135            var nuevoDetalle = MapearDetallesFranjas(resultadoCalculo.DetallePorFranja);
 136
 137            // 6. Persistir
 0138            var irpfActualizado = await _repository.ActualizarIrpfMensualAsync(irpfExistente, nuevoDetalle);
 139
 140            // 7. Auditar
 0141            var datosNuevos = $"TotalIrpf={irpfActualizado.TotalIrpf}, BaseImponibleNeta={irpfActualizado.BaseImponibleN
 0142            await _auditoriaService.LogAuditoriaAsync(
 0143                usuarioId: _currentUser.UserId ?? 0,
 0144                accion: AccionEnum.ActualizarIrpf,
 0145                contexto: ContextoEnum.IRPF,
 0146                entidad: "irpf_mensual",
 0147                entidadId: irpfActualizado.Id,
 0148                detalle: new { Anterior = datosAntiguos, Nuevo = datosNuevos });
 149
 0150            return (true, irpfActualizado.Id, null);
 151        }
 0152        catch (Exception ex)
 153        {
 0154            return (false, null, $"Error al actualizar IRPF: {ex.Message}");
 155        }
 0156    }
 157
 158    /// <summary>
 159    /// Obtiene IRPF paginado para una liquidación.
 160    /// </summary>
 161    public async Task<(IEnumerable<IrpfMensual> Items, int TotalCount)> ObtenerPaginadoAsync(
 162        long liquidacionId,
 163        int pagina,
 164        int tamano)
 165    {
 0166        return await _repository.ObtenerIrpfsMensualPorLiquidacionAsync(liquidacionId, pagina, tamano);
 0167    }
 168
 169    private static List<IrpfDetalleFranjas> MapearDetallesFranjas(IEnumerable<DetalleFranjaResultado> detalles) =>
 6170        detalles.Select(d => new IrpfDetalleFranjas
 6171        {
 6172            FranjaIrpfId = d.FranjaId,
 6173            BaseEnFranja = d.BaseFranja,
 6174            Tasa = d.Tasa,
 6175            Impuesto = d.Impuesto
 6176        }).ToList();
 177}
 178