< Summary

Information
Line coverage
90%
Covered lines: 10
Uncovered lines: 1
Coverable lines: 11
Total lines: 257
Line coverage: 90.9%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Id()100%11100%
get_GradoId()100%11100%
get_GradoCodigo()100%11100%
get_GradoNombre()100%11100%
get_ClasificacionSiif()100%11100%
get_NombreOg()100%11100%
get_OrdenOg()100%11100%
get_Monto()100%11100%
get_GrupoRemuneracion()100%210%
get_VigenteDesde()100%11100%
get_VigenteHasta()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
 20    public RemuneracionesGradoController(
 21        ApplicationDbContext context,
 22        ILogger<RemuneracionesGradoController> logger)
 23    {
 24        _context = context;
 25        _logger = logger;
 26    }
 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        {
 39            var items = await _context.RemuneracionesGrado
 40                .AsNoTracking()
 41                .Include(r => r.Item)
 42                .Include(r => r.Grado)
 43                .Where(r => r.Item.FormulaCalculo == "por_grado" && r.Item.Vigente)
 44                .OrderBy(r => r.Item.OrdenCalculo)
 45                .ThenBy(r => r.Grado.Orden)
 46                .ThenBy(r => r.VigenteDesde)
 47                .Select(r => new TablaSalarialItemDto
 48                {
 49                    Id = r.Id,
 50                    GradoId = r.GradoId,
 51                    GradoCodigo = r.Grado.Codigo,
 52                    GradoNombre = r.Grado.Denominacion,
 53                    ClasificacionSiif = r.Item.ClasificacionSiif!,
 54                    NombreOg = r.Item.Nombre,
 55                    OrdenOg = r.Item.OrdenCalculo,
 56                    Monto = r.Monto,
 57                    GrupoRemuneracion = r.GrupoRemuneracion,
 58                    VigenteDesde = r.VigenteDesde,
 59                    VigenteHasta = r.VigenteHasta
 60                })
 61                .ToListAsync();
 62
 63            return Ok(ApiResponse<IEnumerable<TablaSalarialItemDto>>.SuccessResult(items));
 64        }
 65        catch (Exception ex)
 66        {
 67            _logger.LogError(ex, "Error al obtener tabla salarial completa");
 68            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 69        }
 70    }
 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    {
 79        if (string.IsNullOrWhiteSpace(siif))
 80            return BadRequest(ApiResponse<string>.ErrorResult("El parámetro 'siif' es obligatorio."));
 81
 82        try
 83        {
 84            var items = await _context.RemuneracionesGrado
 85                .AsNoTracking()
 86                .Include(r => r.Item)
 87                .Include(r => r.Grado)
 88                .Where(r => r.Item.ClasificacionSiif == siif)
 89                .OrderBy(r => r.Grado.Orden)
 90                .ThenBy(r => r.VigenteDesde)
 91                .Select(r => new RemuneracionGradoDto
 92                {
 93                    Id = r.Id,
 94                    GradoId = r.GradoId,
 95                    GradoCodigo = r.Grado.Codigo,
 96                    GradoNombre = r.Grado.Denominacion,
 97                    ClasificacionSiif = r.Item.ClasificacionSiif!,
 98                    Monto = r.Monto,
 99                    GrupoRemuneracion = r.GrupoRemuneracion,
 100                    VigenteDesde = r.VigenteDesde,
 101                    VigenteHasta = r.VigenteHasta
 102                })
 103                .ToListAsync();
 104
 105            return Ok(ApiResponse<IEnumerable<RemuneracionGradoDto>>.SuccessResult(items));
 106        }
 107        catch (Exception ex)
 108        {
 109            _logger.LogError(ex, "Error al obtener remuneraciones por grado para SIIF {Siif}", siif);
 110            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 111        }
 112    }
 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        {
 123            var entidad = await _context.RemuneracionesGrado.FindAsync(id);
 124            if (entidad is null)
 125                return NotFound(ApiResponse<string>.ErrorResult("Remuneración no encontrada."));
 126
 127            static DateTime ToUtcDate(DateTime d) =>
 128                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 129
 130            entidad.Monto = request.Monto;
 131            entidad.GrupoRemuneracion = request.GrupoRemuneracion;
 132            entidad.VigenteDesde = ToUtcDate(request.VigenteDesde);
 133            entidad.VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null;
 134
 135            await _context.SaveChangesAsync();
 136
 137            return Ok(ApiResponse<string>.SuccessResult("ok", "Remuneración actualizada correctamente."));
 138        }
 139        catch (Exception ex)
 140        {
 141            _logger.LogError(ex, "Error al actualizar remuneración {Id}", id);
 142            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 143        }
 144    }
 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    {
 153        if (string.IsNullOrWhiteSpace(request.ClasificacionSiif))
 154            return BadRequest(ApiResponse<string>.ErrorResult("ClasificacionSiif es obligatorio."));
 155
 156        try
 157        {
 158            var item = await _context.CatalogoItems
 159                .AsNoTracking()
 160                .Where(c => c.ClasificacionSiif == request.ClasificacionSiif && c.Vigente)
 161                .OrderBy(c => c.OrdenCalculo)
 162                .FirstOrDefaultAsync();
 163
 164            if (item is null)
 165                return BadRequest(ApiResponse<string>.ErrorResult(
 166                    $"No existe ítem de catálogo vigente con clasificación SIIF '{request.ClasificacionSiif}'."));
 167
 168            var gradoExiste = await _context.Grados.AnyAsync(g => g.Id == request.GradoId);
 169            if (!gradoExiste)
 170                return BadRequest(ApiResponse<string>.ErrorResult(
 171                    $"No existe el grado {request.GradoId}."));
 172
 173            static DateTime ToUtcDate(DateTime d) =>
 174                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 175
 176            var entidad = new RemuneracionGrado
 177            {
 178                GradoId = request.GradoId,
 179                ItemId = item.Id,
 180                Monto = request.Monto,
 181                GrupoRemuneracion = request.GrupoRemuneracion,
 182                VigenteDesde = ToUtcDate(request.VigenteDesde),
 183                VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null
 184            };
 185
 186            _context.RemuneracionesGrado.Add(entidad);
 187            await _context.SaveChangesAsync();
 188
 189            var dto = new RemuneracionGradoDto
 190            {
 191                Id = entidad.Id,
 192                GradoId = entidad.GradoId,
 193                ClasificacionSiif = request.ClasificacionSiif,
 194                Monto = entidad.Monto,
 195                GrupoRemuneracion = entidad.GrupoRemuneracion,
 196                VigenteDesde = entidad.VigenteDesde,
 197                VigenteHasta = entidad.VigenteHasta
 198            };
 199
 200            return Ok(ApiResponse<RemuneracionGradoDto>.SuccessResult(dto, "Remuneración por grado registrada correctame
 201        }
 202        catch (Exception ex)
 203        {
 204            _logger.LogError(ex, "Error al registrar remuneración por grado");
 205            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 206        }
 207    }
 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{
 2246    public long Id { get; set; }
 1247    public long GradoId { get; set; }
 2248    public string GradoCodigo { get; set; } = string.Empty;
 2249    public string GradoNombre { get; set; } = string.Empty;
 2250    public string ClasificacionSiif { get; set; } = string.Empty;
 2251    public string NombreOg { get; set; } = string.Empty;
 1252    public int OrdenOg { get; set; }
 2253    public decimal Monto { get; set; }
 0254    public string? GrupoRemuneracion { get; set; }
 1255    public DateTime VigenteDesde { get; set; }
 1256    public DateTime? VigenteHasta { get; set; }
 257}