< 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: 220
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.Http;
 7using Microsoft.AspNetCore.Mvc;
 8
 9namespace FAU.API.Controllers;
 10
 11[ApiController]
 12[Route("api/liquidacion/recibos")]
 13[Authorize]
 14[Produces("application/json")]
 15public class RecibosController : ControllerBase
 16{
 17    private readonly IReciboService _reciboService;
 18    private readonly IPeriodoService _periodoService;
 19    private readonly IAuditoriaService _auditoriaService;
 20    private readonly ICurrentUserContext _currentUser;
 21    private readonly ILogger<RecibosController> _logger;
 22
 1223    public RecibosController(
 1224        IReciboService reciboService,
 1225        IPeriodoService periodoService,
 1226        IAuditoriaService auditoriaService,
 1227        ICurrentUserContext currentUser,
 1228        ILogger<RecibosController> logger)
 29    {
 1230        _reciboService    = reciboService;
 1231        _periodoService   = periodoService;
 1232        _auditoriaService = auditoriaService;
 1233        _currentUser      = currentUser;
 1234        _logger           = logger;
 1235    }
 36
 37    /// <summary>
 38    /// Lista los recibos disponibles para un período cerrado, con filtros opcionales.
 39    /// GET /api/liquidacion/recibos/{periodoId}
 40    /// </summary>
 41    [HttpGet("{periodoId}")]
 42    [RequirePermission(PermisoEnum.VerRecibos)]
 43    [ProducesResponseType(typeof(ApiResponse<List<ReciboResumenDto>>), StatusCodes.Status200OK)]
 44    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 45    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 46    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 47    public async Task<IActionResult> GetResumenes(
 48        long periodoId,
 49        [FromQuery] string? regimen = null,
 50        [FromQuery] long? programaId = null,
 51        [FromQuery] long? unidadId = null,
 52        [FromQuery] string? cedula = null)
 53    {
 54        try
 55        {
 356            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 357            if (periodo == null)
 158                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 59
 260            if (periodo.Estado != Periodo.EstadoCerrada)
 161                return Conflict(ApiResponse<string>.ErrorResult(
 162                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 63
 164            var filtro = new ReciboFiltroRequest
 165            {
 166                Regimen    = regimen,
 167                ProgramaId = programaId,
 168                UnidadId   = unidadId,
 169                Cedula     = cedula
 170            };
 71
 172            var resumenes = await _reciboService.ObtenerResumenesAsync(periodoId, filtro);
 173            return Ok(ApiResponse<List<ReciboResumenDto>>.SuccessResult(resumenes));
 74        }
 075        catch (Exception ex)
 76        {
 077            _logger.LogError(ex, "Error al obtener resúmenes de recibos para período {PeriodoId}", periodoId);
 078            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 79        }
 380    }
 81
 82    /// <summary>
 83    /// Retorna los datos completos de un recibo individual.
 84    /// GET /api/liquidacion/recibos/{periodoId}/{relacionId}
 85    /// </summary>
 86    [HttpGet("{periodoId}/{relacionId}")]
 87    [RequirePermission(PermisoEnum.VerRecibos)]
 88    [ProducesResponseType(typeof(ApiResponse<ReciboDto>), StatusCodes.Status200OK)]
 89    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 90    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 91    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 92    public async Task<IActionResult> GetRecibo(long periodoId, long relacionId)
 93    {
 94        try
 95        {
 496            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 497            if (periodo == null)
 198                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 99
 3100            if (periodo.Estado != Periodo.EstadoCerrada)
 1101                return Conflict(ApiResponse<string>.ErrorResult(
 1102                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 103
 2104            var recibo = await _reciboService.ObtenerReciboAsync(periodoId, relacionId);
 2105            if (recibo == null)
 1106                return NotFound(ApiResponse<string>.ErrorResult("No se encontró liquidación para el funcionario en este 
 107
 1108            return Ok(ApiResponse<ReciboDto>.SuccessResult(recibo));
 109        }
 0110        catch (Exception ex)
 111        {
 0112            _logger.LogError(ex, "Error al obtener recibo {RelacionId} del período {PeriodoId}", relacionId, periodoId);
 0113            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 114        }
 4115    }
 116
 117    /// <summary>
 118    /// Descarga el recibo de un funcionario como PDF.
 119    /// GET /api/liquidacion/recibos/{periodoId}/{relacionId}/pdf
 120    /// </summary>
 121    [HttpGet("{periodoId}/{relacionId}/pdf")]
 122    [RequirePermission(PermisoEnum.VerRecibos)]
 123    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 124    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 125    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 126    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 127    public async Task<IActionResult> GetReciboPdf(long periodoId, long relacionId)
 128    {
 129        try
 130        {
 3131            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3132            if (periodo == null)
 1133                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 134
 2135            if (periodo.Estado != Periodo.EstadoCerrada)
 1136                return Conflict(ApiResponse<string>.ErrorResult(
 1137                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 138
 1139            var recibo = await _reciboService.ObtenerReciboAsync(periodoId, relacionId);
 1140            if (recibo == null)
 1141                return NotFound(ApiResponse<string>.ErrorResult("No se encontró liquidación para el funcionario en este 
 142
 0143            var pdfBytes = await _reciboService.GenerarPdfAsync(periodoId, relacionId);
 144
 0145            await _auditoriaService.LogAuditoriaAsync(
 0146                _currentUser.UserId ?? 0,
 0147                AccionEnum.GenerarReciboIndividual,
 0148                ContextoEnum.Liquidaciones,
 0149                _currentUser.Host,
 0150                entidad: "Recibo",
 0151                entidadId: periodoId,
 0152                detalle: new { periodoId, relacionId });
 153
 0154            var fileName = $"recibo_{periodo.Anio}_{periodo.Mes:D2}_{recibo.Cedula}.pdf";
 0155            return File(pdfBytes, "application/pdf", fileName);
 156        }
 0157        catch (Exception ex)
 158        {
 0159            _logger.LogError(ex, "Error al generar PDF del recibo {RelacionId} del período {PeriodoId}", relacionId, per
 0160            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 161        }
 3162    }
 163
 164    /// <summary>
 165    /// Descarga el lote completo de recibos de un período como ZIP.
 166    /// GET /api/liquidacion/recibos/{periodoId}/lote
 167    /// </summary>
 168    [HttpGet("{periodoId}/lote")]
 169    [RequirePermission(PermisoEnum.VerRecibos)]
 170    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 171    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 172    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 173    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 174    public async Task<IActionResult> GetLote(
 175        long periodoId,
 176        [FromQuery] string? regimen = null,
 177        [FromQuery] long? programaId = null,
 178        [FromQuery] long? unidadId = null)
 179    {
 180        try
 181        {
 2182            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 2183            if (periodo == null)
 1184                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 185
 1186            if (periodo.Estado != Periodo.EstadoCerrada)
 1187                return Conflict(ApiResponse<string>.ErrorResult(
 1188                    "El período no está cerrado; los recibos solo están disponibles para períodos en estado CERRADA"));
 189
 0190            var filtro = new ReciboFiltroRequest
 0191            {
 0192                Regimen    = regimen,
 0193                ProgramaId = programaId,
 0194                UnidadId   = unidadId
 0195            };
 196
 0197            var zipStream = await _reciboService.GenerarLoteZipAsync(periodoId, filtro);
 198
 199            // Contar recibos para auditoría (el stream ya fue generado)
 0200            var resumenes = await _reciboService.ObtenerResumenesAsync(periodoId, filtro);
 201
 0202            await _auditoriaService.LogAuditoriaAsync(
 0203                _currentUser.UserId ?? 0,
 0204                AccionEnum.GenerarRecibosLote,
 0205                ContextoEnum.Liquidaciones,
 0206                _currentUser.Host,
 0207                entidad: "Recibo",
 0208                entidadId: periodoId,
 0209                detalle: new { periodoId, cantidad = resumenes.Count });
 210
 0211            var fileName = $"recibos_{periodo.Anio}_{periodo.Mes:D2}.zip";
 0212            return File(zipStream, "application/zip", fileName);
 213        }
 0214        catch (Exception ex)
 215        {
 0216            _logger.LogError(ex, "Error al generar lote de recibos del período {PeriodoId}", periodoId);
 0217            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 218        }
 2219    }
 220}