< Summary

Information
Class: FAU.Logica.Services.BeneficioSocialService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/BeneficioSocialService.cs
Line coverage
81%
Covered lines: 186
Uncovered lines: 43
Coverable lines: 229
Total lines: 447
Line coverage: 81.2%
Branch coverage
61%
Covered branches: 135
Total branches: 220
Branch coverage: 61.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ObtenerPorIdAsync()100%22100%
ObtenerPaginadoAsync()83.33%66100%
CrearAsync()60.71%302885.36%
ActualizarAsync()54.76%454287.93%
EliminarAsync()83.33%6676.92%
ValidarReglasPorTipo(...)51.11%3794545.16%
ConvertirCategoriaRequest(...)50%7666.66%
ConstruirObservaciones(...)60%181057.14%
RequiereJustificacionFueraPlazo(...)100%11100%
CalcularRetroactividadPendiente(...)100%11100%
EsBeneficioUnicaVez(...)100%44100%
ConstruirClaveEvento(...)61.53%131387.5%
DeterminarCategoria(...)60.41%634881.25%
MapearDto(...)100%1010100%

File(s)

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

#LineLine coverage
 1using FAU.DataAccess.Repositories;
 2using FAU.Entidades;
 3using FAU.Logica.DTOs;
 4
 5namespace FAU.Logica.Services;
 6
 7public class BeneficioSocialService : IBeneficioSocialService
 8{
 9    private const string EstadoActivo = "activo";
 10    private const string EstadoInactivo = "inactivo";
 11    private const string EstadoPendienteDoc = "pendiente_doc";
 12
 13    private readonly IBeneficioSocialRepository _beneficioSocialRepository;
 14    private readonly IAuditoriaService _auditoriaService;
 15
 1016    public BeneficioSocialService(
 1017        IBeneficioSocialRepository beneficioSocialRepository,
 1018        IAuditoriaService auditoriaService)
 19    {
 1020        _beneficioSocialRepository = beneficioSocialRepository;
 1021        _auditoriaService = auditoriaService;
 1022    }
 23
 24    public async Task<BeneficioSocialDto?> ObtenerPorIdAsync(long id)
 25    {
 226        var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 227        return entidad == null ? null : MapearDto(entidad);
 228    }
 29
 30    public async Task<PagedResult<BeneficioSocialDto>> ObtenerPaginadoAsync(int pagina, int tamano, long? personaId = nu
 31    {
 232        if (pagina < 1) pagina = 1;
 133        if (tamano < 1) tamano = 10;
 234        if (tamano > 100) tamano = 100;
 35
 136        var (items, totalCount) = await _beneficioSocialRepository.ObtenerPaginadoAsync(pagina, tamano, personaId, estad
 37
 138        return new PagedResult<BeneficioSocialDto>
 139        {
 140            Items = items.Select(item => MapearDto(item)).ToList(),
 141            Page = pagina,
 142            PageSize = tamano,
 143            TotalCount = totalCount
 144        };
 145    }
 46
 47    public async Task<(bool Exito, BeneficioSocialDto? Resultado, string? Error)> CrearAsync(CrearBeneficioSocialRequest
 48    {
 49        try
 50        {
 351            var tipo = await _beneficioSocialRepository.ObtenerTipoBeneficioAsync(request.TipoBeneficioId);
 352            if (tipo == null || !tipo.Activo)
 53            {
 054                return (false, null, "Tipo de beneficio no encontrado o inactivo");
 55            }
 56
 357            var categoriaEsperada = ConvertirCategoriaRequest(DeterminarCategoria(tipo));
 358            if (request.CategoriaBeneficio != categoriaEsperada)
 59            {
 060                return (false, null, "La categoria declarada no coincide con el tipo de beneficio seleccionado");
 61            }
 62
 363            var personaActiva = await _beneficioSocialRepository.ExistePersonaActivaAsync(request.PersonaId, request.Vig
 364            if (!personaActiva)
 65            {
 166                return (false, null, "La persona no tiene relacion laboral activa para la vigencia indicada");
 67            }
 68
 269            var validacion = ValidarReglasPorTipo(tipo, request, esActualizacion: false);
 270            if (!string.IsNullOrEmpty(validacion))
 71            {
 072                return (false, null, validacion);
 73            }
 74
 275            var claveEvento = ConstruirClaveEvento(tipo, request.DatosTipo.FechaEvento, request.VigenteDesde);
 276            if (EsBeneficioUnicaVez(tipo) && await _beneficioSocialRepository.ExisteDuplicadoEventoAsync(request.Persona
 77            {
 178                return (false, null, "Existe un beneficio duplicado para un evento de unica vez");
 79            }
 80
 181            var retroactividadPendiente = CalcularRetroactividadPendiente(request.VigenteDesde);
 182            if (retroactividadPendiente && !tipo.PermiteRetroactividad)
 83            {
 084                return (false, null, "El tipo de beneficio no permite retroactividad para la vigencia indicada");
 85            }
 86
 187            var estado = request.DatosTipo.PendienteDocumentacion ? EstadoPendienteDoc : EstadoActivo;
 188            var entidad = new BeneficioSocial
 189            {
 190                PersonaId = request.PersonaId,
 191                TipoBeneficioId = request.TipoBeneficioId,
 192                Cantidad = (short)Math.Max(request.Cantidad ?? 1, (short)0),
 193                VigenteDesde = request.VigenteDesde.Date,
 194                VigenteHasta = request.VigenteHasta?.Date,
 195                Estado = estado,
 196                DocumentoRespaldo = request.DocumentoRespaldo?.Trim(),
 197                Observaciones = ConstruirObservaciones(request.Observaciones, request.DatosTipo.JustificacionRetroactivo
 198                CreadoPor = usuarioId,
 199                FechaEvento = request.DatosTipo.FechaEvento?.Date,
 1100                ClaveEvento = claveEvento,
 1101                RetroactividadPendiente = retroactividadPendiente
 1102            };
 103
 1104            var creado = await _beneficioSocialRepository.CrearAsync(entidad);
 1105            _auditoriaService.LogAuditoria(usuarioId, (int)AccionEnum.CrearBeneficioSocial, (int)ContextoEnum.Sistema, h
 106
 1107            var recargado = await _beneficioSocialRepository.ObtenerPorIdAsync(creado.Id);
 1108            return (true, recargado == null ? MapearDto(creado, tipo) : MapearDto(recargado), null);
 109        }
 0110        catch (Exception ex)
 111        {
 0112            return (false, null, $"Error al crear beneficio social: {ex.Message}");
 113        }
 3114    }
 115
 116    public async Task<(bool Exito, BeneficioSocialDto? Resultado, string? Error)> ActualizarAsync(long id, ActualizarBen
 117    {
 118        try
 119        {
 2120            var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 2121            if (entidad == null)
 122            {
 1123                return (false, null, "Beneficio social no encontrado");
 124            }
 125
 1126            var tipo = entidad.TipoBeneficio ?? await _beneficioSocialRepository.ObtenerTipoBeneficioAsync(entidad.TipoB
 1127            if (tipo == null || !tipo.Activo)
 128            {
 0129                return (false, null, "Tipo de beneficio no encontrado o inactivo");
 130            }
 131
 1132            var vigenteDesde = request.VigenteDesde?.Date ?? entidad.VigenteDesde.Date;
 1133            var fechaEvento = request.FechaEvento?.Date ?? entidad.FechaEvento;
 134
 1135            if (!await _beneficioSocialRepository.ExistePersonaActivaAsync(entidad.PersonaId, vigenteDesde))
 136            {
 0137                return (false, null, "La persona no tiene relacion laboral activa para la vigencia indicada");
 138            }
 139
 1140            var requestConsolidado = new CrearBeneficioSocialRequest
 1141            {
 1142                PersonaId = entidad.PersonaId,
 1143                TipoBeneficioId = entidad.TipoBeneficioId,
 1144                Cantidad = request.Cantidad ?? entidad.Cantidad,
 1145                VigenteDesde = vigenteDesde,
 1146                VigenteHasta = request.VigenteHasta ?? entidad.VigenteHasta,
 1147                DocumentoRespaldo = request.DocumentoRespaldo ?? entidad.DocumentoRespaldo,
 1148                Observaciones = request.Observaciones ?? entidad.Observaciones,
 1149                CategoriaBeneficio = ConvertirCategoriaRequest(DeterminarCategoria(tipo)),
 1150                DatosTipo = new DatosTipoBeneficioRequest
 1151                {
 1152                    FechaEvento = fechaEvento,
 1153                    EstadoCivilUnion = request.EstadoCivilUnion,
 1154                    TieneHijosACargo = request.TieneHijosACargo,
 1155                    CantidadHijosMenores = request.CantidadHijosMenores,
 1156                    CantidadHijosDiscapacidad = request.CantidadHijosDiscapacidad,
 1157                    PrimaSolidariaFamiliarTipo = request.PrimaSolidariaFamiliarTipo,
 1158                    TieneJustificacionRetroactivo = request.TieneJustificacionRetroactivo ?? false,
 1159                    JustificacionRetroactivo = request.JustificacionRetroactivo,
 1160                    PendienteDocumentacion = request.PendienteDocumentacion ?? (entidad.Estado == EstadoPendienteDoc)
 1161                }
 1162            };
 163
 1164            var validacion = ValidarReglasPorTipo(tipo, requestConsolidado, esActualizacion: true);
 1165            if (!string.IsNullOrEmpty(validacion))
 166            {
 0167                return (false, null, validacion);
 168            }
 169
 1170            var claveEvento = ConstruirClaveEvento(tipo, fechaEvento, vigenteDesde);
 1171            if (EsBeneficioUnicaVez(tipo)
 1172                && await _beneficioSocialRepository.ExisteDuplicadoEventoAsync(entidad.PersonaId, entidad.TipoBeneficioI
 173            {
 0174                return (false, null, "Existe un beneficio duplicado para un evento de unica vez");
 175            }
 176
 1177            var retroactividadPendiente = CalcularRetroactividadPendiente(vigenteDesde);
 1178            if (retroactividadPendiente && !tipo.PermiteRetroactividad)
 179            {
 0180                return (false, null, "El tipo de beneficio no permite retroactividad para la vigencia indicada");
 181            }
 182
 1183            entidad.Cantidad = requestConsolidado.Cantidad ?? entidad.Cantidad;
 1184            entidad.VigenteDesde = vigenteDesde;
 1185            entidad.VigenteHasta = requestConsolidado.VigenteHasta?.Date;
 1186            entidad.DocumentoRespaldo = requestConsolidado.DocumentoRespaldo?.Trim();
 1187            entidad.Observaciones = ConstruirObservaciones(requestConsolidado.Observaciones, requestConsolidado.DatosTip
 1188            entidad.Estado = requestConsolidado.DatosTipo.PendienteDocumentacion ? EstadoPendienteDoc : EstadoActivo;
 1189            entidad.FechaEvento = fechaEvento;
 1190            entidad.ClaveEvento = claveEvento;
 1191            entidad.RetroactividadPendiente = retroactividadPendiente;
 192
 1193            var actualizado = await _beneficioSocialRepository.ActualizarAsync(entidad);
 1194            _auditoriaService.LogAuditoria(usuarioId, (int)AccionEnum.ActualizarBeneficioSocial, (int)ContextoEnum.Siste
 195
 1196            return (true, MapearDto(actualizado, tipo), null);
 197        }
 0198        catch (Exception ex)
 199        {
 0200            return (false, null, $"Error al actualizar beneficio social: {ex.Message}");
 201        }
 2202    }
 203
 204    public async Task<(bool Exito, bool BajaLogica, string? Error)> EliminarAsync(long id, long usuarioId, string host)
 205    {
 206        try
 207        {
 2208            var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 2209            if (entidad == null)
 210            {
 0211                return (false, false, "Beneficio social no encontrado");
 212            }
 213
 2214            if (entidad.Estado == EstadoInactivo)
 215            {
 1216                return (true, true, null);
 217            }
 218
 1219            entidad.Estado = EstadoInactivo;
 1220            entidad.VigenteHasta ??= DateTime.UtcNow.Date;
 1221            await _beneficioSocialRepository.ActualizarAsync(entidad);
 1222            _auditoriaService.LogAuditoria(usuarioId, (int)AccionEnum.EliminarBeneficioSocial, (int)ContextoEnum.Sistema
 223
 1224            return (true, true, null);
 225        }
 0226        catch (Exception ex)
 227        {
 0228            return (false, false, $"Error al eliminar beneficio social: {ex.Message}");
 229        }
 2230    }
 231
 232    private static string? ValidarReglasPorTipo(TipoBeneficio tipo, CrearBeneficioSocialRequest request, bool esActualiz
 233    {
 3234        var datosTipo = request.DatosTipo ?? new DatosTipoBeneficioRequest();
 235
 3236        if (request.Cantidad.HasValue && request.Cantidad.Value < 0)
 237        {
 0238            return "La cantidad no puede ser negativa";
 239        }
 240
 3241        if (request.VigenteHasta.HasValue && request.VigenteHasta.Value.Date < request.VigenteDesde.Date)
 242        {
 0243            return "La vigencia hasta no puede ser anterior a la vigencia desde";
 244        }
 245
 3246        var categoria = DeterminarCategoria(tipo);
 247        switch (categoria)
 248        {
 249            case CategoriaTipoBeneficio.AsignacionFamiliar:
 0250                var hijosMenores = datosTipo.CantidadHijosMenores ?? 0;
 0251                var hijosDiscapacidad = datosTipo.CantidadHijosDiscapacidad ?? 0;
 0252                if (hijosMenores + hijosDiscapacidad <= 0)
 253                {
 0254                    return "Debe indicar al menos un hijo menor o con discapacidad para asignacion familiar";
 255                }
 256
 0257                if (string.IsNullOrWhiteSpace(request.DocumentoRespaldo))
 258                {
 0259                    return "La documentacion probatoria es requerida para asignacion familiar";
 260                }
 261                break;
 262
 263            case CategoriaTipoBeneficio.HogarConstituido:
 0264                if (string.IsNullOrWhiteSpace(datosTipo.EstadoCivilUnion))
 265                {
 0266                    return "Debe indicar estado civil o union concubinaria para hogar constituido";
 267                }
 268                break;
 269
 270            case CategoriaTipoBeneficio.PrimaSolidariaFamiliar:
 1271                if (string.IsNullOrWhiteSpace(datosTipo.PrimaSolidariaFamiliarTipo))
 272                {
 0273                    return "Debe indicar el tipo de prima solidaria familiar";
 274                }
 275                break;
 276
 277            case CategoriaTipoBeneficio.PrimaNacimiento:
 1278                if (!datosTipo.FechaEvento.HasValue)
 279                {
 0280                    return "Debe indicar la fecha de nacimiento para prima por nacimiento";
 281                }
 282
 1283                if (RequiereJustificacionFueraPlazo(datosTipo.FechaEvento.Value, request.VigenteDesde)
 1284                    && !datosTipo.TieneJustificacionRetroactivo
 1285                    && string.IsNullOrWhiteSpace(datosTipo.JustificacionRetroactivo))
 286                {
 0287                    return "La prima por nacimiento fuera de plazo requiere justificacion de retroactivo";
 288                }
 289                break;
 290
 291            case CategoriaTipoBeneficio.PrimaMatrimonio:
 1292                if (!datosTipo.FechaEvento.HasValue)
 293                {
 0294                    return "Debe indicar la fecha de matrimonio o union";
 295                }
 296
 1297                if (RequiereJustificacionFueraPlazo(datosTipo.FechaEvento.Value, request.VigenteDesde)
 1298                    && !datosTipo.TieneJustificacionRetroactivo
 1299                    && string.IsNullOrWhiteSpace(datosTipo.JustificacionRetroactivo))
 300                {
 0301                    return "La prima por matrimonio fuera de plazo requiere justificacion";
 302                }
 303                break;
 304
 305            case CategoriaTipoBeneficio.General:
 0306                if (!esActualizacion && request.Cantidad.GetValueOrDefault(1) <= 0)
 307                {
 0308                    return "La cantidad debe ser mayor a cero";
 309                }
 310                break;
 311        }
 312
 3313        return null;
 314    }
 315
 316    private static CategoriaBeneficioRequest ConvertirCategoriaRequest(CategoriaTipoBeneficio categoria)
 317    {
 4318        return categoria switch
 4319        {
 0320            CategoriaTipoBeneficio.AsignacionFamiliar => CategoriaBeneficioRequest.AsignacionFamiliar,
 0321            CategoriaTipoBeneficio.HogarConstituido => CategoriaBeneficioRequest.HogarConstituido,
 1322            CategoriaTipoBeneficio.PrimaSolidariaFamiliar => CategoriaBeneficioRequest.PrimaSolidariaFamiliar,
 2323            CategoriaTipoBeneficio.PrimaNacimiento => CategoriaBeneficioRequest.PrimaNacimiento,
 1324            CategoriaTipoBeneficio.PrimaMatrimonio => CategoriaBeneficioRequest.PrimaMatrimonio,
 0325            _ => CategoriaBeneficioRequest.General
 4326        };
 327    }
 328
 329    private static string ConstruirObservaciones(string? observaciones, string? justificacionRetroactivo)
 330    {
 2331        var observacionNormalizada = observaciones?.Trim();
 2332        var justificacionNormalizada = justificacionRetroactivo?.Trim();
 333
 2334        if (string.IsNullOrWhiteSpace(justificacionNormalizada))
 335        {
 2336            return observacionNormalizada ?? string.Empty;
 337        }
 338
 0339        if (string.IsNullOrWhiteSpace(observacionNormalizada))
 340        {
 0341            return $"Justificacion retroactivo: {justificacionNormalizada}";
 342        }
 343
 0344        return $"{observacionNormalizada} | Justificacion retroactivo: {justificacionNormalizada}";
 345    }
 346
 347    private static bool RequiereJustificacionFueraPlazo(DateTime fechaEvento, DateTime vigenteDesde)
 348    {
 2349        return (vigenteDesde.Date - fechaEvento.Date).TotalDays > 90;
 350    }
 351
 352    private static bool CalcularRetroactividadPendiente(DateTime vigenteDesde)
 353    {
 2354        var primerDiaMes = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1);
 2355        return vigenteDesde.Date < primerDiaMes;
 356    }
 357
 358    private static bool EsBeneficioUnicaVez(TipoBeneficio tipo)
 359    {
 3360        var categoria = DeterminarCategoria(tipo);
 3361        return categoria == CategoriaTipoBeneficio.HogarConstituido
 3362               || categoria == CategoriaTipoBeneficio.PrimaNacimiento
 3363               || categoria == CategoriaTipoBeneficio.PrimaMatrimonio;
 364    }
 365
 366    private static string ConstruirClaveEvento(TipoBeneficio tipo, DateTime? fechaEvento, DateTime vigenteDesde)
 367    {
 3368        var categoria = DeterminarCategoria(tipo);
 369
 3370        return categoria switch
 3371        {
 1372            CategoriaTipoBeneficio.PrimaNacimiento => fechaEvento?.Date.ToString("yyyy-MM-dd") ?? vigenteDesde.Date.ToSt
 1373            CategoriaTipoBeneficio.PrimaMatrimonio => fechaEvento?.Date.ToString("yyyy-MM-dd") ?? vigenteDesde.Date.ToSt
 0374            CategoriaTipoBeneficio.HogarConstituido => "UNICA",
 1375            _ => vigenteDesde.Date.ToString("yyyy-MM")
 3376        };
 377    }
 378
 379    private static CategoriaTipoBeneficio DeterminarCategoria(TipoBeneficio tipo)
 380    {
 13381        var codigo = (tipo.Codigo ?? string.Empty).Trim().ToUpperInvariant();
 13382        var nombre = (tipo.Nombre ?? string.Empty).Trim().ToUpperInvariant();
 383
 13384        if (codigo.StartsWith("300") || codigo.StartsWith("301") || codigo.StartsWith("302") || codigo.StartsWith("303")
 13385            || codigo.StartsWith("304") || codigo.StartsWith("305") || codigo.StartsWith("306") || codigo.StartsWith("30
 13386            || codigo.StartsWith("308") || codigo.StartsWith("309") || codigo.StartsWith("310")
 13387            || nombre.Contains("ASIGNACION FAMILIAR"))
 388        {
 0389            return CategoriaTipoBeneficio.AsignacionFamiliar;
 390        }
 391
 13392        if (codigo == "320" || nombre.Contains("HOGAR"))
 393        {
 0394            return CategoriaTipoBeneficio.HogarConstituido;
 395        }
 396
 13397        if (codigo == "330" || nombre.Contains("SOLIDARIA") || nombre.Contains("PSF"))
 398        {
 4399            return CategoriaTipoBeneficio.PrimaSolidariaFamiliar;
 400        }
 401
 9402        if (codigo == "340" || nombre.Contains("NACIMIENTO"))
 403        {
 5404            return CategoriaTipoBeneficio.PrimaNacimiento;
 405        }
 406
 4407        if (codigo == "350" || nombre.Contains("MATRIMONIO") || nombre.Contains("UNION"))
 408        {
 4409            return CategoriaTipoBeneficio.PrimaMatrimonio;
 410        }
 411
 0412        return CategoriaTipoBeneficio.General;
 413    }
 414
 415    private static BeneficioSocialDto MapearDto(BeneficioSocial entidad, TipoBeneficio? tipo = null)
 416    {
 4417        var tipoBeneficio = tipo ?? entidad.TipoBeneficio;
 418
 4419        return new BeneficioSocialDto
 4420        {
 4421            Id = entidad.Id,
 4422            PersonaId = entidad.PersonaId,
 4423            TipoBeneficioId = entidad.TipoBeneficioId,
 4424            TipoBeneficioCodigo = tipoBeneficio?.Codigo ?? string.Empty,
 4425            TipoBeneficioNombre = tipoBeneficio?.Nombre ?? string.Empty,
 4426            Cantidad = entidad.Cantidad,
 4427            VigenteDesde = entidad.VigenteDesde,
 4428            VigenteHasta = entidad.VigenteHasta,
 4429            Estado = entidad.Estado,
 4430            DocumentoRespaldo = entidad.DocumentoRespaldo,
 4431            Observaciones = entidad.Observaciones,
 4432            FechaEvento = entidad.FechaEvento,
 4433            ClaveEvento = entidad.ClaveEvento,
 4434            RetroactividadPendiente = entidad.RetroactividadPendiente
 4435        };
 436    }
 437
 438    private enum CategoriaTipoBeneficio
 439    {
 440        General = 0,
 441        AsignacionFamiliar = 1,
 442        HogarConstituido = 2,
 443        PrimaSolidariaFamiliar = 3,
 444        PrimaNacimiento = 4,
 445        PrimaMatrimonio = 5
 446    }
 447}