< 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
89%
Covered lines: 99
Uncovered lines: 12
Coverable lines: 111
Total lines: 247
Line coverage: 89.1%
Branch coverage
85%
Covered branches: 12
Total branches: 14
Branch coverage: 85.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetTablaSalarial()100%1188.88%
Get()100%2288.46%
Put()75%4475%
ToUtcDate()100%11100%
Post()87.5%8892.1%
ToUtcDate()100%11100%

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
 920    public RemuneracionesGradoController(
 921        ApplicationDbContext context,
 922        ILogger<RemuneracionesGradoController> logger)
 23    {
 924        _context = context;
 925        _logger = logger;
 926    }
 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        {
 139            var items = await _context.RemuneracionesGrado
 140                .AsNoTracking()
 141                .Include(r => r.Item)
 142                .Include(r => r.Grado)
 143                .Where(r => r.Item.FormulaCalculo == "por_grado" && r.Item.Vigente)
 144                .OrderBy(r => r.Item.OrdenCalculo)
 145                .ThenBy(r => r.Grado.Orden)
 146                .ThenBy(r => r.VigenteDesde)
 147                .Select(r => new TablaSalarialItemDto
 148                {
 149                    Id = r.Id,
 150                    GradoId = r.GradoId,
 151                    GradoCodigo = r.Grado.Codigo,
 152                    GradoNombre = r.Grado.Denominacion,
 153                    ClasificacionSiif = r.Item.ClasificacionSiif!,
 154                    NombreOg = r.Item.Nombre,
 155                    OrdenOg = r.Item.OrdenCalculo,
 156                    Monto = r.Monto,
 157                    VigenteDesde = r.VigenteDesde,
 158                    VigenteHasta = r.VigenteHasta
 159                })
 160                .ToListAsync();
 61
 162            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        }
 169    }
 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    {
 278        if (string.IsNullOrWhiteSpace(siif))
 179            return BadRequest(ApiResponse<string>.ErrorResult("El parámetro 'siif' es obligatorio."));
 80
 81        try
 82        {
 183            var items = await _context.RemuneracionesGrado
 184                .AsNoTracking()
 185                .Include(r => r.Item)
 186                .Include(r => r.Grado)
 187                .Where(r => r.Item.ClasificacionSiif == siif)
 188                .OrderBy(r => r.Grado.Orden)
 189                .ThenBy(r => r.VigenteDesde)
 190                .Select(r => new RemuneracionGradoDto
 191                {
 192                    Id = r.Id,
 193                    GradoId = r.GradoId,
 194                    GradoCodigo = r.Grado.Codigo,
 195                    GradoNombre = r.Grado.Denominacion,
 196                    ClasificacionSiif = r.Item.ClasificacionSiif!,
 197                    Monto = r.Monto,
 198                    VigenteDesde = r.VigenteDesde,
 199                    VigenteHasta = r.VigenteHasta
 1100                })
 1101                .ToListAsync();
 102
 1103            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        }
 2110    }
 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        {
 2121            var entidad = await _context.RemuneracionesGrado.FindAsync(id);
 2122            if (entidad is null)
 1123                return NotFound(ApiResponse<string>.ErrorResult("Remuneración no encontrada."));
 124
 125            static DateTime ToUtcDate(DateTime d) =>
 1126                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 127
 1128            entidad.Monto = request.Monto;
 1129            entidad.VigenteDesde = ToUtcDate(request.VigenteDesde);
 1130            entidad.VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null;
 131
 1132            await _context.SaveChangesAsync();
 133
 1134            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        }
 2141    }
 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    {
 4150        if (string.IsNullOrWhiteSpace(request.ClasificacionSiif))
 1151            return BadRequest(ApiResponse<string>.ErrorResult("ClasificacionSiif es obligatorio."));
 152
 153        try
 154        {
 3155            var item = await _context.CatalogoItems
 3156                .AsNoTracking()
 3157                .Where(c => c.ClasificacionSiif == request.ClasificacionSiif && c.Vigente)
 3158                .OrderBy(c => c.OrdenCalculo)
 3159                .FirstOrDefaultAsync();
 160
 3161            if (item is null)
 1162                return BadRequest(ApiResponse<string>.ErrorResult(
 1163                    $"No existe ítem de catálogo vigente con clasificación SIIF '{request.ClasificacionSiif}'."));
 164
 2165            var gradoExiste = await _context.Grados.AnyAsync(g => g.Id == request.GradoId);
 2166            if (!gradoExiste)
 1167                return BadRequest(ApiResponse<string>.ErrorResult(
 1168                    $"No existe el grado {request.GradoId}."));
 169
 170            static DateTime ToUtcDate(DateTime d) =>
 1171                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 172
 1173            var entidad = new RemuneracionGrado
 1174            {
 1175                GradoId = request.GradoId,
 1176                ItemId = item.Id,
 1177                Monto = request.Monto,
 1178                VigenteDesde = ToUtcDate(request.VigenteDesde),
 1179                VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null
 1180            };
 181
 1182            _context.RemuneracionesGrado.Add(entidad);
 1183            await _context.SaveChangesAsync();
 184
 1185            var dto = new RemuneracionGradoDto
 1186            {
 1187                Id = entidad.Id,
 1188                GradoId = entidad.GradoId,
 1189                ClasificacionSiif = request.ClasificacionSiif,
 1190                Monto = entidad.Monto,
 1191                VigenteDesde = entidad.VigenteDesde,
 1192                VigenteHasta = entidad.VigenteHasta
 1193            };
 194
 1195            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        }
 4202    }
 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}