< Summary

Information
Class: FAU.Logica.Services.DependienteService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/DependienteService.cs
Line coverage
90%
Covered lines: 126
Uncovered lines: 13
Coverable lines: 139
Total lines: 233
Line coverage: 90.6%
Branch coverage
78%
Covered branches: 78
Total branches: 100
Branch coverage: 78%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
GetDependientesPagedAsync()33.33%7673.33%
GetDependientesByPersonaAsync()75%44100%
GetDependienteByIdAsync()100%22100%
CreateDependienteAsync()100%3232100%
UpdateDependienteAsync()71.87%663268%
MapToDto(...)50%1212100%
CalcularTipoDeduccionIrpf(...)90%101087.5%
CalcularEdad(...)50%22100%

File(s)

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

#LineLine coverage
 1using FAU.DataAccess.Repositories;
 2using FAU.Entidades;
 3using FAU.Logica.DTOs;
 4using FAU.Logica.Validadores;
 5using Microsoft.Extensions.Logging;
 6
 7namespace FAU.Logica.Services;
 8
 9public class DependienteService : IDependienteService
 10{
 111    private static readonly HashSet<string> TiposValidos = new(StringComparer.Ordinal)
 112    {
 113        "hijo", "conyuge_sin_ingresos"
 114    };
 15
 116    private static readonly HashSet<short> PorcentajesValidos = new() { 50, 100 };
 17
 18    private readonly IDependienteRepository _dependienteRepository;
 19    private readonly IPersonalRepository _personalRepository;
 20    private readonly ILogger<DependienteService> _logger;
 21    private readonly IAuditoriaService _auditoriaService;
 22    private readonly ICurrentUserContext _currentUser;
 23
 2124    public DependienteService(
 2125        IDependienteRepository dependienteRepository,
 2126        IPersonalRepository personalRepository,
 2127        ILogger<DependienteService> logger,
 2128        IAuditoriaService auditoriaService,
 2129        ICurrentUserContext currentUser)
 30    {
 2131        _dependienteRepository = dependienteRepository;
 2132        _personalRepository = personalRepository;
 2133        _logger = logger;
 2134        _auditoriaService = auditoriaService;
 2135        _currentUser = currentUser;
 2136    }
 37
 38    public async Task<PagedResult<DependienteDto>> GetDependientesPagedAsync(
 39        int page, int pageSize, string? cedulaTitular = null, string? tipo = null, bool? activo = null)
 40    {
 141        long? personaId = null;
 142        if (!string.IsNullOrWhiteSpace(cedulaTitular))
 43        {
 044            var relacion = await _personalRepository.GetByCedulaAsync(cedulaTitular);
 045            if (relacion == null)
 046                return new PagedResult<DependienteDto> { Items = [], Page = page, PageSize = pageSize, TotalCount = 0 };
 047            personaId = relacion.PersonaId;
 48        }
 49
 150        var (items, totalCount) = await _dependienteRepository.GetDependientesPagedAsync(page, pageSize, personaId, tipo
 151        return new PagedResult<DependienteDto>
 152        {
 153            Items = items.Select(MapToDto),
 154            Page = page,
 155            PageSize = pageSize,
 156            TotalCount = totalCount
 157        };
 158    }
 59
 60    public async Task<IEnumerable<DependienteDto>> GetDependientesByPersonaAsync(string cedula)
 61    {
 162        var relacion = await _personalRepository.GetByCedulaAsync(cedula);
 163        if (relacion == null) return [];
 164        var dependientes = await _dependienteRepository.GetDependientesByPersonaAsync(relacion.PersonaId);
 165        return dependientes.Select(MapToDto);
 166    }
 67
 68    public async Task<DependienteDto?> GetDependienteByIdAsync(long id)
 69    {
 470        var dependiente = await _dependienteRepository.GetDependienteByIdAsync(id);
 471        return dependiente == null ? null : MapToDto(dependiente);
 472    }
 73
 74    public async Task<(bool Success, Dependiente? Dependiente, string? Error)> CreateDependienteAsync(CreateDependienteR
 75    {
 1276        if (!TiposValidos.Contains(request.Tipo))
 177            return (false, null, $"Tipo inválido. Valores aceptados: {string.Join(", ", TiposValidos)}");
 78
 79        // Validaciones específicas por tipo
 1180        if (request.Tipo == "hijo")
 81        {
 882            if (!request.FechaNacimiento.HasValue)
 183                return (false, null, "fecha_nacimiento es obligatoria para dependientes de tipo hijo");
 84
 785            if (request.FechaNacimiento.Value.Date > DateTime.Today)
 186                return (false, null, "fecha_nacimiento no puede ser una fecha futura");
 87
 688            if (request.PorcentajeDeduccion.HasValue && !PorcentajesValidos.Contains(request.PorcentajeDeduccion.Value))
 189                return (false, null, "porcentaje_deduccion debe ser 50 o 100");
 90        }
 391        else if (request.Tipo == "conyuge_sin_ingresos")
 92        {
 393            if (request.PorcentajeDeduccion.HasValue)
 194                return (false, null, "porcentaje_deduccion no aplica para tipo conyuge_sin_ingresos");
 95        }
 96
 797        if (!string.IsNullOrWhiteSpace(request.Cedula) && !CedulaValidator.Validar(request.Cedula))
 198            return (false, null, "La cédula ingresada no es válida");
 99
 6100        var vigenteDesde = DateTime.Today;
 101
 6102        if (request.VigenteHasta.HasValue && request.VigenteHasta.Value < vigenteDesde)
 1103            return (false, null, "vigente_hasta debe ser mayor o igual a la fecha actual");
 104
 105        // Resolver cédula titular → personaId
 5106        var relacionActiva = await _personalRepository.GetByCedulaAsync(request.CedulaTitular);
 5107        if (relacionActiva == null)
 1108            return (false, null, $"No se encontró personal activo con cédula {request.CedulaTitular}");
 109
 4110        var personaId = relacionActiva.PersonaId;
 111
 112        // Restricción: solo un cónyuge activo por persona
 4113        if (request.Tipo == "conyuge_sin_ingresos")
 114        {
 2115            if (await _dependienteRepository.ExisteConyugeActivoAsync(personaId))
 1116                return (false, null, "Ya existe un dependiente activo de tipo conyuge_sin_ingresos para este funcionario
 117        }
 118
 3119        var dependiente = new Dependiente
 3120        {
 3121            PersonaId = personaId,
 3122            Tipo = request.Tipo,
 3123            Nombre = request.Nombre,
 3124            Cedula = request.Cedula,
 3125            FechaNacimiento = request.FechaNacimiento,
 3126            Discapacitado = request.Discapacitado,
 3127            PorcentajeDeduccion = request.Tipo == "hijo" ? request.PorcentajeDeduccion : null,
 3128            VigenteDesde = vigenteDesde,
 3129            VigenteHasta = request.VigenteHasta,
 3130            Activo = true
 3131        };
 132
 3133        var creado = await _dependienteRepository.CreateDependienteAsync(dependiente);
 3134        _logger.LogInformation("Dependiente tipo {Tipo} creado para persona {PersonaId}", dependiente.Tipo, dependiente.
 3135        await _auditoriaService.LogAuditoriaAsync(_currentUser.UserId ?? 0, AccionEnum.CrearDependientes, ContextoEnum.D
 3136        return (true, creado, null);
 12137    }
 138
 139    public async Task<(bool Success, Dependiente? Dependiente, string? Error)> UpdateDependienteAsync(long id, UpdateDep
 140    {
 3141        var dependiente = await _dependienteRepository.GetDependienteByIdAsync(id);
 4142        if (dependiente == null) return (false, null, "Dependiente no encontrado");
 143
 2144        if (request.VigenteHasta.HasValue && request.VigenteHasta.Value < dependiente.VigenteDesde)
 0145            return (false, null, "vigente_hasta debe ser mayor o igual a vigente_desde");
 146
 2147        if (request.PorcentajeDeduccion.HasValue)
 148        {
 0149            if (dependiente.Tipo != "hijo")
 0150                return (false, null, "porcentaje_deduccion no aplica para tipo conyuge_sin_ingresos");
 0151            if (!PorcentajesValidos.Contains(request.PorcentajeDeduccion.Value))
 0152                return (false, null, "porcentaje_deduccion debe ser 50 o 100");
 0153            dependiente.PorcentajeDeduccion = request.PorcentajeDeduccion;
 154        }
 155
 2156        if (request.Discapacitado.HasValue)
 0157            dependiente.Discapacitado = request.Discapacitado.Value;
 158
 2159        if (request.VigenteHasta.HasValue)
 0160            dependiente.VigenteHasta = request.VigenteHasta;
 161
 2162        if (request.Activo.HasValue)
 163        {
 164            // Si se reactiva un cónyuge, verificar que no haya otro activo
 2165            if (request.Activo.Value && dependiente.Tipo == "conyuge_sin_ingresos" && !dependiente.Activo)
 166            {
 1167                if (await _dependienteRepository.ExisteConyugeActivoAsync(dependiente.PersonaId))
 1168                    return (false, null, "Ya existe un dependiente activo de tipo conyuge_sin_ingresos para este funcion
 169            }
 170
 1171            dependiente.Activo = request.Activo.Value;
 172            // Al desactivar sin fecha de cierre, setear vigente_hasta = hoy
 1173            if (!request.Activo.Value && !dependiente.VigenteHasta.HasValue && !request.VigenteHasta.HasValue)
 1174                dependiente.VigenteHasta = DateTime.Now.Date;
 175        }
 176
 1177        var actualizado = await _dependienteRepository.UpdateDependienteAsync(dependiente);
 1178        await _auditoriaService.LogAuditoriaAsync(_currentUser.UserId ?? 0, AccionEnum.ActualizarDependientes, ContextoE
 1179        return (true, actualizado, null);
 3180    }
 181
 182    // ── Helper ──────────────────────────────────────────────────────────────
 183
 184    private static DependienteDto MapToDto(Dependiente d)
 185    {
 5186        var tipoDeduccion = CalcularTipoDeduccionIrpf(d);
 5187        var aplicaDeduccion = d.Activo && tipoDeduccion != null;
 188
 5189        return new DependienteDto
 5190        {
 5191            Id = d.Id,
 5192            PersonaId = d.PersonaId,
 5193            CedulaTitular = d.Persona?.Cedula ?? string.Empty,
 5194            NombreTitular = d.Persona?.NombreCompleto ?? string.Empty,
 5195            Tipo = d.Tipo,
 5196            Nombre = d.Nombre,
 5197            Cedula = d.Cedula,
 5198            FechaNacimiento = d.FechaNacimiento,
 5199            Discapacitado = d.Discapacitado,
 5200            PorcentajeDeduccion = d.PorcentajeDeduccion,
 5201            VigenteDesde = d.VigenteDesde,
 5202            VigenteHasta = d.VigenteHasta,
 5203            Activo = d.Activo,
 5204            AplicaDeduccion = aplicaDeduccion,
 5205            TipoDeduccionIrpf = d.Activo ? tipoDeduccion : null
 5206        };
 207    }
 208
 209    private static string? CalcularTipoDeduccionIrpf(Dependiente d)
 210    {
 5211        if (d.Tipo == "conyuge_sin_ingresos")
 0212            return "conyuge_sin_ingresos";
 213
 5214        if (d.Tipo == "hijo")
 215        {
 5216            if (d.Discapacitado)
 1217                return "hijo_discapacidad";
 218
 4219            if (d.FechaNacimiento.HasValue && CalcularEdad(d.FechaNacimiento.Value) < 18)
 1220                return "hijo_menor";
 221        }
 222
 3223        return null;
 224    }
 225
 226    private static int CalcularEdad(DateTime fechaNacimiento)
 227    {
 2228        var hoy = DateTime.Today;
 2229        var edad = hoy.Year - fechaNacimiento.Year;
 2230        if (fechaNacimiento.Date > hoy.AddYears(-edad)) edad--;
 2231        return edad;
 232    }
 233}