< 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: 104
Uncovered lines: 12
Coverable lines: 116
Total lines: 257
Line coverage: 89.6%
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%1189.28%
Get()100%2288.88%
Put()75%4476.92%
ToUtcDate()100%11100%
Post()87.5%8892.5%
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                    GrupoRemuneracion = r.GrupoRemuneracion,
 158                    VigenteDesde = r.VigenteDesde,
 159                    VigenteHasta = r.VigenteHasta
 160                })
 161                .ToListAsync();
 62
 163            return Ok(ApiResponse<IEnumerable<TablaSalarialItemDto>>.SuccessResult(items));
 64        }
 065        catch (Exception ex)
 66        {
 067            _logger.LogError(ex, "Error al obtener tabla salarial completa");
 068            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 69        }
 170    }
 71
 72    // GET /api/remuneraciones-grado?siif=015.000
 73    [HttpGet]
 74    [ProducesResponseType(typeof(ApiResponse<IEnumerable<RemuneracionGradoDto>>), StatusCodes.Status200OK)]
 75    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 76    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 77    public async Task<IActionResult> Get([FromQuery] string siif)
 78    {
 279        if (string.IsNullOrWhiteSpace(siif))
 180            return BadRequest(ApiResponse<string>.ErrorResult("El parámetro 'siif' es obligatorio."));
 81
 82        try
 83        {
 184            var items = await _context.RemuneracionesGrado
 185                .AsNoTracking()
 186                .Include(r => r.Item)
 187                .Include(r => r.Grado)
 188                .Where(r => r.Item.ClasificacionSiif == siif)
 189                .OrderBy(r => r.Grado.Orden)
 190                .ThenBy(r => r.VigenteDesde)
 191                .Select(r => new RemuneracionGradoDto
 192                {
 193                    Id = r.Id,
 194                    GradoId = r.GradoId,
 195                    GradoCodigo = r.Grado.Codigo,
 196                    GradoNombre = r.Grado.Denominacion,
 197                    ClasificacionSiif = r.Item.ClasificacionSiif!,
 198                    Monto = r.Monto,
 199                    GrupoRemuneracion = r.GrupoRemuneracion,
 1100                    VigenteDesde = r.VigenteDesde,
 1101                    VigenteHasta = r.VigenteHasta
 1102                })
 1103                .ToListAsync();
 104
 1105            return Ok(ApiResponse<IEnumerable<RemuneracionGradoDto>>.SuccessResult(items));
 106        }
 0107        catch (Exception ex)
 108        {
 0109            _logger.LogError(ex, "Error al obtener remuneraciones por grado para SIIF {Siif}", siif);
 0110            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 111        }
 2112    }
 113
 114    // PUT /api/remuneraciones-grado/{id}
 115    [HttpPut("{id:long}")]
 116    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status200OK)]
 117    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 118    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 119    public async Task<IActionResult> Put(long id, [FromBody] ActualizarRemuneracionGradoRequest request)
 120    {
 121        try
 122        {
 2123            var entidad = await _context.RemuneracionesGrado.FindAsync(id);
 2124            if (entidad is null)
 1125                return NotFound(ApiResponse<string>.ErrorResult("Remuneración no encontrada."));
 126
 127            static DateTime ToUtcDate(DateTime d) =>
 1128                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 129
 1130            entidad.Monto = request.Monto;
 1131            entidad.GrupoRemuneracion = request.GrupoRemuneracion;
 1132            entidad.VigenteDesde = ToUtcDate(request.VigenteDesde);
 1133            entidad.VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null;
 134
 1135            await _context.SaveChangesAsync();
 136
 1137            return Ok(ApiResponse<string>.SuccessResult("ok", "Remuneración actualizada correctamente."));
 138        }
 0139        catch (Exception ex)
 140        {
 0141            _logger.LogError(ex, "Error al actualizar remuneración {Id}", id);
 0142            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 143        }
 2144    }
 145
 146    // POST /api/remuneraciones-grado
 147    [HttpPost]
 148    [ProducesResponseType(typeof(ApiResponse<RemuneracionGradoDto>), StatusCodes.Status200OK)]
 149    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 150    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 151    public async Task<IActionResult> Post([FromBody] CrearRemuneracionGradoRequest request)
 152    {
 4153        if (string.IsNullOrWhiteSpace(request.ClasificacionSiif))
 1154            return BadRequest(ApiResponse<string>.ErrorResult("ClasificacionSiif es obligatorio."));
 155
 156        try
 157        {
 3158            var item = await _context.CatalogoItems
 3159                .AsNoTracking()
 3160                .Where(c => c.ClasificacionSiif == request.ClasificacionSiif && c.Vigente)
 3161                .OrderBy(c => c.OrdenCalculo)
 3162                .FirstOrDefaultAsync();
 163
 3164            if (item is null)
 1165                return BadRequest(ApiResponse<string>.ErrorResult(
 1166                    $"No existe ítem de catálogo vigente con clasificación SIIF '{request.ClasificacionSiif}'."));
 167
 2168            var gradoExiste = await _context.Grados.AnyAsync(g => g.Id == request.GradoId);
 2169            if (!gradoExiste)
 1170                return BadRequest(ApiResponse<string>.ErrorResult(
 1171                    $"No existe el grado {request.GradoId}."));
 172
 173            static DateTime ToUtcDate(DateTime d) =>
 1174                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 175
 1176            var entidad = new RemuneracionGrado
 1177            {
 1178                GradoId = request.GradoId,
 1179                ItemId = item.Id,
 1180                Monto = request.Monto,
 1181                GrupoRemuneracion = request.GrupoRemuneracion,
 1182                VigenteDesde = ToUtcDate(request.VigenteDesde),
 1183                VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null
 1184            };
 185
 1186            _context.RemuneracionesGrado.Add(entidad);
 1187            await _context.SaveChangesAsync();
 188
 1189            var dto = new RemuneracionGradoDto
 1190            {
 1191                Id = entidad.Id,
 1192                GradoId = entidad.GradoId,
 1193                ClasificacionSiif = request.ClasificacionSiif,
 1194                Monto = entidad.Monto,
 1195                GrupoRemuneracion = entidad.GrupoRemuneracion,
 1196                VigenteDesde = entidad.VigenteDesde,
 1197                VigenteHasta = entidad.VigenteHasta
 1198            };
 199
 1200            return Ok(ApiResponse<RemuneracionGradoDto>.SuccessResult(dto, "Remuneración por grado registrada correctame
 201        }
 0202        catch (Exception ex)
 203        {
 0204            _logger.LogError(ex, "Error al registrar remuneración por grado");
 0205            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 206        }
 4207    }
 208}
 209
 210// ── DTOs ────────────────────────────────────────────────────────────────────
 211
 212public class ActualizarRemuneracionGradoRequest
 213{
 214    public decimal Monto { get; set; }
 215    // "COMANDO", "SSGG" o null (valor general). Ver RemuneracionGrado.GrupoRemuneracion.
 216    public string? GrupoRemuneracion { get; set; }
 217    public DateTime VigenteDesde { get; set; }
 218    public DateTime? VigenteHasta { get; set; }
 219}
 220
 221public class CrearRemuneracionGradoRequest
 222{
 223    public string ClasificacionSiif { get; set; } = string.Empty; // ej. "015.000"
 224    public long GradoId { get; set; }
 225    public decimal Monto { get; set; }
 226    public string? GrupoRemuneracion { get; set; }
 227    public DateTime VigenteDesde { get; set; }
 228    public DateTime? VigenteHasta { get; set; }
 229}
 230
 231public class RemuneracionGradoDto
 232{
 233    public long Id { get; set; }
 234    public long GradoId { get; set; }
 235    public string GradoCodigo { get; set; } = string.Empty;
 236    public string GradoNombre { get; set; } = string.Empty;
 237    public string ClasificacionSiif { get; set; } = string.Empty;
 238    public decimal Monto { get; set; }
 239    public string? GrupoRemuneracion { get; set; }
 240    public DateTime VigenteDesde { get; set; }
 241    public DateTime? VigenteHasta { get; set; }
 242}
 243
 244public class TablaSalarialItemDto
 245{
 246    public long Id { get; set; }
 247    public long GradoId { get; set; }
 248    public string GradoCodigo { get; set; } = string.Empty;
 249    public string GradoNombre { get; set; } = string.Empty;
 250    public string ClasificacionSiif { get; set; } = string.Empty;
 251    public string NombreOg { get; set; } = string.Empty;
 252    public int OrdenOg { get; set; }
 253    public decimal Monto { get; set; }
 254    public string? GrupoRemuneracion { get; set; }
 255    public DateTime VigenteDesde { get; set; }
 256    public DateTime? VigenteHasta { get; set; }
 257}