< 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
59%
Covered lines: 58
Uncovered lines: 39
Coverable lines: 97
Total lines: 219
Line coverage: 59.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%4485.71%
GetRecibo()100%6678.57%
GetReciboPdf()83.33%13641.66%
GetLote()75%10426.92%

File(s)

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

#LineLine coverage
 1using FAU.Entidades;
 2using FAU.Logica.DTOs;
 3using FAU.Logica.Services;
 4using Microsoft.AspNetCore.Authorization;
 5using Microsoft.AspNetCore.Http;
 6using Microsoft.AspNetCore.Mvc;
 7
 8namespace FAU.API.Controllers;
 9
 10[ApiController]
 11[Route("api/liquidacion/recibos")]
 12[Authorize]
 13[Produces("application/json")]
 14public class RecibosController : ControllerBase
 15{
 16    private readonly IReciboService _reciboService;
 17    private readonly IPeriodoService _periodoService;
 18    private readonly IAuditoriaService _auditoriaService;
 19    private readonly ICurrentUserContext _currentUser;
 20    private readonly ILogger<RecibosController> _logger;
 21
 1222    public RecibosController(
 1223        IReciboService reciboService,
 1224        IPeriodoService periodoService,
 1225        IAuditoriaService auditoriaService,
 1226        ICurrentUserContext currentUser,
 1227        ILogger<RecibosController> logger)
 28    {
 1229        _reciboService    = reciboService;
 1230        _periodoService   = periodoService;
 1231        _auditoriaService = auditoriaService;
 1232        _currentUser      = currentUser;
 1233        _logger           = logger;
 1234    }
 35
 36    /// <summary>
 37    /// Lista los recibos disponibles para un período cerrado, con filtros opcionales.
 38    /// GET /api/liquidacion/recibos/{periodoId}
 39    /// </summary>
 40    [HttpGet("{periodoId}")]
 41    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 42    [ProducesResponseType(typeof(ApiResponse<List<ReciboResumenDto>>), StatusCodes.Status200OK)]
 43    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 44    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 45    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 46    public async Task<IActionResult> GetResumenes(
 47        long periodoId,
 48        [FromQuery] string? regimen = null,
 49        [FromQuery] long? programaId = null,
 50        [FromQuery] long? unidadId = null,
 51        [FromQuery] string? cedula = null,
 52        [FromQuery] int? page = null,
 53        [FromQuery] int? pageSize = null)
 54    {
 55        try
 56        {
 357            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 358            if (periodo == null)
 159                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 60
 261            if (periodo.Estado != Periodo.EstadoCerrada)
 162                return Conflict(ApiResponse<string>.ErrorResult(
 163                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 64
 165            var filtro = new ReciboFiltroRequest
 166            {
 167                Regimen    = regimen,
 168                ProgramaId = programaId,
 169                UnidadId   = unidadId,
 170                Cedula     = cedula,
 171                Page       = page,
 172                PageSize   = pageSize
 173            };
 74
 175            var resumenes = await _reciboService.ObtenerResumenesAsync(periodoId, filtro);
 176            return Ok(ApiResponse<object>.SuccessResult(resumenes));
 77        }
 078        catch (Exception ex)
 79        {
 080            _logger.LogError(ex, "Error al obtener resúmenes de recibos para período {PeriodoId}", periodoId);
 081            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 82        }
 383    }
 84
 85    /// <summary>
 86    /// Retorna los datos completos de un recibo individual.
 87    /// GET /api/liquidacion/recibos/{periodoId}/{relacionId}
 88    /// </summary>
 89    [HttpGet("{periodoId}/{relacionId}")]
 90    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 91    [ProducesResponseType(typeof(ApiResponse<ReciboDto>), StatusCodes.Status200OK)]
 92    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 93    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 94    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 95    public async Task<IActionResult> GetRecibo(long periodoId, long relacionId)
 96    {
 97        try
 98        {
 499            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 4100            if (periodo == null)
 1101                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 102
 3103            if (periodo.Estado != Periodo.EstadoCerrada)
 1104                return Conflict(ApiResponse<string>.ErrorResult(
 1105                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 106
 2107            var recibo = await _reciboService.ObtenerReciboAsync(periodoId, relacionId);
 2108            if (recibo == null)
 1109                return NotFound(ApiResponse<string>.ErrorResult("No se encontró liquidación para el funcionario en este 
 110
 1111            return Ok(ApiResponse<ReciboDto>.SuccessResult(recibo));
 112        }
 0113        catch (Exception ex)
 114        {
 0115            _logger.LogError(ex, "Error al obtener recibo {RelacionId} del período {PeriodoId}", relacionId, periodoId);
 0116            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 117        }
 4118    }
 119
 120    /// <summary>
 121    /// Descarga el recibo de un funcionario como PDF.
 122    /// GET /api/liquidacion/recibos/{periodoId}/{relacionId}/pdf
 123    /// </summary>
 124    [HttpGet("{periodoId}/{relacionId}/pdf")]
 125    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 126    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 127    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 128    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 129    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 130    public async Task<IActionResult> GetReciboPdf(long periodoId, long relacionId)
 131    {
 132        try
 133        {
 3134            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3135            if (periodo == null)
 1136                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 137
 2138            if (periodo.Estado != Periodo.EstadoCerrada)
 1139                return Conflict(ApiResponse<string>.ErrorResult(
 1140                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 141
 1142            var recibo = await _reciboService.ObtenerReciboAsync(periodoId, relacionId);
 1143            if (recibo == null)
 1144                return NotFound(ApiResponse<string>.ErrorResult("No se encontró liquidación para el funcionario en este 
 145
 0146            var pdfBytes = await _reciboService.GenerarPdfAsync(periodoId, relacionId);
 147
 0148            await _auditoriaService.LogAuditoriaAsync(
 0149                _currentUser.UserId ?? 0,
 0150                AccionEnum.GenerarReciboIndividual,
 0151                ContextoEnum.Liquidaciones,
 0152                _currentUser.Host,
 0153                entidad: "Recibo",
 0154                entidadId: periodoId,
 0155                detalle: new { periodoId, relacionId });
 156
 0157            var fileName = $"recibo_{periodo.Anio}_{periodo.Mes:D2}_{recibo.Cedula}.pdf";
 0158            return File(pdfBytes, "application/pdf", fileName);
 159        }
 0160        catch (Exception ex)
 161        {
 0162            _logger.LogError(ex, "Error al generar PDF del recibo {RelacionId} del período {PeriodoId}", relacionId, per
 0163            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 164        }
 3165    }
 166
 167    /// <summary>
 168    /// Descarga el lote completo de recibos de un período como ZIP.
 169    /// GET /api/liquidacion/recibos/{periodoId}/lote
 170    /// </summary>
 171    [HttpGet("{periodoId}/lote")]
 172    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 173    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 174    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 175    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 176    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 177    public async Task<IActionResult> GetLote(
 178        long periodoId,
 179        [FromQuery] string? regimen = null)
 180    {
 181        try
 182        {
 2183            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 2184            if (periodo == null)
 1185                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 186
 1187            if (periodo.Estado != Periodo.EstadoCerrada)
 1188                return Conflict(ApiResponse<string>.ErrorResult(
 1189                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 190
 0191            var filtro = new ReciboFiltroRequest
 0192            {
 0193                Regimen = regimen
 0194            };
 195
 0196            var zipStream = await _reciboService.GenerarLoteZipAsync(periodoId, filtro);
 197
 198            // Contar recibos para auditoría (el stream ya fue generado)
 0199            var resumenes = await _reciboService.ObtenerResumenesAsync(periodoId, filtro);
 200
 0201            await _auditoriaService.LogAuditoriaAsync(
 0202                _currentUser.UserId ?? 0,
 0203                AccionEnum.GenerarRecibosLote,
 0204                ContextoEnum.Liquidaciones,
 0205                _currentUser.Host,
 0206                entidad: "Recibo",
 0207                entidadId: periodoId,
 0208                detalle: new { periodoId, cantidad = resumenes.TotalCount });
 209
 0210            var fileName = $"recibos_{periodo.Anio}_{periodo.Mes:D2}.zip";
 0211            return File(zipStream, "application/zip", fileName);
 212        }
 0213        catch (Exception ex)
 214        {
 0215            _logger.LogError(ex, "Error al generar lote de recibos del período {PeriodoId}", periodoId);
 0216            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 217        }
 2218    }
 219}