< 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: 305
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.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/reportes-control")]
 12[Authorize]
 13[Produces("application/json")]
 14public class ReporteControlController : ControllerBase
 15{
 16    private readonly IReporteControlService _service;
 17    private readonly IPeriodoService _periodoService;
 18    private readonly IAuditoriaService _auditoriaService;
 19    private readonly ICurrentUserContext _currentUser;
 20    private readonly ILogger<ReporteControlController> _logger;
 21
 2722    public ReporteControlController(
 2723        IReporteControlService service,
 2724        IPeriodoService periodoService,
 2725        IAuditoriaService auditoriaService,
 2726        ICurrentUserContext currentUser,
 2727        ILogger<ReporteControlController> logger)
 28    {
 2729        _service          = service;
 2730        _periodoService   = periodoService;
 2731        _auditoriaService = auditoriaService;
 2732        _currentUser      = currentUser;
 2733        _logger           = logger;
 2734    }
 35
 36    // ─── Cuadro de Efectivos Superior ───────────────────────────────────────
 37
 38    [HttpGet("{periodoId}/efectivos-superior")]
 39    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 40    [ProducesResponseType(typeof(ApiResponse<CuadroEfectivosDto>), StatusCodes.Status200OK)]
 41    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 42    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 43    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 44    public async Task<IActionResult> GetEfectivosSuperior(
 45        long periodoId,
 46        [FromQuery] string? programa,
 47        [FromQuery] string? regimen)
 48    {
 49        try
 50        {
 351            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 352            if (periodo == null)
 153                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 254            if (periodo.Estado != Periodo.EstadoCerrada)
 155                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 56
 157            var result = await _service.ObtenerEfectivosSuperiorAsync(periodoId, programa, regimen);
 158            return Ok(ApiResponse<CuadroEfectivosDto>.SuccessResult(result));
 59        }
 060        catch (Exception ex)
 61        {
 062            _logger.LogError(ex, "Error al obtener efectivos superior del período {PeriodoId}", periodoId);
 063            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 64        }
 365    }
 66
 67    [HttpGet("{periodoId}/efectivos-superior/csv")]
 68    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 69    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 70    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 71    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 72    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 73    public async Task<IActionResult> GetEfectivosSuperiorCsv(
 74        long periodoId,
 75        [FromQuery] string? programa,
 76        [FromQuery] string? regimen)
 77    {
 78        try
 79        {
 380            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 381            if (periodo == null)
 182                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 283            if (periodo.Estado != Periodo.EstadoCerrada)
 184                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 85
 186            var csv = await _service.GenerarCsvEfectivosSuperiorAsync(periodoId, programa, regimen);
 187            await RegistrarAuditoriaAsync(AccionEnum.GenerarReporteEfectivosSuperior, periodoId);
 188            return File(csv, "text/csv", $"efectivos_superior_{periodo.Anio}_{periodo.Mes:D2}.csv");
 89        }
 090        catch (Exception ex)
 91        {
 092            _logger.LogError(ex, "Error al generar CSV efectivos superior del período {PeriodoId}", periodoId);
 093            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 94        }
 395    }
 96
 97    // ─── Cuadro de Efectivos Subalterno ─────────────────────────────────────
 98
 99    [HttpGet("{periodoId}/efectivos-subalterno")]
 100    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 101    [ProducesResponseType(typeof(ApiResponse<CuadroEfectivosSubalternoDto>), StatusCodes.Status200OK)]
 102    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 103    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 104    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 105    public async Task<IActionResult> GetEfectivosSubalterno(
 106        long periodoId,
 107        [FromQuery] string? programa,
 108        [FromQuery] string? regimen)
 109    {
 110        try
 111        {
 3112            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3113            if (periodo == null)
 1114                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2115            if (periodo.Estado != Periodo.EstadoCerrada)
 1116                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 117
 1118            var result = await _service.ObtenerEfectivosSubalternoAsync(periodoId, programa, regimen);
 1119            return Ok(ApiResponse<CuadroEfectivosSubalternoDto>.SuccessResult(result));
 120        }
 0121        catch (Exception ex)
 122        {
 0123            _logger.LogError(ex, "Error al obtener efectivos subalterno del período {PeriodoId}", periodoId);
 0124            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 125        }
 3126    }
 127
 128    [HttpGet("{periodoId}/efectivos-subalterno/csv")]
 129    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 130    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 131    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 132    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 133    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 134    public async Task<IActionResult> GetEfectivosSubalternoCsv(
 135        long periodoId,
 136        [FromQuery] string? programa,
 137        [FromQuery] string? regimen)
 138    {
 139        try
 140        {
 3141            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3142            if (periodo == null)
 1143                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2144            if (periodo.Estado != Periodo.EstadoCerrada)
 1145                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 146
 1147            var csv = await _service.GenerarCsvEfectivosSubalternoAsync(periodoId, programa, regimen);
 1148            await RegistrarAuditoriaAsync(AccionEnum.GenerarReporteEfectivosSubalterno, periodoId);
 1149            return File(csv, "text/csv", $"efectivos_subalterno_{periodo.Anio}_{periodo.Mes:D2}.csv");
 150        }
 0151        catch (Exception ex)
 152        {
 0153            _logger.LogError(ex, "Error al generar CSV efectivos subalterno del período {PeriodoId}", periodoId);
 0154            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 155        }
 3156    }
 157
 158    // ─── Listín de Contralor ─────────────────────────────────────────────────
 159
 160    [HttpGet("{periodoId}/listin")]
 161    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 162    [ProducesResponseType(typeof(ApiResponse<ListinContralorDto>), StatusCodes.Status200OK)]
 163    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 164    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 165    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 166    public async Task<IActionResult> GetListin(
 167        long periodoId,
 168        [FromQuery] int? situacionDesde,
 169        [FromQuery] int? situacionHasta)
 170    {
 171        try
 172        {
 3173            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3174            if (periodo == null)
 1175                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2176            if (periodo.Estado != Periodo.EstadoCerrada)
 1177                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 178
 1179            var result = await _service.ObtenerListinAsync(periodoId, situacionDesde, situacionHasta);
 1180            return Ok(ApiResponse<ListinContralorDto>.SuccessResult(result));
 181        }
 0182        catch (Exception ex)
 183        {
 0184            _logger.LogError(ex, "Error al obtener listín del período {PeriodoId}", periodoId);
 0185            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 186        }
 3187    }
 188
 189    [HttpGet("{periodoId}/listin/csv")]
 190    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 191    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 192    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 193    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 194    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 195    public async Task<IActionResult> GetListinCsv(
 196        long periodoId,
 197        [FromQuery] int? situacionDesde,
 198        [FromQuery] int? situacionHasta)
 199    {
 200        try
 201        {
 3202            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3203            if (periodo == null)
 1204                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2205            if (periodo.Estado != Periodo.EstadoCerrada)
 1206                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 207
 1208            var csv = await _service.GenerarCsvListinAsync(periodoId, situacionDesde, situacionHasta);
 1209            await RegistrarAuditoriaAsync(AccionEnum.GenerarReporteListin, periodoId);
 1210            return File(csv, "text/csv", $"listin_{periodo.Anio}_{periodo.Mes:D2}.csv");
 211        }
 0212        catch (Exception ex)
 213        {
 0214            _logger.LogError(ex, "Error al generar CSV listín del período {PeriodoId}", periodoId);
 0215            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 216        }
 3217    }
 218
 219    // ─── Comparativo SGH ─────────────────────────────────────────────────────
 220
 221    /// <summary>
 222    /// Compara el padrón SGH enviado en el body contra los funcionarios activos del sistema.
 223    /// POST /api/liquidacion/reportes-control/{periodoId}/sgh/comparativo
 224    /// </summary>
 225    [HttpPost("{periodoId}/sgh/comparativo")]
 226    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 227    [ProducesResponseType(typeof(ApiResponse<ComparativoSghDto>), StatusCodes.Status200OK)]
 228    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 229    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 230    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 231    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 232    public async Task<IActionResult> PostComparativoSgh(
 233        long periodoId,
 234        [FromBody] List<PadronSghEntradaDto> filas)
 235    {
 236        try
 237        {
 4238            if (filas == null || filas.Count == 0)
 1239                return BadRequest(ApiResponse<string>.ErrorResult("El padrón SGH no puede estar vacío"));
 240
 3241            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3242            if (periodo == null)
 1243                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2244            if (periodo.Estado != Periodo.EstadoCerrada)
 1245                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 246
 1247            var result = await _service.ObtenerComparativoSghAsync(periodoId, filas);
 1248            return Ok(ApiResponse<ComparativoSghDto>.SuccessResult(result));
 249        }
 0250        catch (Exception ex)
 251        {
 0252            _logger.LogError(ex, "Error al obtener comparativo SGH del período {PeriodoId}", periodoId);
 0253            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 254        }
 4255    }
 256
 257    /// <summary>
 258    /// Descarga el comparativo SGH como CSV.
 259    /// POST /api/liquidacion/reportes-control/{periodoId}/sgh/comparativo/csv
 260    /// </summary>
 261    [HttpPost("{periodoId}/sgh/comparativo/csv")]
 262    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 263    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 264    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 265    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 266    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 267    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 268    public async Task<IActionResult> PostComparativoSghCsv(
 269        long periodoId,
 270        [FromBody] List<PadronSghEntradaDto> filas)
 271    {
 272        try
 273        {
 4274            if (filas == null || filas.Count == 0)
 1275                return BadRequest(ApiResponse<string>.ErrorResult("El padrón SGH no puede estar vacío"));
 276
 3277            var periodo = await _periodoService.ObtenerPorIdAsync(periodoId);
 3278            if (periodo == null)
 1279                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 2280            if (periodo.Estado != Periodo.EstadoCerrada)
 1281                return Conflict(ApiResponse<string>.ErrorResult("El período no está cerrado"));
 282
 1283            var csv = await _service.GenerarCsvComparativoSghAsync(periodoId, filas);
 1284            await RegistrarAuditoriaAsync(AccionEnum.GenerarReporteComparativoSgh, periodoId);
 1285            return File(csv, "text/csv", $"comparativo_sgh_{periodo.Anio}_{periodo.Mes:D2}.csv");
 286        }
 0287        catch (Exception ex)
 288        {
 0289            _logger.LogError(ex, "Error al generar CSV comparativo SGH del período {PeriodoId}", periodoId);
 0290            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 291        }
 4292    }
 293
 294    // ─── Helper ──────────────────────────────────────────────────────────────
 295
 296    private Task RegistrarAuditoriaAsync(AccionEnum accion, long periodoId) =>
 4297        _auditoriaService.LogAuditoriaAsync(
 4298            _currentUser.UserId ?? 0,
 4299            accion,
 4300            ContextoEnum.Liquidaciones,
 4301            _currentUser.Host,
 4302            entidad: "ReporteControl",
 4303            entidadId: periodoId,
 4304            detalle: new { periodoId });
 305}