< 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: 219
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.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    {
 53        try
 54        {
 355            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 356            if (periodo == null)
 157                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 58
 259            if (periodo.Estado != Periodo.EstadoCerrada)
 160                return Conflict(ApiResponse<string>.ErrorResult(
 161                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 62
 163            var filtro = new ReciboFiltroRequest
 164            {
 165                Regimen    = regimen,
 166                ProgramaId = programaId,
 167                UnidadId   = unidadId,
 168                Cedula     = cedula
 169            };
 70
 171            var resumenes = await _reciboService.ObtenerResumenesAsync(periodoId, filtro);
 172            return Ok(ApiResponse<List<ReciboResumenDto>>.SuccessResult(resumenes));
 73        }
 074        catch (Exception ex)
 75        {
 076            _logger.LogError(ex, "Error al obtener resúmenes de recibos para período {PeriodoId}", periodoId);
 077            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 78        }
 379    }
 80
 81    /// <summary>
 82    /// Retorna los datos completos de un recibo individual.
 83    /// GET /api/liquidacion/recibos/{periodoId}/{relacionId}
 84    /// </summary>
 85    [HttpGet("{periodoId}/{relacionId}")]
 86    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 87    [ProducesResponseType(typeof(ApiResponse<ReciboDto>), StatusCodes.Status200OK)]
 88    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 89    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 90    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 91    public async Task<IActionResult> GetRecibo(long periodoId, long relacionId)
 92    {
 93        try
 94        {
 495            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 496            if (periodo == null)
 197                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 98
 399            if (periodo.Estado != Periodo.EstadoCerrada)
 1100                return Conflict(ApiResponse<string>.ErrorResult(
 1101                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 102
 2103            var recibo = await _reciboService.ObtenerReciboAsync(periodoId, relacionId);
 2104            if (recibo == null)
 1105                return NotFound(ApiResponse<string>.ErrorResult("No se encontró liquidación para el funcionario en este 
 106
 1107            return Ok(ApiResponse<ReciboDto>.SuccessResult(recibo));
 108        }
 0109        catch (Exception ex)
 110        {
 0111            _logger.LogError(ex, "Error al obtener recibo {RelacionId} del período {PeriodoId}", relacionId, periodoId);
 0112            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 113        }
 4114    }
 115
 116    /// <summary>
 117    /// Descarga el recibo de un funcionario como PDF.
 118    /// GET /api/liquidacion/recibos/{periodoId}/{relacionId}/pdf
 119    /// </summary>
 120    [HttpGet("{periodoId}/{relacionId}/pdf")]
 121    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 122    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 123    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 124    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 125    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 126    public async Task<IActionResult> GetReciboPdf(long periodoId, long relacionId)
 127    {
 128        try
 129        {
 3130            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3131            if (periodo == null)
 1132                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 133
 2134            if (periodo.Estado != Periodo.EstadoCerrada)
 1135                return Conflict(ApiResponse<string>.ErrorResult(
 1136                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 137
 1138            var recibo = await _reciboService.ObtenerReciboAsync(periodoId, relacionId);
 1139            if (recibo == null)
 1140                return NotFound(ApiResponse<string>.ErrorResult("No se encontró liquidación para el funcionario en este 
 141
 0142            var pdfBytes = await _reciboService.GenerarPdfAsync(periodoId, relacionId);
 143
 0144            await _auditoriaService.LogAuditoriaAsync(
 0145                _currentUser.UserId ?? 0,
 0146                AccionEnum.GenerarReciboIndividual,
 0147                ContextoEnum.Liquidaciones,
 0148                _currentUser.Host,
 0149                entidad: "Recibo",
 0150                entidadId: periodoId,
 0151                detalle: new { periodoId, relacionId });
 152
 0153            var fileName = $"recibo_{periodo.Anio}_{periodo.Mes:D2}_{recibo.Cedula}.pdf";
 0154            return File(pdfBytes, "application/pdf", fileName);
 155        }
 0156        catch (Exception ex)
 157        {
 0158            _logger.LogError(ex, "Error al generar PDF del recibo {RelacionId} del período {PeriodoId}", relacionId, per
 0159            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 160        }
 3161    }
 162
 163    /// <summary>
 164    /// Descarga el lote completo de recibos de un período como ZIP.
 165    /// GET /api/liquidacion/recibos/{periodoId}/lote
 166    /// </summary>
 167    [HttpGet("{periodoId}/lote")]
 168    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 169    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 170    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 171    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 172    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 173    public async Task<IActionResult> GetLote(
 174        long periodoId,
 175        [FromQuery] string? regimen = null,
 176        [FromQuery] long? programaId = null,
 177        [FromQuery] long? unidadId = null)
 178    {
 179        try
 180        {
 2181            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 2182            if (periodo == null)
 1183                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 184
 1185            if (periodo.Estado != Periodo.EstadoCerrada)
 1186                return Conflict(ApiResponse<string>.ErrorResult(
 1187                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 188
 0189            var filtro = new ReciboFiltroRequest
 0190            {
 0191                Regimen    = regimen,
 0192                ProgramaId = programaId,
 0193                UnidadId   = unidadId
 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.Count });
 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}