< 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: 360
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.Entidades;
 2using FAU.Logica.DTOs;
 3using FAU.Logica.Exceptions;
 4using FAU.Logica.Services;
 5using Microsoft.AspNetCore.Authorization;
 6using Microsoft.AspNetCore.Mvc;
 7
 8namespace FAU.API.Controllers;
 9
 10[ApiController]
 11[Route("api/liquidacion/[controller]")]
 12[Authorize]
 13[Produces("application/json")]
 14public class PeriodosController : ControllerBase
 15{
 16    private readonly IPeriodoService _periodoService;
 17    private readonly ILiquidacionService _liquidacionService;
 18    private readonly ILogger<PeriodosController> _logger;
 19
 1920    public PeriodosController(
 1921        IPeriodoService periodoService,
 1922        ILiquidacionService liquidacionService,
 1923        ILogger<PeriodosController> logger)
 24    {
 1925        _periodoService       = periodoService;
 1926        _liquidacionService   = liquidacionService;
 1927        _logger               = logger;
 1928    }
 29
 30    [HttpPost]
 31    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 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    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 69    [ProducesResponseType(typeof(ApiResponse<List<PeriodoDto>>), StatusCodes.Status200OK)]
 70    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 71    public async Task<IActionResult> GetUltimos([FromQuery] int cantidad = 6)
 72    {
 73        try
 74        {
 075            var periodos = await _periodoService.ObtenerUltimosAsync(Math.Min(cantidad, 12));
 076            return Ok(ApiResponse<List<PeriodoDto>>.SuccessResult(periodos.Select(MapearPeriodo).ToList()));
 77        }
 078        catch (Exception ex)
 79        {
 080            _logger.LogError(ex, "Error al obtener últimos períodos");
 081            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 82        }
 083    }
 84
 85    [HttpGet("activo")]
 86    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 87    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 88    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 89    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 90    public async Task<IActionResult> GetPeriodoActivo()
 91    {
 92        try
 93        {
 294            var periodo = await _periodoService.ObtenerEnCursoAsync();
 295            if (periodo == null)
 96            {
 197                return NotFound(ApiResponse<string>.ErrorResult("No hay un período activo"));
 98            }
 99
 1100            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodo)));
 101        }
 0102        catch (Exception ex)
 103        {
 0104            _logger.LogError(ex, "Error al obtener período activo");
 0105            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 106        }
 2107    }
 108
 109    [HttpGet("{id}")]
 110    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 111    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 112    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 113    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 114    public async Task<IActionResult> GetPeriodo(long id)
 115    {
 116        try
 117        {
 2118            var periodo = await _periodoService.ObtenerPorIdAsync(id);
 2119            if (periodo == null)
 120            {
 1121                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 122            }
 123
 1124            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodo)));
 125        }
 0126        catch (Exception ex)
 127        {
 0128            _logger.LogError(ex, "Error al obtener período {Id}", id);
 0129            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 130        }
 2131    }
 132
 133    [HttpGet("{id}/precondiciones")]
 134    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 135    [ProducesResponseType(typeof(ApiResponse<PrecondicionesDto>), StatusCodes.Status200OK)]
 136    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 137    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 138    public async Task<IActionResult> GetPrecondiciones(long id)
 139    {
 140        try
 141        {
 0142            var precondiciones = await _periodoService.ObtenerPrecondicionesAsync(id);
 0143            if (precondiciones == null)
 0144                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 0145            return Ok(ApiResponse<PrecondicionesDto>.SuccessResult(precondiciones));
 146        }
 0147        catch (Exception ex)
 148        {
 0149            _logger.LogError(ex, "Error al obtener precondiciones del período {Id}", id);
 0150            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 151        }
 0152    }
 153
 154    [HttpGet("{id}/validar-insumos")]
 155    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 156    [ProducesResponseType(typeof(ApiResponse<ValidacionInsumosDto>), StatusCodes.Status200OK)]
 157    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 158    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 159    public async Task<IActionResult> ValidarInsumos(long id)
 160    {
 161        try
 162        {
 0163            var validacion = await _periodoService.ValidarInsumosAsync(id);
 0164            if (validacion == null)
 0165                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 0166            return Ok(ApiResponse<ValidacionInsumosDto>.SuccessResult(validacion));
 167        }
 0168        catch (Exception ex)
 169        {
 0170            _logger.LogError(ex, "Error al validar insumos del período {Id}", id);
 0171            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 172        }
 0173    }
 174
 175    [HttpPost("{id}/cerrar-insumos")]
 176    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 177    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 178    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 179    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 180    [ProducesResponseType(typeof(ValidacionInsumosDto), StatusCodes.Status422UnprocessableEntity)]
 181    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 182    public async Task<IActionResult> CerrarInsumos(long id, [FromBody] CerrarInsumosRequest? request = null)
 183    {
 184        try
 185        {
 4186            var (success, periodo, validacion, error) =
 4187                await _periodoService.CerrarInsumosAsync(id, request?.Confirmaciones);
 188
 4189            if (!success)
 190            {
 3191                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1192                    return NotFound(ApiResponse<string>.ErrorResult(error));
 2193                if (error?.Contains("Solo se puede", StringComparison.OrdinalIgnoreCase) == true)
 1194                    return Conflict(ApiResponse<string>.ErrorResult(error));
 1195                if (validacion != null)
 1196                    return UnprocessableEntity(validacion);
 0197                return UnprocessableEntity(ApiResponse<string>.ErrorResult(error ?? "Error al cerrar insumos"));
 198            }
 199
 1200            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1201            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Insumos cerrados. Período lis
 202        }
 0203        catch (Exception ex)
 204        {
 0205            _logger.LogError(ex, "Error al cerrar insumos del período {Id}", id);
 0206            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 207        }
 4208    }
 209
 210    [HttpPost("{id}/reabrir-insumos")]
 211    [Authorize(Roles = "Administrador,Supervisor")]
 212    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 213    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 214    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 215    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 216    public async Task<IActionResult> ReabrirInsumos(long id)
 217    {
 218        try
 219        {
 2220            var (success, periodo, error) = await _periodoService.ReabrirInsumosAsync(id);
 221
 2222            if (!success)
 223            {
 1224                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 0225                    return NotFound(ApiResponse<string>.ErrorResult(error));
 1226                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al reabrir insumos"));
 227            }
 228
 1229            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1230            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Insumos reabiertos. Período e
 231        }
 0232        catch (Exception ex)
 233        {
 0234            _logger.LogError(ex, "Error al reabrir insumos del período {Id}", id);
 0235            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 236        }
 2237    }
 238
 239    [HttpPost("{id}/reabrir")]
 240    [Authorize(Roles = "Administrador,Supervisor")]
 241    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 242    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 243    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 244    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 245    public async Task<IActionResult> ReabrirDesdeCalculada(long id)
 246    {
 247        try
 248        {
 0249            var (success, periodo, error) = await _periodoService.ReabrirDesdeCalculadaAsync(id);
 250
 0251            if (!success)
 252            {
 0253                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 0254                    return NotFound(ApiResponse<string>.ErrorResult(error));
 0255                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al reabrir período"));
 256            }
 257
 0258            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 0259            return Ok(ApiResponse<PeriodoDto>.SuccessResult(
 0260                MapearPeriodo(periodoFinal!),
 0261                "Período reabierto. Realizá las correcciones y volvé a calcular. El próximo cálculo generará una nueva v
 262        }
 0263        catch (Exception ex)
 264        {
 0265            _logger.LogError(ex, "Error al reabrir período {Id}", id);
 0266            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 267        }
 0268    }
 269
 270    [HttpPost("{id}/calcular")]
 271    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 272    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 273    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 274    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 275    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 276    public async Task<IActionResult> Calcular(long id)
 277    {
 278        try
 279        {
 280            // 1. Marcar período como EN_CALCULO
 3281            var (success, periodo, error) = await _periodoService.IniciarCalculoAsync(id);
 3282            if (!success)
 283            {
 2284                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1285                    return NotFound(ApiResponse<string>.ErrorResult(error));
 1286                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al iniciar cálculo"));
 287            }
 288
 289            // 2. Ejecutar el cálculo — la versión se determina automáticamente (max+1)
 1290            var (exitoCalculo, errorCalculo, version) = await _liquidacionService.GenerarLiquidacionAsync(id);
 291
 292            // 3. Finalizar — éxito o error vuelve a LISTA_CALCULO para reintentar
 1293            await _periodoService.FinalizarCalculoAsync(id, exitoCalculo);
 294
 1295            if (!exitoCalculo)
 0296                return StatusCode(500, ApiResponse<string>.ErrorResult(errorCalculo ?? "Error durante el cálculo"));
 297
 1298            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1299            return Ok(ApiResponse<PeriodoDto>.SuccessResult(
 1300                MapearPeriodo(periodoFinal!),
 1301                $"Liquidación versión {version} generada correctamente."));
 302        }
 0303        catch (Exception ex)
 304        {
 0305            await _periodoService.FinalizarCalculoAsync(id, exitoso: false).ConfigureAwait(false);
 0306            _logger.LogError(ex, "Error al calcular período {Id}", id);
 0307            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 308        }
 3309    }
 310
 311    [HttpPost("{id}/cerrar")]
 312    [Authorize(Roles = "Administrador,Supervisor")]
 313    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 314    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 315    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 316    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 317    public async Task<IActionResult> Cerrar(long id)
 318    {
 319        try
 320        {
 3321            var (success, periodo, error) = await _periodoService.CerrarAsync(id);
 322
 3323            if (!success)
 324            {
 2325                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1326                    return NotFound(ApiResponse<string>.ErrorResult(error));
 1327                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al cerrar período"));
 328            }
 329
 1330            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1331            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Período cerrado definitivamen
 332        }
 0333        catch (Exception ex)
 334        {
 0335            _logger.LogError(ex, "Error al cerrar período {Id}", id);
 0336            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 337        }
 3338    }
 339
 340    private static PeriodoDto MapearPeriodo(Periodo periodo)
 341    {
 7342        return new PeriodoDto
 7343        {
 7344            Id = periodo.Id,
 7345            Anio = periodo.Anio,
 7346            Mes = periodo.Mes,
 7347            Estado = periodo.Estado,
 7348            SnapshotId = periodo.SnapshotId,
 7349            FechaApertura = periodo.FechaApertura,
 7350            UsuarioApertura = periodo.UsuarioAperturaUsername,
 7351            HashSnapshot = periodo.HashSnapshot,
 7352            FechaCierreInsumos = periodo.FechaCierreInsumos,
 7353            UsuarioCierreInsumos = periodo.UsuarioCierreInsumos?.Username,
 7354            FechaCalculo = periodo.FechaCalculo,
 7355            UsuarioCalculo = periodo.UsuarioCalculo?.Username,
 7356            FechaCierre = periodo.FechaCierre,
 7357            UsuarioCierre = periodo.UsuarioCierre?.Username
 7358        };
 359    }
 360}