< Summary

Information
Class: FAU.API.Controllers.RemuneracionesGradoController
Assembly: FAU.API
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/RemuneracionesGradoController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 111
Coverable lines: 111
Total lines: 247
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 14
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetTablaSalarial()100%210%
Get()0%620%
Put()0%2040%
ToUtcDate()100%210%
Post()0%7280%
ToUtcDate()100%210%

File(s)

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

#LineLine coverage
 1using FAU.DataAccess;
 2using FAU.Entidades;
 3using FAU.Logica.DTOs;
 4using Microsoft.AspNetCore.Authorization;
 5using Microsoft.AspNetCore.Http;
 6using Microsoft.AspNetCore.Mvc;
 7using Microsoft.EntityFrameworkCore;
 8
 9namespace FAU.API.Controllers;
 10
 11[ApiController]
 12[Route("api/remuneraciones-grado")]
 13[Authorize(Roles = "Administrador")]
 14[Produces("application/json")]
 15public class RemuneracionesGradoController : ControllerBase
 16{
 17    private readonly ApplicationDbContext _context;
 18    private readonly ILogger<RemuneracionesGradoController> _logger;
 19
 020    public RemuneracionesGradoController(
 021        ApplicationDbContext context,
 022        ILogger<RemuneracionesGradoController> logger)
 23    {
 024        _context = context;
 025        _logger = logger;
 026    }
 27
 28    // GET /api/remuneraciones-grado/tabla-salarial
 29    // Returns ALL remuneraciones_grado entries for 'por_grado' catalog items,
 30    // including the catalog item name and SIIF code for each.
 31    // Used by the admin UI to display the full salary instructivo table.
 32    [HttpGet("tabla-salarial")]
 33    [ProducesResponseType(typeof(ApiResponse<IEnumerable<TablaSalarialItemDto>>), StatusCodes.Status200OK)]
 34    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 35    public async Task<IActionResult> GetTablaSalarial()
 36    {
 37        try
 38        {
 039            var items = await _context.RemuneracionesGrado
 040                .AsNoTracking()
 041                .Include(r => r.Item)
 042                .Include(r => r.Grado)
 043                .Where(r => r.Item.FormulaCalculo == "por_grado" && r.Item.Vigente)
 044                .OrderBy(r => r.Item.OrdenCalculo)
 045                .ThenBy(r => r.Grado.Orden)
 046                .ThenBy(r => r.VigenteDesde)
 047                .Select(r => new TablaSalarialItemDto
 048                {
 049                    Id = r.Id,
 050                    GradoId = r.GradoId,
 051                    GradoCodigo = r.Grado.Codigo,
 052                    GradoNombre = r.Grado.Denominacion,
 053                    ClasificacionSiif = r.Item.ClasificacionSiif!,
 054                    NombreOg = r.Item.Nombre,
 055                    OrdenOg = r.Item.OrdenCalculo,
 056                    Monto = r.Monto,
 057                    VigenteDesde = r.VigenteDesde,
 058                    VigenteHasta = r.VigenteHasta
 059                })
 060                .ToListAsync();
 61
 062            return Ok(ApiResponse<IEnumerable<TablaSalarialItemDto>>.SuccessResult(items));
 63        }
 064        catch (Exception ex)
 65        {
 066            _logger.LogError(ex, "Error al obtener tabla salarial completa");
 067            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 68        }
 069    }
 70
 71    // GET /api/remuneraciones-grado?siif=015.000
 72    [HttpGet]
 73    [ProducesResponseType(typeof(ApiResponse<IEnumerable<RemuneracionGradoDto>>), StatusCodes.Status200OK)]
 74    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 75    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 76    public async Task<IActionResult> Get([FromQuery] string siif)
 77    {
 078        if (string.IsNullOrWhiteSpace(siif))
 079            return BadRequest(ApiResponse<string>.ErrorResult("El parámetro 'siif' es obligatorio."));
 80
 81        try
 82        {
 083            var items = await _context.RemuneracionesGrado
 084                .AsNoTracking()
 085                .Include(r => r.Item)
 086                .Include(r => r.Grado)
 087                .Where(r => r.Item.ClasificacionSiif == siif)
 088                .OrderBy(r => r.Grado.Orden)
 089                .ThenBy(r => r.VigenteDesde)
 090                .Select(r => new RemuneracionGradoDto
 091                {
 092                    Id = r.Id,
 093                    GradoId = r.GradoId,
 094                    GradoCodigo = r.Grado.Codigo,
 095                    GradoNombre = r.Grado.Denominacion,
 096                    ClasificacionSiif = r.Item.ClasificacionSiif!,
 097                    Monto = r.Monto,
 098                    VigenteDesde = r.VigenteDesde,
 099                    VigenteHasta = r.VigenteHasta
 0100                })
 0101                .ToListAsync();
 102
 0103            return Ok(ApiResponse<IEnumerable<RemuneracionGradoDto>>.SuccessResult(items));
 104        }
 0105        catch (Exception ex)
 106        {
 0107            _logger.LogError(ex, "Error al obtener remuneraciones por grado para SIIF {Siif}", siif);
 0108            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 109        }
 0110    }
 111
 112    // PUT /api/remuneraciones-grado/{id}
 113    [HttpPut("{id:long}")]
 114    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status200OK)]
 115    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 116    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 117    public async Task<IActionResult> Put(long id, [FromBody] ActualizarRemuneracionGradoRequest request)
 118    {
 119        try
 120        {
 0121            var entidad = await _context.RemuneracionesGrado.FindAsync(id);
 0122            if (entidad is null)
 0123                return NotFound(ApiResponse<string>.ErrorResult("Remuneración no encontrada."));
 124
 125            static DateTime ToUtcDate(DateTime d) =>
 0126                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 127
 0128            entidad.Monto = request.Monto;
 0129            entidad.VigenteDesde = ToUtcDate(request.VigenteDesde);
 0130            entidad.VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null;
 131
 0132            await _context.SaveChangesAsync();
 133
 0134            return Ok(ApiResponse<string>.SuccessResult("ok", "Remuneración actualizada correctamente."));
 135        }
 0136        catch (Exception ex)
 137        {
 0138            _logger.LogError(ex, "Error al actualizar remuneración {Id}", id);
 0139            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 140        }
 0141    }
 142
 143    // POST /api/remuneraciones-grado
 144    [HttpPost]
 145    [ProducesResponseType(typeof(ApiResponse<RemuneracionGradoDto>), StatusCodes.Status200OK)]
 146    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 147    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 148    public async Task<IActionResult> Post([FromBody] CrearRemuneracionGradoRequest request)
 149    {
 0150        if (string.IsNullOrWhiteSpace(request.ClasificacionSiif))
 0151            return BadRequest(ApiResponse<string>.ErrorResult("ClasificacionSiif es obligatorio."));
 152
 153        try
 154        {
 0155            var item = await _context.CatalogoItems
 0156                .AsNoTracking()
 0157                .Where(c => c.ClasificacionSiif == request.ClasificacionSiif && c.Vigente)
 0158                .OrderBy(c => c.OrdenCalculo)
 0159                .FirstOrDefaultAsync();
 160
 0161            if (item is null)
 0162                return BadRequest(ApiResponse<string>.ErrorResult(
 0163                    $"No existe ítem de catálogo vigente con clasificación SIIF '{request.ClasificacionSiif}'."));
 164
 0165            var gradoExiste = await _context.Grados.AnyAsync(g => g.Id == request.GradoId);
 0166            if (!gradoExiste)
 0167                return BadRequest(ApiResponse<string>.ErrorResult(
 0168                    $"No existe el grado {request.GradoId}."));
 169
 170            static DateTime ToUtcDate(DateTime d) =>
 0171                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 172
 0173            var entidad = new RemuneracionGrado
 0174            {
 0175                GradoId = request.GradoId,
 0176                ItemId = item.Id,
 0177                Monto = request.Monto,
 0178                VigenteDesde = ToUtcDate(request.VigenteDesde),
 0179                VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null
 0180            };
 181
 0182            _context.RemuneracionesGrado.Add(entidad);
 0183            await _context.SaveChangesAsync();
 184
 0185            var dto = new RemuneracionGradoDto
 0186            {
 0187                Id = entidad.Id,
 0188                GradoId = entidad.GradoId,
 0189                ClasificacionSiif = request.ClasificacionSiif,
 0190                Monto = entidad.Monto,
 0191                VigenteDesde = entidad.VigenteDesde,
 0192                VigenteHasta = entidad.VigenteHasta
 0193            };
 194
 0195            return Ok(ApiResponse<RemuneracionGradoDto>.SuccessResult(dto, "Remuneración por grado registrada correctame
 196        }
 0197        catch (Exception ex)
 198        {
 0199            _logger.LogError(ex, "Error al registrar remuneración por grado");
 0200            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 201        }
 0202    }
 203}
 204
 205// ── DTOs ────────────────────────────────────────────────────────────────────
 206
 207public class ActualizarRemuneracionGradoRequest
 208{
 209    public decimal Monto { get; set; }
 210    public DateTime VigenteDesde { get; set; }
 211    public DateTime? VigenteHasta { get; set; }
 212}
 213
 214public class CrearRemuneracionGradoRequest
 215{
 216    public string ClasificacionSiif { get; set; } = string.Empty; // ej. "015.000"
 217    public long GradoId { get; set; }
 218    public decimal Monto { get; set; }
 219    public DateTime VigenteDesde { get; set; }
 220    public DateTime? VigenteHasta { get; set; }
 221}
 222
 223public class RemuneracionGradoDto
 224{
 225    public long Id { get; set; }
 226    public long GradoId { get; set; }
 227    public string GradoCodigo { get; set; } = string.Empty;
 228    public string GradoNombre { get; set; } = string.Empty;
 229    public string ClasificacionSiif { get; set; } = string.Empty;
 230    public decimal Monto { get; set; }
 231    public DateTime VigenteDesde { get; set; }
 232    public DateTime? VigenteHasta { get; set; }
 233}
 234
 235public class TablaSalarialItemDto
 236{
 237    public long Id { get; set; }
 238    public long GradoId { get; set; }
 239    public string GradoCodigo { get; set; } = string.Empty;
 240    public string GradoNombre { get; set; } = string.Empty;
 241    public string ClasificacionSiif { get; set; } = string.Empty;
 242    public string NombreOg { get; set; } = string.Empty;
 243    public int OrdenOg { get; set; }
 244    public decimal Monto { get; set; }
 245    public DateTime VigenteDesde { get; set; }
 246    public DateTime? VigenteHasta { get; set; }
 247}