< 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
82%
Covered lines: 207
Uncovered lines: 43
Coverable lines: 250
Total lines: 471
Line coverage: 82.8%
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%302887.5%
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
 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);
 105
 1106            await _auditoriaService.LogAuditoriaAsync(
 1107                usuarioId,
 1108                AccionEnum.CrearBeneficio,
 1109                ContextoEnum.Beneficios,
 1110                host: host,
 1111                entidad: nameof(BeneficioSocial),
 1112                entidadId: creado.Id,
 1113                detalle: new { operacion = "crear", id = creado.Id, personaId = creado.PersonaId, tipoBeneficioId = crea
 114
 1115            var recargado = await _beneficioSocialRepository.ObtenerPorIdAsync(creado.Id);
 1116            return (true, recargado == null ? MapearDto(creado, tipo) : MapearDto(recargado), null);
 117        }
 0118        catch (Exception ex)
 119        {
 0120            return (false, null, $"Error al crear beneficio social: {ex.Message}");
 121        }
 3122    }
 123
 124    public async Task<(bool Exito, BeneficioSocialDto? Resultado, string? Error)> ActualizarAsync(long id, ActualizarBen
 125    {
 126        try
 127        {
 2128            var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 2129            if (entidad == null)
 130            {
 1131                return (false, null, "Beneficio social no encontrado");
 132            }
 133
 1134            var tipo = entidad.TipoBeneficio ?? await _beneficioSocialRepository.ObtenerTipoBeneficioAsync(entidad.TipoB
 1135            if (tipo == null || !tipo.Activo)
 136            {
 0137                return (false, null, "Tipo de beneficio no encontrado o inactivo");
 138            }
 139
 1140            var vigenteDesde = request.VigenteDesde?.Date ?? entidad.VigenteDesde.Date;
 1141            var fechaEvento = request.FechaEvento?.Date ?? entidad.FechaEvento;
 142
 1143            if (!await _beneficioSocialRepository.ExistePersonaActivaAsync(entidad.PersonaId, vigenteDesde))
 144            {
 0145                return (false, null, "La persona no tiene relacion laboral activa para la vigencia indicada");
 146            }
 147
 1148            var requestConsolidado = new CrearBeneficioSocialRequest
 1149            {
 1150                PersonaId = entidad.PersonaId,
 1151                TipoBeneficioId = entidad.TipoBeneficioId,
 1152                Cantidad = request.Cantidad ?? entidad.Cantidad,
 1153                VigenteDesde = vigenteDesde,
 1154                VigenteHasta = request.VigenteHasta ?? entidad.VigenteHasta,
 1155                DocumentoRespaldo = request.DocumentoRespaldo ?? entidad.DocumentoRespaldo,
 1156                Observaciones = request.Observaciones ?? entidad.Observaciones,
 1157                CategoriaBeneficio = ConvertirCategoriaRequest(DeterminarCategoria(tipo)),
 1158                DatosTipo = new DatosTipoBeneficioRequest
 1159                {
 1160                    FechaEvento = fechaEvento,
 1161                    EstadoCivilUnion = request.EstadoCivilUnion,
 1162                    TieneHijosACargo = request.TieneHijosACargo,
 1163                    CantidadHijosMenores = request.CantidadHijosMenores,
 1164                    CantidadHijosDiscapacidad = request.CantidadHijosDiscapacidad,
 1165                    PrimaSolidariaFamiliarTipo = request.PrimaSolidariaFamiliarTipo,
 1166                    TieneJustificacionRetroactivo = request.TieneJustificacionRetroactivo ?? false,
 1167                    JustificacionRetroactivo = request.JustificacionRetroactivo,
 1168                    PendienteDocumentacion = request.PendienteDocumentacion ?? (entidad.Estado == EstadoPendienteDoc)
 1169                }
 1170            };
 171
 1172            var validacion = ValidarReglasPorTipo(tipo, requestConsolidado, esActualizacion: true);
 1173            if (!string.IsNullOrEmpty(validacion))
 174            {
 0175                return (false, null, validacion);
 176            }
 177
 1178            var claveEvento = ConstruirClaveEvento(tipo, fechaEvento, vigenteDesde);
 1179            if (EsBeneficioUnicaVez(tipo)
 1180                && await _beneficioSocialRepository.ExisteDuplicadoEventoAsync(entidad.PersonaId, entidad.TipoBeneficioI
 181            {
 0182                return (false, null, "Existe un beneficio duplicado para un evento de unica vez");
 183            }
 184
 1185            var retroactividadPendiente = CalcularRetroactividadPendiente(vigenteDesde);
 1186            if (retroactividadPendiente && !tipo.PermiteRetroactividad)
 187            {
 0188                return (false, null, "El tipo de beneficio no permite retroactividad para la vigencia indicada");
 189            }
 190
 1191            entidad.Cantidad = requestConsolidado.Cantidad ?? entidad.Cantidad;
 1192            entidad.VigenteDesde = vigenteDesde;
 1193            entidad.VigenteHasta = requestConsolidado.VigenteHasta?.Date;
 1194            entidad.DocumentoRespaldo = requestConsolidado.DocumentoRespaldo?.Trim();
 1195            entidad.Observaciones = ConstruirObservaciones(requestConsolidado.Observaciones, requestConsolidado.DatosTip
 1196            entidad.Estado = requestConsolidado.DatosTipo.PendienteDocumentacion ? EstadoPendienteDoc : EstadoActivo;
 1197            entidad.FechaEvento = fechaEvento;
 1198            entidad.ClaveEvento = claveEvento;
 1199            entidad.RetroactividadPendiente = retroactividadPendiente;
 200
 1201            var actualizado = await _beneficioSocialRepository.ActualizarAsync(entidad);
 202
 1203            await _auditoriaService.LogAuditoriaAsync(
 1204                usuarioId,
 1205                AccionEnum.ActualizarBeneficio,
 1206                ContextoEnum.Beneficios,
 1207                host: host,
 1208                entidad: nameof(BeneficioSocial),
 1209                entidadId: actualizado.Id,
 1210                detalle: new { operacion = "actualizar", id = actualizado.Id, personaId = actualizado.PersonaId, tipoBen
 211
 1212            return (true, MapearDto(actualizado, tipo), null);
 213        }
 0214        catch (Exception ex)
 215        {
 0216            return (false, null, $"Error al actualizar beneficio social: {ex.Message}");
 217        }
 2218    }
 219
 220    public async Task<(bool Exito, bool BajaLogica, string? Error)> EliminarAsync(long id, long usuarioId, string host)
 221    {
 222        try
 223        {
 2224            var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 2225            if (entidad == null)
 226            {
 0227                return (false, false, "Beneficio social no encontrado");
 228            }
 229
 2230            if (entidad.Estado == EstadoInactivo)
 231            {
 1232                return (true, true, null);
 233            }
 234
 1235            entidad.Estado = EstadoInactivo;
 1236            entidad.VigenteHasta ??= DateTime.UtcNow.Date;
 1237            await _beneficioSocialRepository.ActualizarAsync(entidad);
 238
 1239            await _auditoriaService.LogAuditoriaAsync(
 1240                usuarioId,
 1241                AccionEnum.EliminarBeneficio,
 1242                ContextoEnum.Beneficios,
 1243                host: host,
 1244                entidad: nameof(BeneficioSocial),
 1245                entidadId: entidad.Id,
 1246                detalle: new { operacion = "eliminar", id = entidad.Id, personaId = entidad.PersonaId, tipoBeneficioId =
 247
 1248            return (true, true, null);
 249        }
 0250        catch (Exception ex)
 251        {
 0252            return (false, false, $"Error al eliminar beneficio social: {ex.Message}");
 253        }
 2254    }
 255
 256    private static string? ValidarReglasPorTipo(TipoBeneficio tipo, CrearBeneficioSocialRequest request, bool esActualiz
 257    {
 3258        var datosTipo = request.DatosTipo ?? new DatosTipoBeneficioRequest();
 259
 3260        if (request.Cantidad.HasValue && request.Cantidad.Value < 0)
 261        {
 0262            return "La cantidad no puede ser negativa";
 263        }
 264
 3265        if (request.VigenteHasta.HasValue && request.VigenteHasta.Value.Date < request.VigenteDesde.Date)
 266        {
 0267            return "La vigencia hasta no puede ser anterior a la vigencia desde";
 268        }
 269
 3270        var categoria = DeterminarCategoria(tipo);
 271        switch (categoria)
 272        {
 273            case CategoriaTipoBeneficio.AsignacionFamiliar:
 0274                var hijosMenores = datosTipo.CantidadHijosMenores ?? 0;
 0275                var hijosDiscapacidad = datosTipo.CantidadHijosDiscapacidad ?? 0;
 0276                if (hijosMenores + hijosDiscapacidad <= 0)
 277                {
 0278                    return "Debe indicar al menos un hijo menor o con discapacidad para asignacion familiar";
 279                }
 280
 0281                if (string.IsNullOrWhiteSpace(request.DocumentoRespaldo))
 282                {
 0283                    return "La documentacion probatoria es requerida para asignacion familiar";
 284                }
 285                break;
 286
 287            case CategoriaTipoBeneficio.HogarConstituido:
 0288                if (string.IsNullOrWhiteSpace(datosTipo.EstadoCivilUnion))
 289                {
 0290                    return "Debe indicar estado civil o union concubinaria para hogar constituido";
 291                }
 292                break;
 293
 294            case CategoriaTipoBeneficio.PrimaSolidariaFamiliar:
 1295                if (string.IsNullOrWhiteSpace(datosTipo.PrimaSolidariaFamiliarTipo))
 296                {
 0297                    return "Debe indicar el tipo de prima solidaria familiar";
 298                }
 299                break;
 300
 301            case CategoriaTipoBeneficio.PrimaNacimiento:
 1302                if (!datosTipo.FechaEvento.HasValue)
 303                {
 0304                    return "Debe indicar la fecha de nacimiento para prima por nacimiento";
 305                }
 306
 1307                if (RequiereJustificacionFueraPlazo(datosTipo.FechaEvento.Value, request.VigenteDesde)
 1308                    && !datosTipo.TieneJustificacionRetroactivo
 1309                    && string.IsNullOrWhiteSpace(datosTipo.JustificacionRetroactivo))
 310                {
 0311                    return "La prima por nacimiento fuera de plazo requiere justificacion de retroactivo";
 312                }
 313                break;
 314
 315            case CategoriaTipoBeneficio.PrimaMatrimonio:
 1316                if (!datosTipo.FechaEvento.HasValue)
 317                {
 0318                    return "Debe indicar la fecha de matrimonio o union";
 319                }
 320
 1321                if (RequiereJustificacionFueraPlazo(datosTipo.FechaEvento.Value, request.VigenteDesde)
 1322                    && !datosTipo.TieneJustificacionRetroactivo
 1323                    && string.IsNullOrWhiteSpace(datosTipo.JustificacionRetroactivo))
 324                {
 0325                    return "La prima por matrimonio fuera de plazo requiere justificacion";
 326                }
 327                break;
 328
 329            case CategoriaTipoBeneficio.General:
 0330                if (!esActualizacion && request.Cantidad.GetValueOrDefault(1) <= 0)
 331                {
 0332                    return "La cantidad debe ser mayor a cero";
 333                }
 334                break;
 335        }
 336
 3337        return null;
 338    }
 339
 340    private static CategoriaBeneficioRequest ConvertirCategoriaRequest(CategoriaTipoBeneficio categoria)
 341    {
 4342        return categoria switch
 4343        {
 0344            CategoriaTipoBeneficio.AsignacionFamiliar => CategoriaBeneficioRequest.AsignacionFamiliar,
 0345            CategoriaTipoBeneficio.HogarConstituido => CategoriaBeneficioRequest.HogarConstituido,
 1346            CategoriaTipoBeneficio.PrimaSolidariaFamiliar => CategoriaBeneficioRequest.PrimaSolidariaFamiliar,
 2347            CategoriaTipoBeneficio.PrimaNacimiento => CategoriaBeneficioRequest.PrimaNacimiento,
 1348            CategoriaTipoBeneficio.PrimaMatrimonio => CategoriaBeneficioRequest.PrimaMatrimonio,
 0349            _ => CategoriaBeneficioRequest.General
 4350        };
 351    }
 352
 353    private static string ConstruirObservaciones(string? observaciones, string? justificacionRetroactivo)
 354    {
 2355        var observacionNormalizada = observaciones?.Trim();
 2356        var justificacionNormalizada = justificacionRetroactivo?.Trim();
 357
 2358        if (string.IsNullOrWhiteSpace(justificacionNormalizada))
 359        {
 2360            return observacionNormalizada ?? string.Empty;
 361        }
 362
 0363        if (string.IsNullOrWhiteSpace(observacionNormalizada))
 364        {
 0365            return $"Justificacion retroactivo: {justificacionNormalizada}";
 366        }
 367
 0368        return $"{observacionNormalizada} | Justificacion retroactivo: {justificacionNormalizada}";
 369    }
 370
 371    private static bool RequiereJustificacionFueraPlazo(DateTime fechaEvento, DateTime vigenteDesde)
 372    {
 2373        return (vigenteDesde.Date - fechaEvento.Date).TotalDays > 90;
 374    }
 375
 376    private static bool CalcularRetroactividadPendiente(DateTime vigenteDesde)
 377    {
 2378        var primerDiaMes = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1);
 2379        return vigenteDesde.Date < primerDiaMes;
 380    }
 381
 382    private static bool EsBeneficioUnicaVez(TipoBeneficio tipo)
 383    {
 3384        var categoria = DeterminarCategoria(tipo);
 3385        return categoria == CategoriaTipoBeneficio.HogarConstituido
 3386               || categoria == CategoriaTipoBeneficio.PrimaNacimiento
 3387               || categoria == CategoriaTipoBeneficio.PrimaMatrimonio;
 388    }
 389
 390    private static string ConstruirClaveEvento(TipoBeneficio tipo, DateTime? fechaEvento, DateTime vigenteDesde)
 391    {
 3392        var categoria = DeterminarCategoria(tipo);
 393
 3394        return categoria switch
 3395        {
 1396            CategoriaTipoBeneficio.PrimaNacimiento => fechaEvento?.Date.ToString("yyyy-MM-dd") ?? vigenteDesde.Date.ToSt
 1397            CategoriaTipoBeneficio.PrimaMatrimonio => fechaEvento?.Date.ToString("yyyy-MM-dd") ?? vigenteDesde.Date.ToSt
 0398            CategoriaTipoBeneficio.HogarConstituido => "UNICA",
 1399            _ => vigenteDesde.Date.ToString("yyyy-MM")
 3400        };
 401    }
 402
 403    private static CategoriaTipoBeneficio DeterminarCategoria(TipoBeneficio tipo)
 404    {
 13405        var codigo = (tipo.Codigo ?? string.Empty).Trim().ToUpperInvariant();
 13406        var nombre = (tipo.Nombre ?? string.Empty).Trim().ToUpperInvariant();
 407
 13408        if (codigo.StartsWith("300") || codigo.StartsWith("301") || codigo.StartsWith("302") || codigo.StartsWith("303")
 13409            || codigo.StartsWith("304") || codigo.StartsWith("305") || codigo.StartsWith("306") || codigo.StartsWith("30
 13410            || codigo.StartsWith("308") || codigo.StartsWith("309") || codigo.StartsWith("310")
 13411            || nombre.Contains("ASIGNACION FAMILIAR"))
 412        {
 0413            return CategoriaTipoBeneficio.AsignacionFamiliar;
 414        }
 415
 13416        if (codigo == "320" || nombre.Contains("HOGAR"))
 417        {
 0418            return CategoriaTipoBeneficio.HogarConstituido;
 419        }
 420
 13421        if (codigo == "330" || nombre.Contains("SOLIDARIA") || nombre.Contains("PSF"))
 422        {
 4423            return CategoriaTipoBeneficio.PrimaSolidariaFamiliar;
 424        }
 425
 9426        if (codigo == "340" || nombre.Contains("NACIMIENTO"))
 427        {
 5428            return CategoriaTipoBeneficio.PrimaNacimiento;
 429        }
 430
 4431        if (codigo == "350" || nombre.Contains("MATRIMONIO") || nombre.Contains("UNION"))
 432        {
 4433            return CategoriaTipoBeneficio.PrimaMatrimonio;
 434        }
 435
 0436        return CategoriaTipoBeneficio.General;
 437    }
 438
 439    private static BeneficioSocialDto MapearDto(BeneficioSocial entidad, TipoBeneficio? tipo = null)
 440    {
 4441        var tipoBeneficio = tipo ?? entidad.TipoBeneficio;
 442
 4443        return new BeneficioSocialDto
 4444        {
 4445            Id = entidad.Id,
 4446            PersonaId = entidad.PersonaId,
 4447            TipoBeneficioId = entidad.TipoBeneficioId,
 4448            TipoBeneficioCodigo = tipoBeneficio?.Codigo ?? string.Empty,
 4449            TipoBeneficioNombre = tipoBeneficio?.Nombre ?? string.Empty,
 4450            Cantidad = entidad.Cantidad,
 4451            VigenteDesde = entidad.VigenteDesde,
 4452            VigenteHasta = entidad.VigenteHasta,
 4453            Estado = entidad.Estado,
 4454            DocumentoRespaldo = entidad.DocumentoRespaldo,
 4455            Observaciones = entidad.Observaciones,
 4456            FechaEvento = entidad.FechaEvento,
 4457            ClaveEvento = entidad.ClaveEvento,
 4458            RetroactividadPendiente = entidad.RetroactividadPendiente
 4459        };
 460    }
 461
 462    private enum CategoriaTipoBeneficio
 463    {
 464        General = 0,
 465        AsignacionFamiliar = 1,
 466        HogarConstituido = 2,
 467        PrimaSolidariaFamiliar = 3,
 468        PrimaNacimiento = 4,
 469        PrimaMatrimonio = 5
 470    }
 471}