< 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
83%
Covered lines: 210
Uncovered lines: 43
Coverable lines: 253
Total lines: 475
Line coverage: 83%
Branch coverage
61%
Covered branches: 137
Total branches: 222
Branch coverage: 61.7%
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()63.33%323087.75%
ActualizarAsync()54.76%444289.23%
EliminarAsync()83.33%6685%
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    private readonly IPeriodoGuard _periodoGuard;
 16
 1017    public BeneficioSocialService(
 1018        IBeneficioSocialRepository beneficioSocialRepository,
 1019        IAuditoriaService auditoriaService,
 1020        IPeriodoGuard periodoGuard)
 21    {
 1022        _beneficioSocialRepository = beneficioSocialRepository;
 1023        _auditoriaService = auditoriaService;
 1024        _periodoGuard = periodoGuard;
 1025    }
 26
 27    public async Task<BeneficioSocialDto?> ObtenerPorIdAsync(long id)
 28    {
 229        var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 230        return entidad == null ? null : MapearDto(entidad);
 231    }
 32
 33    public async Task<PagedResult<BeneficioSocialDto>> ObtenerPaginadoAsync(int pagina, int tamano, long? personaId = nu
 34    {
 235        if (pagina < 1) pagina = 1;
 136        if (tamano < 1) tamano = 10;
 237        if (tamano > 100) tamano = 100;
 38
 139        var (items, totalCount) = await _beneficioSocialRepository.ObtenerPaginadoAsync(pagina, tamano, personaId, estad
 40
 141        return new PagedResult<BeneficioSocialDto>
 142        {
 143            Items = items.Select(item => MapearDto(item)).ToList(),
 144            Page = pagina,
 145            PageSize = tamano,
 146            TotalCount = totalCount
 147        };
 148    }
 49
 50    public async Task<(bool Exito, BeneficioSocialDto? Resultado, string? Error)> CrearAsync(CrearBeneficioSocialRequest
 51    {
 352        await _periodoGuard.VerificarPeriodoAbiertoAsync();
 53        try
 54        {
 355            var tipo = await _beneficioSocialRepository.ObtenerTipoBeneficioAsync(request.TipoBeneficioId);
 356            if (tipo == null || !tipo.Activo)
 57            {
 058                return (false, null, "Tipo de beneficio no encontrado o inactivo");
 59            }
 60
 361            var categoriaEsperada = ConvertirCategoriaRequest(DeterminarCategoria(tipo));
 362            if (request.CategoriaBeneficio != categoriaEsperada)
 63            {
 064                return (false, null, "La categoria declarada no coincide con el tipo de beneficio seleccionado");
 65            }
 66
 367            var personaActiva = await _beneficioSocialRepository.ExistePersonaActivaAsync(request.PersonaId, request.Vig
 368            if (!personaActiva)
 69            {
 170                return (false, null, "La persona no tiene relacion laboral activa para la vigencia indicada");
 71            }
 72
 273            var validacion = ValidarReglasPorTipo(tipo, request, esActualizacion: false);
 274            if (!string.IsNullOrEmpty(validacion))
 75            {
 076                return (false, null, validacion);
 77            }
 78
 279            var claveEvento = ConstruirClaveEvento(tipo, request.DatosTipo.FechaEvento, request.VigenteDesde);
 280            if (EsBeneficioUnicaVez(tipo) && await _beneficioSocialRepository.ExisteDuplicadoEventoAsync(request.Persona
 81            {
 182                return (false, null, "Existe un beneficio duplicado para un evento de unica vez");
 83            }
 84
 185            var retroactividadPendiente = CalcularRetroactividadPendiente(request.VigenteDesde);
 186            if (retroactividadPendiente && !tipo.PermiteRetroactividad)
 87            {
 088                return (false, null, "El tipo de beneficio no permite retroactividad para la vigencia indicada");
 89            }
 90
 191            var estado = request.DatosTipo.PendienteDocumentacion ? EstadoPendienteDoc : EstadoActivo;
 192            var entidad = new BeneficioSocial
 193            {
 194                PersonaId = request.PersonaId,
 195                TipoBeneficioId = request.TipoBeneficioId,
 196                Cantidad = (short)Math.Max(request.Cantidad ?? 1, (short)0),
 197                VigenteDesde = request.VigenteDesde.Date,
 198                VigenteHasta = request.VigenteHasta?.Date,
 199                Estado = estado,
 1100                DocumentoRespaldo = request.DocumentoRespaldo?.Trim(),
 1101                Observaciones = ConstruirObservaciones(request.Observaciones, request.DatosTipo.JustificacionRetroactivo
 1102                CreadoPor = usuarioId,
 1103                FechaEvento = request.DatosTipo.FechaEvento?.Date,
 1104                ClaveEvento = claveEvento,
 1105                RetroactividadPendiente = retroactividadPendiente
 1106            };
 107
 1108            var creado = await _beneficioSocialRepository.CrearAsync(entidad);
 109
 1110            await _auditoriaService.LogAuditoriaAsync(
 1111                usuarioId,
 1112                AccionEnum.CrearBeneficio,
 1113                ContextoEnum.Beneficios,
 1114                host: host,
 1115                entidad: nameof(BeneficioSocial),
 1116                entidadId: creado.Id,
 1117                detalle: new { operacion = "crear", id = creado.Id, personaId = creado.PersonaId, tipoBeneficioId = crea
 118
 1119            var recargado = await _beneficioSocialRepository.ObtenerPorIdAsync(creado.Id);
 1120            return (true, recargado == null ? MapearDto(creado, tipo) : MapearDto(recargado), null);
 121        }
 0122        catch (Exception ex)
 123        {
 0124            return (false, null, $"Error al crear beneficio social: {ex.Message}");
 125        }
 3126    }
 127
 128    public async Task<(bool Exito, BeneficioSocialDto? Resultado, string? Error)> ActualizarAsync(long id, ActualizarBen
 129    {
 130        try
 131        {
 2132            var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 2133            if (entidad == null)
 134            {
 1135                return (false, null, "Beneficio social no encontrado");
 136            }
 137
 1138            var tipo = entidad.TipoBeneficio ?? await _beneficioSocialRepository.ObtenerTipoBeneficioAsync(entidad.TipoB
 1139            if (tipo == null || !tipo.Activo)
 140            {
 0141                return (false, null, "Tipo de beneficio no encontrado o inactivo");
 142            }
 143
 1144            var vigenteDesde = request.VigenteDesde?.Date ?? entidad.VigenteDesde.Date;
 1145            var fechaEvento = request.FechaEvento?.Date ?? entidad.FechaEvento;
 146
 1147            if (!await _beneficioSocialRepository.ExistePersonaActivaAsync(entidad.PersonaId, vigenteDesde))
 148            {
 0149                return (false, null, "La persona no tiene relacion laboral activa para la vigencia indicada");
 150            }
 151
 1152            var requestConsolidado = new CrearBeneficioSocialRequest
 1153            {
 1154                PersonaId = entidad.PersonaId,
 1155                TipoBeneficioId = entidad.TipoBeneficioId,
 1156                Cantidad = request.Cantidad ?? entidad.Cantidad,
 1157                VigenteDesde = vigenteDesde,
 1158                VigenteHasta = request.VigenteHasta ?? entidad.VigenteHasta,
 1159                DocumentoRespaldo = request.DocumentoRespaldo ?? entidad.DocumentoRespaldo,
 1160                Observaciones = request.Observaciones ?? entidad.Observaciones,
 1161                CategoriaBeneficio = ConvertirCategoriaRequest(DeterminarCategoria(tipo)),
 1162                DatosTipo = new DatosTipoBeneficioRequest
 1163                {
 1164                    FechaEvento = fechaEvento,
 1165                    EstadoCivilUnion = request.EstadoCivilUnion,
 1166                    TieneHijosACargo = request.TieneHijosACargo,
 1167                    CantidadHijosMenores = request.CantidadHijosMenores,
 1168                    CantidadHijosDiscapacidad = request.CantidadHijosDiscapacidad,
 1169                    PrimaSolidariaFamiliarTipo = request.PrimaSolidariaFamiliarTipo,
 1170                    TieneJustificacionRetroactivo = request.TieneJustificacionRetroactivo ?? false,
 1171                    JustificacionRetroactivo = request.JustificacionRetroactivo,
 1172                    PendienteDocumentacion = request.PendienteDocumentacion ?? (entidad.Estado == EstadoPendienteDoc)
 1173                }
 1174            };
 175
 1176            var validacion = ValidarReglasPorTipo(tipo, requestConsolidado, esActualizacion: true);
 1177            if (!string.IsNullOrEmpty(validacion))
 178            {
 0179                return (false, null, validacion);
 180            }
 181
 1182            var claveEvento = ConstruirClaveEvento(tipo, fechaEvento, vigenteDesde);
 1183            if (EsBeneficioUnicaVez(tipo)
 1184                && await _beneficioSocialRepository.ExisteDuplicadoEventoAsync(entidad.PersonaId, entidad.TipoBeneficioI
 185            {
 0186                return (false, null, "Existe un beneficio duplicado para un evento de unica vez");
 187            }
 188
 1189            var retroactividadPendiente = CalcularRetroactividadPendiente(vigenteDesde);
 1190            if (retroactividadPendiente && !tipo.PermiteRetroactividad)
 191            {
 0192                return (false, null, "El tipo de beneficio no permite retroactividad para la vigencia indicada");
 193            }
 194
 1195            entidad.Cantidad = requestConsolidado.Cantidad ?? entidad.Cantidad;
 1196            entidad.VigenteDesde = vigenteDesde;
 1197            entidad.VigenteHasta = requestConsolidado.VigenteHasta?.Date;
 1198            entidad.DocumentoRespaldo = requestConsolidado.DocumentoRespaldo?.Trim();
 1199            entidad.Observaciones = ConstruirObservaciones(requestConsolidado.Observaciones, requestConsolidado.DatosTip
 1200            entidad.Estado = requestConsolidado.DatosTipo.PendienteDocumentacion ? EstadoPendienteDoc : EstadoActivo;
 1201            entidad.FechaEvento = fechaEvento;
 1202            entidad.ClaveEvento = claveEvento;
 1203            entidad.RetroactividadPendiente = retroactividadPendiente;
 204
 1205            var actualizado = await _beneficioSocialRepository.ActualizarAsync(entidad);
 206
 1207            await _auditoriaService.LogAuditoriaAsync(
 1208                usuarioId,
 1209                AccionEnum.ActualizarBeneficio,
 1210                ContextoEnum.Beneficios,
 1211                host: host,
 1212                entidad: nameof(BeneficioSocial),
 1213                entidadId: actualizado.Id,
 1214                detalle: new { operacion = "actualizar", id = actualizado.Id, personaId = actualizado.PersonaId, tipoBen
 215
 1216            return (true, MapearDto(actualizado, tipo), null);
 217        }
 0218        catch (Exception ex)
 219        {
 0220            return (false, null, $"Error al actualizar beneficio social: {ex.Message}");
 221        }
 2222    }
 223
 224    public async Task<(bool Exito, bool BajaLogica, string? Error)> EliminarAsync(long id, long usuarioId, string host)
 225    {
 226        try
 227        {
 2228            var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 2229            if (entidad == null)
 230            {
 0231                return (false, false, "Beneficio social no encontrado");
 232            }
 233
 2234            if (entidad.Estado == EstadoInactivo)
 235            {
 1236                return (true, true, null);
 237            }
 238
 1239            entidad.Estado = EstadoInactivo;
 1240            entidad.VigenteHasta ??= DateTime.UtcNow.Date;
 1241            await _beneficioSocialRepository.ActualizarAsync(entidad);
 242
 1243            await _auditoriaService.LogAuditoriaAsync(
 1244                usuarioId,
 1245                AccionEnum.EliminarBeneficio,
 1246                ContextoEnum.Beneficios,
 1247                host: host,
 1248                entidad: nameof(BeneficioSocial),
 1249                entidadId: entidad.Id,
 1250                detalle: new { operacion = "eliminar", id = entidad.Id, personaId = entidad.PersonaId, tipoBeneficioId =
 251
 1252            return (true, true, null);
 253        }
 0254        catch (Exception ex)
 255        {
 0256            return (false, false, $"Error al eliminar beneficio social: {ex.Message}");
 257        }
 2258    }
 259
 260    private static string? ValidarReglasPorTipo(TipoBeneficio tipo, CrearBeneficioSocialRequest request, bool esActualiz
 261    {
 3262        var datosTipo = request.DatosTipo ?? new DatosTipoBeneficioRequest();
 263
 3264        if (request.Cantidad.HasValue && request.Cantidad.Value < 0)
 265        {
 0266            return "La cantidad no puede ser negativa";
 267        }
 268
 3269        if (request.VigenteHasta.HasValue && request.VigenteHasta.Value.Date < request.VigenteDesde.Date)
 270        {
 0271            return "La vigencia hasta no puede ser anterior a la vigencia desde";
 272        }
 273
 3274        var categoria = DeterminarCategoria(tipo);
 275        switch (categoria)
 276        {
 277            case CategoriaTipoBeneficio.AsignacionFamiliar:
 0278                var hijosMenores = datosTipo.CantidadHijosMenores ?? 0;
 0279                var hijosDiscapacidad = datosTipo.CantidadHijosDiscapacidad ?? 0;
 0280                if (hijosMenores + hijosDiscapacidad <= 0)
 281                {
 0282                    return "Debe indicar al menos un hijo menor o con discapacidad para asignacion familiar";
 283                }
 284
 0285                if (string.IsNullOrWhiteSpace(request.DocumentoRespaldo))
 286                {
 0287                    return "La documentacion probatoria es requerida para asignacion familiar";
 288                }
 289                break;
 290
 291            case CategoriaTipoBeneficio.HogarConstituido:
 0292                if (string.IsNullOrWhiteSpace(datosTipo.EstadoCivilUnion))
 293                {
 0294                    return "Debe indicar estado civil o union concubinaria para hogar constituido";
 295                }
 296                break;
 297
 298            case CategoriaTipoBeneficio.PrimaSolidariaFamiliar:
 1299                if (string.IsNullOrWhiteSpace(datosTipo.PrimaSolidariaFamiliarTipo))
 300                {
 0301                    return "Debe indicar el tipo de prima solidaria familiar";
 302                }
 303                break;
 304
 305            case CategoriaTipoBeneficio.PrimaNacimiento:
 1306                if (!datosTipo.FechaEvento.HasValue)
 307                {
 0308                    return "Debe indicar la fecha de nacimiento para prima por nacimiento";
 309                }
 310
 1311                if (RequiereJustificacionFueraPlazo(datosTipo.FechaEvento.Value, request.VigenteDesde)
 1312                    && !datosTipo.TieneJustificacionRetroactivo
 1313                    && string.IsNullOrWhiteSpace(datosTipo.JustificacionRetroactivo))
 314                {
 0315                    return "La prima por nacimiento fuera de plazo requiere justificacion de retroactivo";
 316                }
 317                break;
 318
 319            case CategoriaTipoBeneficio.PrimaMatrimonio:
 1320                if (!datosTipo.FechaEvento.HasValue)
 321                {
 0322                    return "Debe indicar la fecha de matrimonio o union";
 323                }
 324
 1325                if (RequiereJustificacionFueraPlazo(datosTipo.FechaEvento.Value, request.VigenteDesde)
 1326                    && !datosTipo.TieneJustificacionRetroactivo
 1327                    && string.IsNullOrWhiteSpace(datosTipo.JustificacionRetroactivo))
 328                {
 0329                    return "La prima por matrimonio fuera de plazo requiere justificacion";
 330                }
 331                break;
 332
 333            case CategoriaTipoBeneficio.General:
 0334                if (!esActualizacion && request.Cantidad.GetValueOrDefault(1) <= 0)
 335                {
 0336                    return "La cantidad debe ser mayor a cero";
 337                }
 338                break;
 339        }
 340
 3341        return null;
 342    }
 343
 344    private static CategoriaBeneficioRequest ConvertirCategoriaRequest(CategoriaTipoBeneficio categoria)
 345    {
 4346        return categoria switch
 4347        {
 0348            CategoriaTipoBeneficio.AsignacionFamiliar => CategoriaBeneficioRequest.AsignacionFamiliar,
 0349            CategoriaTipoBeneficio.HogarConstituido => CategoriaBeneficioRequest.HogarConstituido,
 1350            CategoriaTipoBeneficio.PrimaSolidariaFamiliar => CategoriaBeneficioRequest.PrimaSolidariaFamiliar,
 2351            CategoriaTipoBeneficio.PrimaNacimiento => CategoriaBeneficioRequest.PrimaNacimiento,
 1352            CategoriaTipoBeneficio.PrimaMatrimonio => CategoriaBeneficioRequest.PrimaMatrimonio,
 0353            _ => CategoriaBeneficioRequest.General
 4354        };
 355    }
 356
 357    private static string ConstruirObservaciones(string? observaciones, string? justificacionRetroactivo)
 358    {
 2359        var observacionNormalizada = observaciones?.Trim();
 2360        var justificacionNormalizada = justificacionRetroactivo?.Trim();
 361
 2362        if (string.IsNullOrWhiteSpace(justificacionNormalizada))
 363        {
 2364            return observacionNormalizada ?? string.Empty;
 365        }
 366
 0367        if (string.IsNullOrWhiteSpace(observacionNormalizada))
 368        {
 0369            return $"Justificacion retroactivo: {justificacionNormalizada}";
 370        }
 371
 0372        return $"{observacionNormalizada} | Justificacion retroactivo: {justificacionNormalizada}";
 373    }
 374
 375    private static bool RequiereJustificacionFueraPlazo(DateTime fechaEvento, DateTime vigenteDesde)
 376    {
 2377        return (vigenteDesde.Date - fechaEvento.Date).TotalDays > 90;
 378    }
 379
 380    private static bool CalcularRetroactividadPendiente(DateTime vigenteDesde)
 381    {
 2382        var primerDiaMes = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1);
 2383        return vigenteDesde.Date < primerDiaMes;
 384    }
 385
 386    private static bool EsBeneficioUnicaVez(TipoBeneficio tipo)
 387    {
 3388        var categoria = DeterminarCategoria(tipo);
 3389        return categoria == CategoriaTipoBeneficio.HogarConstituido
 3390               || categoria == CategoriaTipoBeneficio.PrimaNacimiento
 3391               || categoria == CategoriaTipoBeneficio.PrimaMatrimonio;
 392    }
 393
 394    private static string ConstruirClaveEvento(TipoBeneficio tipo, DateTime? fechaEvento, DateTime vigenteDesde)
 395    {
 3396        var categoria = DeterminarCategoria(tipo);
 397
 3398        return categoria switch
 3399        {
 1400            CategoriaTipoBeneficio.PrimaNacimiento => fechaEvento?.Date.ToString("yyyy-MM-dd") ?? vigenteDesde.Date.ToSt
 1401            CategoriaTipoBeneficio.PrimaMatrimonio => fechaEvento?.Date.ToString("yyyy-MM-dd") ?? vigenteDesde.Date.ToSt
 0402            CategoriaTipoBeneficio.HogarConstituido => "UNICA",
 1403            _ => vigenteDesde.Date.ToString("yyyy-MM")
 3404        };
 405    }
 406
 407    private static CategoriaTipoBeneficio DeterminarCategoria(TipoBeneficio tipo)
 408    {
 13409        var codigo = (tipo.Codigo ?? string.Empty).Trim().ToUpperInvariant();
 13410        var nombre = (tipo.Nombre ?? string.Empty).Trim().ToUpperInvariant();
 411
 13412        if (codigo.StartsWith("300") || codigo.StartsWith("301") || codigo.StartsWith("302") || codigo.StartsWith("303")
 13413            || codigo.StartsWith("304") || codigo.StartsWith("305") || codigo.StartsWith("306") || codigo.StartsWith("30
 13414            || codigo.StartsWith("308") || codigo.StartsWith("309") || codigo.StartsWith("310")
 13415            || nombre.Contains("ASIGNACION FAMILIAR"))
 416        {
 0417            return CategoriaTipoBeneficio.AsignacionFamiliar;
 418        }
 419
 13420        if (codigo == "320" || nombre.Contains("HOGAR"))
 421        {
 0422            return CategoriaTipoBeneficio.HogarConstituido;
 423        }
 424
 13425        if (codigo == "330" || nombre.Contains("SOLIDARIA") || nombre.Contains("PSF"))
 426        {
 4427            return CategoriaTipoBeneficio.PrimaSolidariaFamiliar;
 428        }
 429
 9430        if (codigo == "340" || nombre.Contains("NACIMIENTO"))
 431        {
 5432            return CategoriaTipoBeneficio.PrimaNacimiento;
 433        }
 434
 4435        if (codigo == "350" || nombre.Contains("MATRIMONIO") || nombre.Contains("UNION"))
 436        {
 4437            return CategoriaTipoBeneficio.PrimaMatrimonio;
 438        }
 439
 0440        return CategoriaTipoBeneficio.General;
 441    }
 442
 443    private static BeneficioSocialDto MapearDto(BeneficioSocial entidad, TipoBeneficio? tipo = null)
 444    {
 4445        var tipoBeneficio = tipo ?? entidad.TipoBeneficio;
 446
 4447        return new BeneficioSocialDto
 4448        {
 4449            Id = entidad.Id,
 4450            PersonaId = entidad.PersonaId,
 4451            TipoBeneficioId = entidad.TipoBeneficioId,
 4452            TipoBeneficioCodigo = tipoBeneficio?.Codigo ?? string.Empty,
 4453            TipoBeneficioNombre = tipoBeneficio?.Nombre ?? string.Empty,
 4454            Cantidad = entidad.Cantidad,
 4455            VigenteDesde = entidad.VigenteDesde,
 4456            VigenteHasta = entidad.VigenteHasta,
 4457            Estado = entidad.Estado,
 4458            DocumentoRespaldo = entidad.DocumentoRespaldo,
 4459            Observaciones = entidad.Observaciones,
 4460            FechaEvento = entidad.FechaEvento,
 4461            ClaveEvento = entidad.ClaveEvento,
 4462            RetroactividadPendiente = entidad.RetroactividadPendiente
 4463        };
 464    }
 465
 466    private enum CategoriaTipoBeneficio
 467    {
 468        General = 0,
 469        AsignacionFamiliar = 1,
 470        HogarConstituido = 2,
 471        PrimaSolidariaFamiliar = 3,
 472        PrimaNacimiento = 4,
 473        PrimaMatrimonio = 5
 474    }
 475}