< Summary

Information
Class: FAU.API.Controllers.ReporteControlController
Assembly: FAU.API
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/ReporteControlController.cs
Line coverage
79%
Covered lines: 92
Uncovered lines: 24
Coverable lines: 116
Total lines: 306
Line coverage: 79.3%
Branch coverage
100%
Covered branches: 40
Total branches: 40
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetEfectivosSuperior()100%4472.72%
GetEfectivosSuperiorCsv()100%4475%
GetEfectivosSubalterno()100%4472.72%
GetEfectivosSubalternoCsv()100%4475%
GetListin()100%4472.72%
GetListinCsv()100%4475%
PostComparativoSgh()100%9876.92%
PostComparativoSghCsv()100%9878.57%
RegistrarAuditoriaAsync(...)100%11100%

File(s)

/home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/ReporteControlController.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/reportes-control")]
 13[Authorize]
 14[Produces("application/json")]
 15public class ReporteControlController : ControllerBase
 16{
 17    private readonly IReporteControlService _service;
 18    private readonly IPeriodoService _periodoService;
 19    private readonly IAuditoriaService _auditoriaService;
 20    private readonly ICurrentUserContext _currentUser;
 21    private readonly ILogger<ReporteControlController> _logger;
 22
 2723    public ReporteControlController(
 2724        IReporteControlService service,
 2725        IPeriodoService periodoService,
 2726        IAuditoriaService auditoriaService,
 2727        ICurrentUserContext currentUser,
 2728        ILogger<ReporteControlController> logger)
 29    {
 2730        _service          = service;
 2731        _periodoService   = periodoService;
 2732        _auditoriaService = auditoriaService;
 2733        _currentUser      = currentUser;
 2734        _logger           = logger;
 2735    }
 36
 37    // ─── Cuadro de Efectivos Superior ───────────────────────────────────────
 38
 39    [HttpGet("{periodoId}/efectivos-superior")]
 40    [RequirePermission(PermisoEnum.VerReportesControl)]
 41    [ProducesResponseType(typeof(ApiResponse<CuadroEfectivosDto>), StatusCodes.Status200OK)]
 42    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 43    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 44    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 45    public async Task<IActionResult> GetEfectivosSuperior(
 46        long periodoId,
 47        [FromQuery] string? programa,
 48        [FromQuery] string? regimen)
 49    {
 50        try
 51        {
 352            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 353            if (periodo == null)
 154                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 255            if (periodo.Estado != Periodo.EstadoCerrada)
 156                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 57
 158            var result = await _service.ObtenerEfectivosSuperiorAsync(periodoId, programa, regimen);
 159            return Ok(ApiResponse<CuadroEfectivosDto>.SuccessResult(result));
 60        }
 061        catch (Exception ex)
 62        {
 063            _logger.LogError(ex, "Error al obtener efectivos superior del período {PeriodoId}", periodoId);
 064            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 65        }
 366    }
 67
 68    [HttpGet("{periodoId}/efectivos-superior/csv")]
 69    [RequirePermission(PermisoEnum.VerReportesControl)]
 70    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 71    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 72    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 73    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 74    public async Task<IActionResult> GetEfectivosSuperiorCsv(
 75        long periodoId,
 76        [FromQuery] string? programa,
 77        [FromQuery] string? regimen)
 78    {
 79        try
 80        {
 381            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 382            if (periodo == null)
 183                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 284            if (periodo.Estado != Periodo.EstadoCerrada)
 185                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 86
 187            var csv = await _service.GenerarCsvEfectivosSuperiorAsync(periodoId, programa, regimen);
 188            await RegistrarAuditoriaAsync(AccionEnum.GenerarReporteEfectivosSuperior, periodoId);
 189            return File(csv, "text/csv", $"efectivos_superior_{periodo.Anio}_{periodo.Mes:D2}.csv");
 90        }
 091        catch (Exception ex)
 92        {
 093            _logger.LogError(ex, "Error al generar CSV efectivos superior del período {PeriodoId}", periodoId);
 094            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 95        }
 396    }
 97
 98    // ─── Cuadro de Efectivos Subalterno ─────────────────────────────────────
 99
 100    [HttpGet("{periodoId}/efectivos-subalterno")]
 101    [RequirePermission(PermisoEnum.VerReportesControl)]
 102    [ProducesResponseType(typeof(ApiResponse<CuadroEfectivosSubalternoDto>), StatusCodes.Status200OK)]
 103    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 104    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 105    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 106    public async Task<IActionResult> GetEfectivosSubalterno(
 107        long periodoId,
 108        [FromQuery] string? programa,
 109        [FromQuery] string? regimen)
 110    {
 111        try
 112        {
 3113            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3114            if (periodo == null)
 1115                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2116            if (periodo.Estado != Periodo.EstadoCerrada)
 1117                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 118
 1119            var result = await _service.ObtenerEfectivosSubalternoAsync(periodoId, programa, regimen);
 1120            return Ok(ApiResponse<CuadroEfectivosSubalternoDto>.SuccessResult(result));
 121        }
 0122        catch (Exception ex)
 123        {
 0124            _logger.LogError(ex, "Error al obtener efectivos subalterno del período {PeriodoId}", periodoId);
 0125            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 126        }
 3127    }
 128
 129    [HttpGet("{periodoId}/efectivos-subalterno/csv")]
 130    [RequirePermission(PermisoEnum.VerReportesControl)]
 131    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 132    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 133    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 134    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 135    public async Task<IActionResult> GetEfectivosSubalternoCsv(
 136        long periodoId,
 137        [FromQuery] string? programa,
 138        [FromQuery] string? regimen)
 139    {
 140        try
 141        {
 3142            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3143            if (periodo == null)
 1144                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2145            if (periodo.Estado != Periodo.EstadoCerrada)
 1146                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 147
 1148            var csv = await _service.GenerarCsvEfectivosSubalternoAsync(periodoId, programa, regimen);
 1149            await RegistrarAuditoriaAsync(AccionEnum.GenerarReporteEfectivosSubalterno, periodoId);
 1150            return File(csv, "text/csv", $"efectivos_subalterno_{periodo.Anio}_{periodo.Mes:D2}.csv");
 151        }
 0152        catch (Exception ex)
 153        {
 0154            _logger.LogError(ex, "Error al generar CSV efectivos subalterno del período {PeriodoId}", periodoId);
 0155            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 156        }
 3157    }
 158
 159    // ─── Listín de Contralor ─────────────────────────────────────────────────
 160
 161    [HttpGet("{periodoId}/listin")]
 162    [RequirePermission(PermisoEnum.VerReportesControl)]
 163    [ProducesResponseType(typeof(ApiResponse<ListinContralorDto>), StatusCodes.Status200OK)]
 164    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 165    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 166    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 167    public async Task<IActionResult> GetListin(
 168        long periodoId,
 169        [FromQuery] int? situacionDesde,
 170        [FromQuery] int? situacionHasta)
 171    {
 172        try
 173        {
 3174            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3175            if (periodo == null)
 1176                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2177            if (periodo.Estado != Periodo.EstadoCerrada)
 1178                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 179
 1180            var result = await _service.ObtenerListinAsync(periodoId, situacionDesde, situacionHasta);
 1181            return Ok(ApiResponse<ListinContralorDto>.SuccessResult(result));
 182        }
 0183        catch (Exception ex)
 184        {
 0185            _logger.LogError(ex, "Error al obtener listín del período {PeriodoId}", periodoId);
 0186            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 187        }
 3188    }
 189
 190    [HttpGet("{periodoId}/listin/csv")]
 191    [RequirePermission(PermisoEnum.VerReportesControl)]
 192    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 193    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 194    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 195    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 196    public async Task<IActionResult> GetListinCsv(
 197        long periodoId,
 198        [FromQuery] int? situacionDesde,
 199        [FromQuery] int? situacionHasta)
 200    {
 201        try
 202        {
 3203            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3204            if (periodo == null)
 1205                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2206            if (periodo.Estado != Periodo.EstadoCerrada)
 1207                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 208
 1209            var csv = await _service.GenerarCsvListinAsync(periodoId, situacionDesde, situacionHasta);
 1210            await RegistrarAuditoriaAsync(AccionEnum.GenerarReporteListin, periodoId);
 1211            return File(csv, "text/csv", $"listin_{periodo.Anio}_{periodo.Mes:D2}.csv");
 212        }
 0213        catch (Exception ex)
 214        {
 0215            _logger.LogError(ex, "Error al generar CSV listín del período {PeriodoId}", periodoId);
 0216            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 217        }
 3218    }
 219
 220    // ─── Comparativo SGH ─────────────────────────────────────────────────────
 221
 222    /// <summary>
 223    /// Compara el padrón SGH enviado en el body contra los funcionarios activos del sistema.
 224    /// POST /api/liquidacion/reportes-control/{periodoId}/sgh/comparativo
 225    /// </summary>
 226    [HttpPost("{periodoId}/sgh/comparativo")]
 227    [RequirePermission(PermisoEnum.VerReportesControl)]
 228    [ProducesResponseType(typeof(ApiResponse<ComparativoSghDto>), StatusCodes.Status200OK)]
 229    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 230    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 231    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 232    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 233    public async Task<IActionResult> PostComparativoSgh(
 234        long periodoId,
 235        [FromBody] List<PadronSghEntradaDto> filas)
 236    {
 237        try
 238        {
 4239            if (filas == null || filas.Count == 0)
 1240                return BadRequest(ApiResponse<string>.ErrorResult("El padrón SGH no puede estar vacío"));
 241
 3242            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3243            if (periodo == null)
 1244                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2245            if (periodo.Estado != Periodo.EstadoCerrada)
 1246                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 247
 1248            var result = await _service.ObtenerComparativoSghAsync(periodoId, filas);
 1249            return Ok(ApiResponse<ComparativoSghDto>.SuccessResult(result));
 250        }
 0251        catch (Exception ex)
 252        {
 0253            _logger.LogError(ex, "Error al obtener comparativo SGH del período {PeriodoId}", periodoId);
 0254            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 255        }
 4256    }
 257
 258    /// <summary>
 259    /// Descarga el comparativo SGH como CSV.
 260    /// POST /api/liquidacion/reportes-control/{periodoId}/sgh/comparativo/csv
 261    /// </summary>
 262    [HttpPost("{periodoId}/sgh/comparativo/csv")]
 263    [RequirePermission(PermisoEnum.VerReportesControl)]
 264    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 265    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 266    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 267    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 268    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 269    public async Task<IActionResult> PostComparativoSghCsv(
 270        long periodoId,
 271        [FromBody] List<PadronSghEntradaDto> filas)
 272    {
 273        try
 274        {
 4275            if (filas == null || filas.Count == 0)
 1276                return BadRequest(ApiResponse<string>.ErrorResult("El padrón SGH no puede estar vacío"));
 277
 3278            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3279            if (periodo == null)
 1280                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2281            if (periodo.Estado != Periodo.EstadoCerrada)
 1282                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 283
 1284            var csv = await _service.GenerarCsvComparativoSghAsync(periodoId, filas);
 1285            await RegistrarAuditoriaAsync(AccionEnum.GenerarReporteComparativoSgh, periodoId);
 1286            return File(csv, "text/csv", $"comparativo_sgh_{periodo.Anio}_{periodo.Mes:D2}.csv");
 287        }
 0288        catch (Exception ex)
 289        {
 0290            _logger.LogError(ex, "Error al generar CSV comparativo SGH del período {PeriodoId}", periodoId);
 0291            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 292        }
 4293    }
 294
 295    // ─── Helper ──────────────────────────────────────────────────────────────
 296
 297    private Task RegistrarAuditoriaAsync(AccionEnum accion, long periodoId) =>
 4298        _auditoriaService.LogAuditoriaAsync(
 4299            _currentUser.UserId ?? 0,
 4300            accion,
 4301            ContextoEnum.Liquidaciones,
 4302            _currentUser.Host,
 4303            entidad: "ReporteControl",
 4304            entidadId: periodoId,
 4305            detalle: new { periodoId });
 306}