< Summary

Information
Class: FAU.API.Controllers.PeriodosController
Assembly: FAU.API
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/PeriodosController.cs
Line coverage
57%
Covered lines: 84
Uncovered lines: 61
Coverable lines: 145
Total lines: 353
Line coverage: 57.9%
Branch coverage
61%
Covered branches: 48
Total branches: 78
Branch coverage: 61.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
AbrirPeriodo()80%131069.23%
GetUltimos()0%620%
GetPeriodoActivo()100%2262.5%
GetPeriodo()100%2262.5%
GetPrecondiciones()0%620%
ValidarInsumos()0%620%
CerrarInsumos()75%201675%
ReabrirInsumos()75%11863.63%
ReabrirDesdeCalculada()0%7280%
Calcular()66.66%151272.22%
Cerrar()87.5%9872.72%
MapearPeriodo(...)50%66100%

File(s)

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

#LineLine coverage
 1using FAU.API.Security;
 2using FAU.Entidades;
 3using FAU.Logica.DTOs;
 4using FAU.Logica.Exceptions;
 5using FAU.Logica.Services;
 6using Microsoft.AspNetCore.Authorization;
 7using Microsoft.AspNetCore.Mvc;
 8
 9namespace FAU.API.Controllers;
 10
 11[ApiController]
 12[Route("api/liquidacion/[controller]")]
 13[Authorize(Roles = "Administrador")]
 14[Produces("application/json")]
 15public class PeriodosController : ControllerBase
 16{
 17    private readonly IPeriodoService _periodoService;
 18    private readonly ILiquidacionService _liquidacionService;
 19    private readonly ILogger<PeriodosController> _logger;
 20
 1921    public PeriodosController(
 1922        IPeriodoService periodoService,
 1923        ILiquidacionService liquidacionService,
 1924        ILogger<PeriodosController> logger)
 25    {
 1926        _periodoService       = periodoService;
 1927        _liquidacionService   = liquidacionService;
 1928        _logger               = logger;
 1929    }
 30
 31    [HttpPost]
 32    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status201Created)]
 33    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 34    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 35    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 36    public async Task<IActionResult> AbrirPeriodo([FromBody] AbrirPeriodoRequest request)
 37    {
 38        try
 39        {
 340            if (!ModelState.IsValid)
 41            {
 042                return BadRequest(ApiResponse<string>.ErrorResult("Solicitud de apertura de período inválida"));
 43            }
 44
 345            var (success, periodo, error) = await _periodoService.AbrirPeriodoAsync(request.Anio, request.Mes);
 46
 347            if (!success)
 48            {
 249                if (error?.Contains("Ya existe un período activo", StringComparison.OrdinalIgnoreCase) == true)
 50                {
 151                    return Conflict(ApiResponse<string>.ErrorResult(error));
 52                }
 53
 154                return BadRequest(ApiResponse<string>.ErrorResult(error ?? "Error al abrir el período"));
 55            }
 56
 157            var periodoFinal = await _periodoService.ObtenerPorIdAsync(periodo!.Id);
 158            return CreatedAtAction(nameof(GetPeriodo), new { id = periodo.Id }, ApiResponse<PeriodoDto>.SuccessResult(Ma
 59        }
 060        catch (Exception ex)
 61        {
 062            _logger.LogError(ex, "Error al abrir período");
 063            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 64        }
 365    }
 66
 67    [HttpGet]
 68    [ProducesResponseType(typeof(ApiResponse<List<PeriodoDto>>), StatusCodes.Status200OK)]
 69    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 70    public async Task<IActionResult> GetUltimos([FromQuery] int cantidad = 6)
 71    {
 72        try
 73        {
 074            var periodos = await _periodoService.ObtenerUltimosAsync(Math.Min(cantidad, 12));
 075            return Ok(ApiResponse<List<PeriodoDto>>.SuccessResult(periodos.Select(MapearPeriodo).ToList()));
 76        }
 077        catch (Exception ex)
 78        {
 079            _logger.LogError(ex, "Error al obtener últimos períodos");
 080            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 81        }
 082    }
 83
 84    [HttpGet("activo")]
 85    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 86    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 87    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 88    public async Task<IActionResult> GetPeriodoActivo()
 89    {
 90        try
 91        {
 292            var periodo = await _periodoService.ObtenerEnCursoAsync();
 293            if (periodo == null)
 94            {
 195                return NotFound(ApiResponse<string>.ErrorResult("No hay un período activo"));
 96            }
 97
 198            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodo)));
 99        }
 0100        catch (Exception ex)
 101        {
 0102            _logger.LogError(ex, "Error al obtener período activo");
 0103            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 104        }
 2105    }
 106
 107    [HttpGet("{id}")]
 108    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 109    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 110    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 111    public async Task<IActionResult> GetPeriodo(long id)
 112    {
 113        try
 114        {
 2115            var periodo = await _periodoService.ObtenerPorIdAsync(id);
 2116            if (periodo == null)
 117            {
 1118                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 119            }
 120
 1121            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodo)));
 122        }
 0123        catch (Exception ex)
 124        {
 0125            _logger.LogError(ex, "Error al obtener período {Id}", id);
 0126            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 127        }
 2128    }
 129
 130    [HttpGet("{id}/precondiciones")]
 131    [ProducesResponseType(typeof(ApiResponse<PrecondicionesDto>), StatusCodes.Status200OK)]
 132    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 133    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 134    public async Task<IActionResult> GetPrecondiciones(long id)
 135    {
 136        try
 137        {
 0138            var precondiciones = await _periodoService.ObtenerPrecondicionesAsync(id);
 0139            if (precondiciones == null)
 0140                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 0141            return Ok(ApiResponse<PrecondicionesDto>.SuccessResult(precondiciones));
 142        }
 0143        catch (Exception ex)
 144        {
 0145            _logger.LogError(ex, "Error al obtener precondiciones del período {Id}", id);
 0146            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 147        }
 0148    }
 149
 150    [HttpGet("{id}/validar-insumos")]
 151    [ProducesResponseType(typeof(ApiResponse<ValidacionInsumosDto>), StatusCodes.Status200OK)]
 152    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 153    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 154    public async Task<IActionResult> ValidarInsumos(long id)
 155    {
 156        try
 157        {
 0158            var validacion = await _periodoService.ValidarInsumosAsync(id);
 0159            if (validacion == null)
 0160                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 0161            return Ok(ApiResponse<ValidacionInsumosDto>.SuccessResult(validacion));
 162        }
 0163        catch (Exception ex)
 164        {
 0165            _logger.LogError(ex, "Error al validar insumos del período {Id}", id);
 0166            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 167        }
 0168    }
 169
 170    [HttpPost("{id}/cerrar-insumos")]
 171    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 172    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 173    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 174    [ProducesResponseType(typeof(ValidacionInsumosDto), StatusCodes.Status422UnprocessableEntity)]
 175    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 176    public async Task<IActionResult> CerrarInsumos(long id, [FromBody] CerrarInsumosRequest? request = null)
 177    {
 178        try
 179        {
 4180            var (success, periodo, validacion, error) =
 4181                await _periodoService.CerrarInsumosAsync(id, request?.Confirmaciones);
 182
 4183            if (!success)
 184            {
 3185                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1186                    return NotFound(ApiResponse<string>.ErrorResult(error));
 2187                if (error?.Contains("Solo se puede", StringComparison.OrdinalIgnoreCase) == true)
 1188                    return Conflict(ApiResponse<string>.ErrorResult(error));
 1189                if (validacion != null)
 1190                    return UnprocessableEntity(validacion);
 0191                return UnprocessableEntity(ApiResponse<string>.ErrorResult(error ?? "Error al cerrar insumos"));
 192            }
 193
 1194            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1195            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Insumos cerrados. Período lis
 196        }
 0197        catch (Exception ex)
 198        {
 0199            _logger.LogError(ex, "Error al cerrar insumos del período {Id}", id);
 0200            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 201        }
 4202    }
 203
 204    [HttpPost("{id}/reabrir-insumos")]
 205    [RequirePermission(PermisoEnum.ReabrirPeriodo)]
 206    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 207    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 208    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 209    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 210    public async Task<IActionResult> ReabrirInsumos(long id)
 211    {
 212        try
 213        {
 2214            var (success, periodo, error) = await _periodoService.ReabrirInsumosAsync(id);
 215
 2216            if (!success)
 217            {
 1218                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 0219                    return NotFound(ApiResponse<string>.ErrorResult(error));
 1220                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al reabrir insumos"));
 221            }
 222
 1223            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1224            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Insumos reabiertos. Período e
 225        }
 0226        catch (Exception ex)
 227        {
 0228            _logger.LogError(ex, "Error al reabrir insumos del período {Id}", id);
 0229            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 230        }
 2231    }
 232
 233    [HttpPost("{id}/reabrir")]
 234    [RequirePermission(PermisoEnum.ReabrirPeriodo)]
 235    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 236    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 237    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 238    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 239    public async Task<IActionResult> ReabrirDesdeCalculada(long id)
 240    {
 241        try
 242        {
 0243            var (success, periodo, error) = await _periodoService.ReabrirDesdeCalculadaAsync(id);
 244
 0245            if (!success)
 246            {
 0247                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 0248                    return NotFound(ApiResponse<string>.ErrorResult(error));
 0249                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al reabrir período"));
 250            }
 251
 0252            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 0253            return Ok(ApiResponse<PeriodoDto>.SuccessResult(
 0254                MapearPeriodo(periodoFinal!),
 0255                "Período reabierto. Realizá las correcciones y volvé a calcular. El próximo cálculo generará una nueva v
 256        }
 0257        catch (Exception ex)
 258        {
 0259            _logger.LogError(ex, "Error al reabrir período {Id}", id);
 0260            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 261        }
 0262    }
 263
 264    [HttpPost("{id}/calcular")]
 265    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 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> Calcular(long id)
 270    {
 271        try
 272        {
 273            // 1. Marcar período como EN_CALCULO
 3274            var (success, periodo, error) = await _periodoService.IniciarCalculoAsync(id);
 3275            if (!success)
 276            {
 2277                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1278                    return NotFound(ApiResponse<string>.ErrorResult(error));
 1279                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al iniciar cálculo"));
 280            }
 281
 282            // 2. Ejecutar el cálculo — la versión se determina automáticamente (max+1)
 1283            var (exitoCalculo, errorCalculo, version) = await _liquidacionService.GenerarLiquidacionAsync(id);
 284
 285            // 3. Finalizar — éxito o error vuelve a LISTA_CALCULO para reintentar
 1286            await _periodoService.FinalizarCalculoAsync(id, exitoCalculo);
 287
 1288            if (!exitoCalculo)
 0289                return StatusCode(500, ApiResponse<string>.ErrorResult(errorCalculo ?? "Error durante el cálculo"));
 290
 1291            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1292            return Ok(ApiResponse<PeriodoDto>.SuccessResult(
 1293                MapearPeriodo(periodoFinal!),
 1294                $"Liquidación versión {version} generada correctamente."));
 295        }
 0296        catch (Exception ex)
 297        {
 0298            await _periodoService.FinalizarCalculoAsync(id, exitoso: false).ConfigureAwait(false);
 0299            _logger.LogError(ex, "Error al calcular período {Id}", id);
 0300            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 301        }
 3302    }
 303
 304    [HttpPost("{id}/cerrar")]
 305    [RequirePermission(PermisoEnum.CerrarPeriodo)]
 306    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 307    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 308    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 309    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 310    public async Task<IActionResult> Cerrar(long id)
 311    {
 312        try
 313        {
 3314            var (success, periodo, error) = await _periodoService.CerrarAsync(id);
 315
 3316            if (!success)
 317            {
 2318                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1319                    return NotFound(ApiResponse<string>.ErrorResult(error));
 1320                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al cerrar período"));
 321            }
 322
 1323            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1324            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Período cerrado definitivamen
 325        }
 0326        catch (Exception ex)
 327        {
 0328            _logger.LogError(ex, "Error al cerrar período {Id}", id);
 0329            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 330        }
 3331    }
 332
 333    private static PeriodoDto MapearPeriodo(Periodo periodo)
 334    {
 7335        return new PeriodoDto
 7336        {
 7337            Id = periodo.Id,
 7338            Anio = periodo.Anio,
 7339            Mes = periodo.Mes,
 7340            Estado = periodo.Estado,
 7341            SnapshotId = periodo.SnapshotId,
 7342            FechaApertura = periodo.FechaApertura,
 7343            UsuarioApertura = periodo.UsuarioAperturaUsername,
 7344            HashSnapshot = periodo.HashSnapshot,
 7345            FechaCierreInsumos = periodo.FechaCierreInsumos,
 7346            UsuarioCierreInsumos = periodo.UsuarioCierreInsumos?.Username,
 7347            FechaCalculo = periodo.FechaCalculo,
 7348            UsuarioCalculo = periodo.UsuarioCalculo?.Username,
 7349            FechaCierre = periodo.FechaCierre,
 7350            UsuarioCierre = periodo.UsuarioCierre?.Username
 7351        };
 352    }
 353}