< Summary

Information
Class: FAU.API.Controllers.RecibosController
Assembly: FAU.API
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/RecibosController.cs
Line coverage
57%
Covered lines: 56
Uncovered lines: 41
Coverable lines: 97
Total lines: 202
Line coverage: 57.7%
Branch coverage
90%
Covered branches: 18
Total branches: 20
Branch coverage: 90%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetResumenes()100%4484.21%
GetRecibo()100%6678.57%
GetReciboPdf()83.33%13641.66%
GetLote()75%11425%

File(s)

/home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/RecibosController.cs

#LineLine coverage
 1using FAU.API.Security;
 2using FAU.Entidades;
 3using FAU.Logica.DTOs;
 4using FAU.Logica.Services;
 5using Microsoft.AspNetCore.Authorization;
 6using Microsoft.AspNetCore.Mvc;
 7
 8namespace FAU.API.Controllers;
 9
 10[ApiController]
 11[Route("api/liquidacion/recibos")]
 12[Authorize]
 13public class RecibosController : ControllerBase
 14{
 15    private readonly IReciboService _reciboService;
 16    private readonly IPeriodoService _periodoService;
 17    private readonly IAuditoriaService _auditoriaService;
 18    private readonly ICurrentUserContext _currentUser;
 19    private readonly ILogger<RecibosController> _logger;
 20
 1221    public RecibosController(
 1222        IReciboService reciboService,
 1223        IPeriodoService periodoService,
 1224        IAuditoriaService auditoriaService,
 1225        ICurrentUserContext currentUser,
 1226        ILogger<RecibosController> logger)
 27    {
 1228        _reciboService    = reciboService;
 1229        _periodoService   = periodoService;
 1230        _auditoriaService = auditoriaService;
 1231        _currentUser      = currentUser;
 1232        _logger           = logger;
 1233    }
 34
 35    /// <summary>
 36    /// Lista los recibos disponibles para un período cerrado, con filtros opcionales.
 37    /// GET /api/liquidacion/recibos/{periodoId}
 38    /// </summary>
 39    [HttpGet("{periodoId}")]
 40    [RequirePermission(PermisoEnum.VerRecibos)]
 41    public async Task<IActionResult> GetResumenes(
 42        long periodoId,
 43        [FromQuery] string? regimen = null,
 44        [FromQuery] long? programaId = null,
 45        [FromQuery] long? unidadId = null,
 46        [FromQuery] string? cedula = null)
 47    {
 48        try
 49        {
 350            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 351            if (periodo == null)
 152                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 53
 254            if (periodo.Estado != Periodo.EstadoCerrada)
 155                return Conflict(ApiResponse<string>.ErrorResult(
 156                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 57
 158            var filtro = new ReciboFiltroRequest
 159            {
 160                Regimen    = regimen,
 161                ProgramaId = programaId,
 162                UnidadId   = unidadId,
 163                Cedula     = cedula
 164            };
 65
 166            var resumenes = await _reciboService.ObtenerResumenesAsync(periodoId, filtro);
 167            return Ok(ApiResponse<List<ReciboResumenDto>>.SuccessResult(resumenes));
 68        }
 069        catch (Exception ex)
 70        {
 071            _logger.LogError(ex, "Error al obtener resúmenes de recibos para período {PeriodoId}", periodoId);
 072            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 73        }
 374    }
 75
 76    /// <summary>
 77    /// Retorna los datos completos de un recibo individual.
 78    /// GET /api/liquidacion/recibos/{periodoId}/{relacionId}
 79    /// </summary>
 80    [HttpGet("{periodoId}/{relacionId}")]
 81    [RequirePermission(PermisoEnum.VerRecibos)]
 82    public async Task<IActionResult> GetRecibo(long periodoId, long relacionId)
 83    {
 84        try
 85        {
 486            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 487            if (periodo == null)
 188                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 89
 390            if (periodo.Estado != Periodo.EstadoCerrada)
 191                return Conflict(ApiResponse<string>.ErrorResult(
 192                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 93
 294            var recibo = await _reciboService.ObtenerReciboAsync(periodoId, relacionId);
 295            if (recibo == null)
 196                return NotFound(ApiResponse<string>.ErrorResult("No se encontró liquidación para el funcionario en este 
 97
 198            return Ok(ApiResponse<ReciboDto>.SuccessResult(recibo));
 99        }
 0100        catch (Exception ex)
 101        {
 0102            _logger.LogError(ex, "Error al obtener recibo {RelacionId} del período {PeriodoId}", relacionId, periodoId);
 0103            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 104        }
 4105    }
 106
 107    /// <summary>
 108    /// Descarga el recibo de un funcionario como PDF.
 109    /// GET /api/liquidacion/recibos/{periodoId}/{relacionId}/pdf
 110    /// </summary>
 111    [HttpGet("{periodoId}/{relacionId}/pdf")]
 112    [RequirePermission(PermisoEnum.VerRecibos)]
 113    public async Task<IActionResult> GetReciboPdf(long periodoId, long relacionId)
 114    {
 115        try
 116        {
 3117            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3118            if (periodo == null)
 1119                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 120
 2121            if (periodo.Estado != Periodo.EstadoCerrada)
 1122                return Conflict(ApiResponse<string>.ErrorResult(
 1123                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 124
 1125            var recibo = await _reciboService.ObtenerReciboAsync(periodoId, relacionId);
 1126            if (recibo == null)
 1127                return NotFound(ApiResponse<string>.ErrorResult("No se encontró liquidación para el funcionario en este 
 128
 0129            var pdfBytes = await _reciboService.GenerarPdfAsync(periodoId, relacionId);
 130
 0131            await _auditoriaService.LogAuditoriaAsync(
 0132                _currentUser.UserId ?? 0,
 0133                AccionEnum.GenerarReciboIndividual,
 0134                ContextoEnum.Liquidaciones,
 0135                _currentUser.Host,
 0136                entidad: "Recibo",
 0137                entidadId: periodoId,
 0138                detalle: new { periodoId, relacionId });
 139
 0140            var fileName = $"recibo_{periodo.Anio}_{periodo.Mes:D2}_{recibo.Cedula}.pdf";
 0141            return File(pdfBytes, "application/pdf", fileName);
 142        }
 0143        catch (Exception ex)
 144        {
 0145            _logger.LogError(ex, "Error al generar PDF del recibo {RelacionId} del período {PeriodoId}", relacionId, per
 0146            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 147        }
 3148    }
 149
 150    /// <summary>
 151    /// Descarga el lote completo de recibos de un período como ZIP.
 152    /// GET /api/liquidacion/recibos/{periodoId}/lote
 153    /// </summary>
 154    [HttpGet("{periodoId}/lote")]
 155    [RequirePermission(PermisoEnum.VerRecibos)]
 156    public async Task<IActionResult> GetLote(
 157        long periodoId,
 158        [FromQuery] string? regimen = null,
 159        [FromQuery] long? programaId = null,
 160        [FromQuery] long? unidadId = null)
 161    {
 162        try
 163        {
 2164            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 2165            if (periodo == null)
 1166                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 167
 1168            if (periodo.Estado != Periodo.EstadoCerrada)
 1169                return Conflict(ApiResponse<string>.ErrorResult(
 1170                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 171
 0172            var filtro = new ReciboFiltroRequest
 0173            {
 0174                Regimen    = regimen,
 0175                ProgramaId = programaId,
 0176                UnidadId   = unidadId
 0177            };
 178
 0179            var zipStream = await _reciboService.GenerarLoteZipAsync(periodoId, filtro);
 180
 181            // Contar recibos para auditoría (el stream ya fue generado)
 0182            var resumenes = await _reciboService.ObtenerResumenesAsync(periodoId, filtro);
 183
 0184            await _auditoriaService.LogAuditoriaAsync(
 0185                _currentUser.UserId ?? 0,
 0186                AccionEnum.GenerarRecibosLote,
 0187                ContextoEnum.Liquidaciones,
 0188                _currentUser.Host,
 0189                entidad: "Recibo",
 0190                entidadId: periodoId,
 0191                detalle: new { periodoId, cantidad = resumenes.Count });
 192
 0193            var fileName = $"recibos_{periodo.Anio}_{periodo.Mes:D2}.zip";
 0194            return File(zipStream, "application/zip", fileName);
 195        }
 0196        catch (Exception ex)
 197        {
 0198            _logger.LogError(ex, "Error al generar lote de recibos del período {PeriodoId}", periodoId);
 0199            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 200        }
 2201    }
 202}