< 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
79%
Covered lines: 207
Uncovered lines: 55
Coverable lines: 262
Total lines: 491
Line coverage: 79%
Branch coverage
61%
Covered branches: 133
Total branches: 216
Branch coverage: 61.5%
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.35%3793737.03%
ConvertirCategoriaRequest(...)50%7666.66%
ConstruirObservaciones(...)60%181057.14%
RequiereJustificacionFueraPlazo(...)100%210%
CalcularRetroactividadPendiente(...)100%11100%
EsBeneficioUnicaVez(...)100%44100%
ConstruirClaveEvento(...)61.53%131387.5%
DeterminarCategoria(...)60.41%634881.25%
MapearDto(...)100%1010100%
SubirDocumentoAsync()0%620%

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    private readonly IStorageService _storage;
 17
 1018    public BeneficioSocialService(
 1019        IBeneficioSocialRepository beneficioSocialRepository,
 1020        IAuditoriaService auditoriaService,
 1021        IPeriodoGuard periodoGuard,
 1022        IStorageService storage)
 23    {
 1024        _beneficioSocialRepository = beneficioSocialRepository;
 1025        _auditoriaService = auditoriaService;
 1026        _periodoGuard = periodoGuard;
 1027        _storage = storage;
 1028    }
 29
 30    public async Task<BeneficioSocialDto?> ObtenerPorIdAsync(long id)
 31    {
 232        var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 233        return entidad == null ? null : MapearDto(entidad);
 234    }
 35
 36    public async Task<PagedResult<BeneficioSocialDto>> ObtenerPaginadoAsync(int pagina, int tamano, long? personaId = nu
 37    {
 238        if (pagina < 1) pagina = 1;
 139        if (tamano < 1) tamano = 10;
 240        if (tamano > 100) tamano = 100;
 41
 142        var (items, totalCount) = await _beneficioSocialRepository.ObtenerPaginadoAsync(pagina, tamano, personaId, estad
 43
 144        return new PagedResult<BeneficioSocialDto>
 145        {
 146            Items = items.Select(item => MapearDto(item)).ToList(),
 147            Page = pagina,
 148            PageSize = tamano,
 149            TotalCount = totalCount
 150        };
 151    }
 52
 53    public async Task<(bool Exito, BeneficioSocialDto? Resultado, string? Error)> CrearAsync(CrearBeneficioSocialRequest
 54    {
 355        await _periodoGuard.VerificarPeriodoAbiertoAsync();
 56        try
 57        {
 358            var tipo = await _beneficioSocialRepository.ObtenerTipoBeneficioAsync(request.TipoBeneficioId);
 359            if (tipo == null || !tipo.Activo)
 60            {
 061                return (false, null, "Tipo de beneficio no encontrado o inactivo");
 62            }
 63
 364            var categoriaEsperada = ConvertirCategoriaRequest(DeterminarCategoria(tipo));
 365            if (request.CategoriaBeneficio != categoriaEsperada)
 66            {
 067                return (false, null, "La categoria declarada no coincide con el tipo de beneficio seleccionado");
 68            }
 69
 370            var personaActiva = await _beneficioSocialRepository.ExistePersonaActivaAsync(request.PersonaId, request.Vig
 371            if (!personaActiva)
 72            {
 173                return (false, null, "La persona no tiene relacion laboral activa para la vigencia indicada");
 74            }
 75
 276            var validacion = ValidarReglasPorTipo(tipo, request, esActualizacion: false);
 277            if (!string.IsNullOrEmpty(validacion))
 78            {
 079                return (false, null, validacion);
 80            }
 81
 282            var claveEvento = ConstruirClaveEvento(tipo, request.DatosTipo.FechaEvento, request.VigenteDesde);
 283            if (EsBeneficioUnicaVez(tipo) && await _beneficioSocialRepository.ExisteDuplicadoEventoAsync(request.Persona
 84            {
 185                return (false, null, "Existe un beneficio duplicado para un evento de unica vez");
 86            }
 87
 188            var retroactividadPendiente = CalcularRetroactividadPendiente(request.VigenteDesde);
 189            if (retroactividadPendiente && !tipo.PermiteRetroactividad)
 90            {
 091                return (false, null, "El tipo de beneficio no permite retroactividad para la vigencia indicada");
 92            }
 93
 194            var estado = request.DatosTipo.PendienteDocumentacion ? EstadoPendienteDoc : EstadoActivo;
 195            var entidad = new BeneficioSocial
 196            {
 197                PersonaId = request.PersonaId,
 198                TipoBeneficioId = request.TipoBeneficioId,
 199                Cantidad = (short)Math.Max(request.Cantidad ?? 1, (short)0),
 1100                VigenteDesde = request.VigenteDesde.Date,
 1101                VigenteHasta = request.VigenteHasta?.Date,
 1102                Estado = estado,
 1103                DocumentoRespaldo = request.DocumentoRespaldo?.Trim(),
 1104                Observaciones = ConstruirObservaciones(request.Observaciones, request.DatosTipo.JustificacionRetroactivo
 1105                CreadoPor = usuarioId,
 1106                FechaEvento = request.DatosTipo.FechaEvento?.Date,
 1107                ClaveEvento = claveEvento,
 1108                RetroactividadPendiente = retroactividadPendiente
 1109            };
 110
 1111            var creado = await _beneficioSocialRepository.CrearAsync(entidad);
 112
 1113            await _auditoriaService.LogAuditoriaAsync(
 1114                usuarioId,
 1115                AccionEnum.CrearBeneficio,
 1116                ContextoEnum.Beneficios,
 1117                host: host,
 1118                entidad: nameof(BeneficioSocial),
 1119                entidadId: creado.Id,
 1120                detalle: new { operacion = "crear", id = creado.Id, personaId = creado.PersonaId, tipoBeneficioId = crea
 121
 1122            var recargado = await _beneficioSocialRepository.ObtenerPorIdAsync(creado.Id);
 1123            return (true, recargado == null ? MapearDto(creado, tipo) : MapearDto(recargado), null);
 124        }
 0125        catch (Exception ex)
 126        {
 0127            return (false, null, $"Error al crear beneficio social: {ex.Message}");
 128        }
 3129    }
 130
 131    public async Task<(bool Exito, BeneficioSocialDto? Resultado, string? Error)> ActualizarAsync(long id, ActualizarBen
 132    {
 133        try
 134        {
 2135            var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 2136            if (entidad == null)
 137            {
 1138                return (false, null, "Beneficio social no encontrado");
 139            }
 140
 1141            var tipo = entidad.TipoBeneficio ?? await _beneficioSocialRepository.ObtenerTipoBeneficioAsync(entidad.TipoB
 1142            if (tipo == null || !tipo.Activo)
 143            {
 0144                return (false, null, "Tipo de beneficio no encontrado o inactivo");
 145            }
 146
 1147            var vigenteDesde = request.VigenteDesde?.Date ?? entidad.VigenteDesde.Date;
 1148            var fechaEvento = request.FechaEvento?.Date ?? entidad.FechaEvento;
 149
 1150            if (!await _beneficioSocialRepository.ExistePersonaActivaAsync(entidad.PersonaId, vigenteDesde))
 151            {
 0152                return (false, null, "La persona no tiene relacion laboral activa para la vigencia indicada");
 153            }
 154
 1155            var requestConsolidado = new CrearBeneficioSocialRequest
 1156            {
 1157                PersonaId = entidad.PersonaId,
 1158                TipoBeneficioId = entidad.TipoBeneficioId,
 1159                Cantidad = request.Cantidad ?? entidad.Cantidad,
 1160                VigenteDesde = vigenteDesde,
 1161                VigenteHasta = request.VigenteHasta ?? entidad.VigenteHasta,
 1162                DocumentoRespaldo = request.DocumentoRespaldo ?? entidad.DocumentoRespaldo,
 1163                Observaciones = request.Observaciones ?? entidad.Observaciones,
 1164                CategoriaBeneficio = ConvertirCategoriaRequest(DeterminarCategoria(tipo)),
 1165                DatosTipo = new DatosTipoBeneficioRequest
 1166                {
 1167                    FechaEvento = fechaEvento,
 1168                    EstadoCivilUnion = request.EstadoCivilUnion,
 1169                    TieneHijosACargo = request.TieneHijosACargo,
 1170                    CantidadHijosMenores = request.CantidadHijosMenores,
 1171                    CantidadHijosDiscapacidad = request.CantidadHijosDiscapacidad,
 1172                    PrimaSolidariaFamiliarTipo = request.PrimaSolidariaFamiliarTipo,
 1173                    TieneJustificacionRetroactivo = request.TieneJustificacionRetroactivo ?? false,
 1174                    JustificacionRetroactivo = request.JustificacionRetroactivo,
 1175                    PendienteDocumentacion = request.PendienteDocumentacion ?? (entidad.Estado == EstadoPendienteDoc)
 1176                }
 1177            };
 178
 1179            var validacion = ValidarReglasPorTipo(tipo, requestConsolidado, esActualizacion: true);
 1180            if (!string.IsNullOrEmpty(validacion))
 181            {
 0182                return (false, null, validacion);
 183            }
 184
 1185            var claveEvento = ConstruirClaveEvento(tipo, fechaEvento, vigenteDesde);
 1186            if (EsBeneficioUnicaVez(tipo)
 1187                && await _beneficioSocialRepository.ExisteDuplicadoEventoAsync(entidad.PersonaId, entidad.TipoBeneficioI
 188            {
 0189                return (false, null, "Existe un beneficio duplicado para un evento de unica vez");
 190            }
 191
 1192            var retroactividadPendiente = CalcularRetroactividadPendiente(vigenteDesde);
 1193            if (retroactividadPendiente && !tipo.PermiteRetroactividad)
 194            {
 0195                return (false, null, "El tipo de beneficio no permite retroactividad para la vigencia indicada");
 196            }
 197
 1198            entidad.Cantidad = requestConsolidado.Cantidad ?? entidad.Cantidad;
 1199            entidad.VigenteDesde = vigenteDesde;
 1200            entidad.VigenteHasta = requestConsolidado.VigenteHasta?.Date;
 1201            entidad.DocumentoRespaldo = requestConsolidado.DocumentoRespaldo?.Trim();
 1202            entidad.Observaciones = ConstruirObservaciones(requestConsolidado.Observaciones, requestConsolidado.DatosTip
 1203            entidad.Estado = requestConsolidado.DatosTipo.PendienteDocumentacion ? EstadoPendienteDoc : EstadoActivo;
 1204            entidad.FechaEvento = fechaEvento;
 1205            entidad.ClaveEvento = claveEvento;
 1206            entidad.RetroactividadPendiente = retroactividadPendiente;
 207
 1208            var actualizado = await _beneficioSocialRepository.ActualizarAsync(entidad);
 209
 1210            await _auditoriaService.LogAuditoriaAsync(
 1211                usuarioId,
 1212                AccionEnum.ActualizarBeneficio,
 1213                ContextoEnum.Beneficios,
 1214                host: host,
 1215                entidad: nameof(BeneficioSocial),
 1216                entidadId: actualizado.Id,
 1217                detalle: new { operacion = "actualizar", id = actualizado.Id, personaId = actualizado.PersonaId, tipoBen
 218
 1219            return (true, MapearDto(actualizado, tipo), null);
 220        }
 0221        catch (Exception ex)
 222        {
 0223            return (false, null, $"Error al actualizar beneficio social: {ex.Message}");
 224        }
 2225    }
 226
 227    public async Task<(bool Exito, bool BajaLogica, string? Error)> EliminarAsync(long id, long usuarioId, string host)
 228    {
 229        try
 230        {
 2231            var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(id);
 2232            if (entidad == null)
 233            {
 0234                return (false, false, "Beneficio social no encontrado");
 235            }
 236
 2237            if (entidad.Estado == EstadoInactivo)
 238            {
 1239                return (true, true, null);
 240            }
 241
 1242            entidad.Estado = EstadoInactivo;
 1243            entidad.VigenteHasta ??= DateTime.UtcNow.Date;
 1244            await _beneficioSocialRepository.ActualizarAsync(entidad);
 245
 1246            await _auditoriaService.LogAuditoriaAsync(
 1247                usuarioId,
 1248                AccionEnum.EliminarBeneficio,
 1249                ContextoEnum.Beneficios,
 1250                host: host,
 1251                entidad: nameof(BeneficioSocial),
 1252                entidadId: entidad.Id,
 1253                detalle: new { operacion = "eliminar", id = entidad.Id, personaId = entidad.PersonaId, tipoBeneficioId =
 254
 1255            return (true, true, null);
 256        }
 0257        catch (Exception ex)
 258        {
 0259            return (false, false, $"Error al eliminar beneficio social: {ex.Message}");
 260        }
 2261    }
 262
 263    private static string? ValidarReglasPorTipo(TipoBeneficio tipo, CrearBeneficioSocialRequest request, bool esActualiz
 264    {
 3265        var datosTipo = request.DatosTipo ?? new DatosTipoBeneficioRequest();
 266
 3267        if (request.Cantidad.HasValue && request.Cantidad.Value < 0)
 268        {
 0269            return "La cantidad no puede ser negativa";
 270        }
 271
 3272        if (request.VigenteHasta.HasValue && request.VigenteHasta.Value.Date < request.VigenteDesde.Date)
 273        {
 0274            return "La vigencia hasta no puede ser anterior a la vigencia desde";
 275        }
 276
 3277        var categoria = DeterminarCategoria(tipo);
 278        switch (categoria)
 279        {
 280            case CategoriaTipoBeneficio.AsignacionFamiliar:
 0281                var hijosMenores = datosTipo.CantidadHijosMenores ?? 0;
 0282                var hijosDiscapacidad = datosTipo.CantidadHijosDiscapacidad ?? 0;
 0283                if (hijosMenores + hijosDiscapacidad <= 0)
 284                {
 0285                    return "Debe indicar al menos un hijo menor o con discapacidad para asignacion familiar";
 286                }
 287
 0288                if (string.IsNullOrWhiteSpace(request.DocumentoRespaldo))
 289                {
 0290                    return "La documentacion probatoria es requerida para asignacion familiar";
 291                }
 292                break;
 293
 294            case CategoriaTipoBeneficio.HogarConstituido:
 0295                if (string.IsNullOrWhiteSpace(datosTipo.EstadoCivilUnion))
 296                {
 0297                    return "Debe indicar estado civil o union concubinaria para hogar constituido";
 298                }
 299                break;
 300
 301            case CategoriaTipoBeneficio.PrimaSolidariaFamiliar:
 1302                if (string.IsNullOrWhiteSpace(datosTipo.PrimaSolidariaFamiliarTipo))
 303                {
 0304                    return "Debe indicar el tipo de prima solidaria familiar";
 305                }
 306                break;
 307
 308            case CategoriaTipoBeneficio.PrimaNacimiento:
 1309                if (!datosTipo.FechaEvento.HasValue)
 310                {
 0311                    return "Debe indicar la fecha de nacimiento para prima por nacimiento";
 312                }
 313
 1314                if ((request.VigenteDesde.Date - datosTipo.FechaEvento.Value.Date).TotalDays > 60)
 315                {
 0316                    return "La solicitud de prima por nacimiento debe presentarse dentro de los 60 días del nacimiento. 
 317                }
 318                break;
 319
 320            case CategoriaTipoBeneficio.PrimaMatrimonio:
 1321                if (!datosTipo.FechaEvento.HasValue)
 322                {
 0323                    return "Debe indicar la fecha de matrimonio o union";
 324                }
 325
 1326                if ((request.VigenteDesde.Date - datosTipo.FechaEvento.Value.Date).TotalDays > 60)
 327                {
 0328                    return "La solicitud de prima por matrimonio debe presentarse dentro de los 60 días del matrimonio. 
 329                }
 330                break;
 331
 332            case CategoriaTipoBeneficio.General:
 0333                if (!esActualizacion && request.Cantidad.GetValueOrDefault(1) <= 0)
 334                {
 0335                    return "La cantidad debe ser mayor a cero";
 336                }
 337                break;
 338        }
 339
 3340        return null;
 341    }
 342
 343    private static CategoriaBeneficioRequest ConvertirCategoriaRequest(CategoriaTipoBeneficio categoria)
 344    {
 4345        return categoria switch
 4346        {
 0347            CategoriaTipoBeneficio.AsignacionFamiliar => CategoriaBeneficioRequest.AsignacionFamiliar,
 0348            CategoriaTipoBeneficio.HogarConstituido => CategoriaBeneficioRequest.HogarConstituido,
 1349            CategoriaTipoBeneficio.PrimaSolidariaFamiliar => CategoriaBeneficioRequest.PrimaSolidariaFamiliar,
 2350            CategoriaTipoBeneficio.PrimaNacimiento => CategoriaBeneficioRequest.PrimaNacimiento,
 1351            CategoriaTipoBeneficio.PrimaMatrimonio => CategoriaBeneficioRequest.PrimaMatrimonio,
 0352            _ => CategoriaBeneficioRequest.General
 4353        };
 354    }
 355
 356    private static string ConstruirObservaciones(string? observaciones, string? justificacionRetroactivo)
 357    {
 2358        var observacionNormalizada = observaciones?.Trim();
 2359        var justificacionNormalizada = justificacionRetroactivo?.Trim();
 360
 2361        if (string.IsNullOrWhiteSpace(justificacionNormalizada))
 362        {
 2363            return observacionNormalizada ?? string.Empty;
 364        }
 365
 0366        if (string.IsNullOrWhiteSpace(observacionNormalizada))
 367        {
 0368            return $"Justificacion retroactivo: {justificacionNormalizada}";
 369        }
 370
 0371        return $"{observacionNormalizada} | Justificacion retroactivo: {justificacionNormalizada}";
 372    }
 373
 374    private static bool RequiereJustificacionFueraPlazo(DateTime fechaEvento, DateTime vigenteDesde)
 375    {
 0376        return (vigenteDesde.Date - fechaEvento.Date).TotalDays > 90;
 377    }
 378
 379    private static bool CalcularRetroactividadPendiente(DateTime vigenteDesde)
 380    {
 2381        var primerDiaMes = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1);
 2382        return vigenteDesde.Date < primerDiaMes;
 383    }
 384
 385    private static bool EsBeneficioUnicaVez(TipoBeneficio tipo)
 386    {
 3387        var categoria = DeterminarCategoria(tipo);
 3388        return categoria == CategoriaTipoBeneficio.HogarConstituido
 3389               || categoria == CategoriaTipoBeneficio.PrimaNacimiento
 3390               || categoria == CategoriaTipoBeneficio.PrimaMatrimonio;
 391    }
 392
 393    private static string ConstruirClaveEvento(TipoBeneficio tipo, DateTime? fechaEvento, DateTime vigenteDesde)
 394    {
 3395        var categoria = DeterminarCategoria(tipo);
 396
 3397        return categoria switch
 3398        {
 1399            CategoriaTipoBeneficio.PrimaNacimiento => fechaEvento?.Date.ToString("yyyy-MM-dd") ?? vigenteDesde.Date.ToSt
 1400            CategoriaTipoBeneficio.PrimaMatrimonio => fechaEvento?.Date.ToString("yyyy-MM-dd") ?? vigenteDesde.Date.ToSt
 0401            CategoriaTipoBeneficio.HogarConstituido => "UNICA",
 1402            _ => vigenteDesde.Date.ToString("yyyy-MM")
 3403        };
 404    }
 405
 406    private static CategoriaTipoBeneficio DeterminarCategoria(TipoBeneficio tipo)
 407    {
 13408        var codigo = (tipo.Codigo ?? string.Empty).Trim().ToUpperInvariant();
 13409        var nombre = (tipo.Nombre ?? string.Empty).Trim().ToUpperInvariant();
 410
 13411        if (codigo.StartsWith("300") || codigo.StartsWith("301") || codigo.StartsWith("302") || codigo.StartsWith("303")
 13412            || codigo.StartsWith("304") || codigo.StartsWith("305") || codigo.StartsWith("306") || codigo.StartsWith("30
 13413            || codigo.StartsWith("308") || codigo.StartsWith("309") || codigo.StartsWith("310")
 13414            || nombre.Contains("ASIGNACION FAMILIAR"))
 415        {
 0416            return CategoriaTipoBeneficio.AsignacionFamiliar;
 417        }
 418
 13419        if (codigo == "320" || nombre.Contains("HOGAR"))
 420        {
 0421            return CategoriaTipoBeneficio.HogarConstituido;
 422        }
 423
 13424        if (codigo == "330" || nombre.Contains("SOLIDARIA") || nombre.Contains("PSF"))
 425        {
 4426            return CategoriaTipoBeneficio.PrimaSolidariaFamiliar;
 427        }
 428
 9429        if (codigo == "340" || nombre.Contains("NACIMIENTO"))
 430        {
 5431            return CategoriaTipoBeneficio.PrimaNacimiento;
 432        }
 433
 4434        if (codigo == "350" || nombre.Contains("MATRIMONIO") || nombre.Contains("UNION"))
 435        {
 4436            return CategoriaTipoBeneficio.PrimaMatrimonio;
 437        }
 438
 0439        return CategoriaTipoBeneficio.General;
 440    }
 441
 442    private static BeneficioSocialDto MapearDto(BeneficioSocial entidad, TipoBeneficio? tipo = null)
 443    {
 4444        var tipoBeneficio = tipo ?? entidad.TipoBeneficio;
 445
 4446        return new BeneficioSocialDto
 4447        {
 4448            Id = entidad.Id,
 4449            PersonaId = entidad.PersonaId,
 4450            TipoBeneficioId = entidad.TipoBeneficioId,
 4451            TipoBeneficioCodigo = tipoBeneficio?.Codigo ?? string.Empty,
 4452            TipoBeneficioNombre = tipoBeneficio?.Nombre ?? string.Empty,
 4453            Cantidad = entidad.Cantidad,
 4454            VigenteDesde = entidad.VigenteDesde,
 4455            VigenteHasta = entidad.VigenteHasta,
 4456            Estado = entidad.Estado,
 4457            DocumentoRespaldo = entidad.DocumentoRespaldo,
 4458            Observaciones = entidad.Observaciones,
 4459            FechaEvento = entidad.FechaEvento,
 4460            ClaveEvento = entidad.ClaveEvento,
 4461            RetroactividadPendiente = entidad.RetroactividadPendiente
 4462        };
 463    }
 464
 465    private enum CategoriaTipoBeneficio
 466    {
 467        General = 0,
 468        AsignacionFamiliar = 1,
 469        HogarConstituido = 2,
 470        PrimaSolidariaFamiliar = 3,
 471        PrimaNacimiento = 4,
 472        PrimaMatrimonio = 5
 473    }
 474
 475    public async Task<string> SubirDocumentoAsync(long beneficioId, Stream contenido, string nombreOriginal, string cont
 476    {
 0477        var entidad = await _beneficioSocialRepository.ObtenerPorIdAsync(beneficioId)
 0478            ?? throw new ArgumentException($"Beneficio social {beneficioId} no encontrado.");
 479
 0480        var ext = Path.GetExtension(nombreOriginal);
 0481        var nombreSanitizado = Path.GetFileNameWithoutExtension(nombreOriginal)
 0482            .Replace(" ", "_").Replace("..", "") + ext;
 0483        var rutaRelativa = $"beneficios/{beneficioId}/{nombreSanitizado}";
 484
 0485        await _storage.GuardarAsync(contenido, rutaRelativa, contentType);
 0486        entidad.DocumentoRespaldo = rutaRelativa;
 0487        await _beneficioSocialRepository.ActualizarAsync(entidad);
 488
 0489        return rutaRelativa;
 0490    }
 491}