< Summary

Information
Class: FAU.API.Controllers.CrearRemuneracionGradoRequest
Assembly: FAU.API
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/RemuneracionesGradoController.cs
Line coverage
100%
Covered lines: 5
Uncovered lines: 0
Coverable lines: 5
Total lines: 247
Line coverage: 100%
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_ClasificacionSiif()100%11100%
get_GradoId()100%11100%
get_Monto()100%11100%
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                    VigenteDesde = r.VigenteDesde,
 58                    VigenteHasta = r.VigenteHasta
 59                })
 60                .ToListAsync();
 61
 62            return Ok(ApiResponse<IEnumerable<TablaSalarialItemDto>>.SuccessResult(items));
 63        }
 64        catch (Exception ex)
 65        {
 66            _logger.LogError(ex, "Error al obtener tabla salarial completa");
 67            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 68        }
 69    }
 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    {
 78        if (string.IsNullOrWhiteSpace(siif))
 79            return BadRequest(ApiResponse<string>.ErrorResult("El parámetro 'siif' es obligatorio."));
 80
 81        try
 82        {
 83            var items = await _context.RemuneracionesGrado
 84                .AsNoTracking()
 85                .Include(r => r.Item)
 86                .Include(r => r.Grado)
 87                .Where(r => r.Item.ClasificacionSiif == siif)
 88                .OrderBy(r => r.Grado.Orden)
 89                .ThenBy(r => r.VigenteDesde)
 90                .Select(r => new RemuneracionGradoDto
 91                {
 92                    Id = r.Id,
 93                    GradoId = r.GradoId,
 94                    GradoCodigo = r.Grado.Codigo,
 95                    GradoNombre = r.Grado.Denominacion,
 96                    ClasificacionSiif = r.Item.ClasificacionSiif!,
 97                    Monto = r.Monto,
 98                    VigenteDesde = r.VigenteDesde,
 99                    VigenteHasta = r.VigenteHasta
 100                })
 101                .ToListAsync();
 102
 103            return Ok(ApiResponse<IEnumerable<RemuneracionGradoDto>>.SuccessResult(items));
 104        }
 105        catch (Exception ex)
 106        {
 107            _logger.LogError(ex, "Error al obtener remuneraciones por grado para SIIF {Siif}", siif);
 108            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 109        }
 110    }
 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        {
 121            var entidad = await _context.RemuneracionesGrado.FindAsync(id);
 122            if (entidad is null)
 123                return NotFound(ApiResponse<string>.ErrorResult("Remuneración no encontrada."));
 124
 125            static DateTime ToUtcDate(DateTime d) =>
 126                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 127
 128            entidad.Monto = request.Monto;
 129            entidad.VigenteDesde = ToUtcDate(request.VigenteDesde);
 130            entidad.VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null;
 131
 132            await _context.SaveChangesAsync();
 133
 134            return Ok(ApiResponse<string>.SuccessResult("ok", "Remuneración actualizada correctamente."));
 135        }
 136        catch (Exception ex)
 137        {
 138            _logger.LogError(ex, "Error al actualizar remuneración {Id}", id);
 139            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 140        }
 141    }
 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    {
 150        if (string.IsNullOrWhiteSpace(request.ClasificacionSiif))
 151            return BadRequest(ApiResponse<string>.ErrorResult("ClasificacionSiif es obligatorio."));
 152
 153        try
 154        {
 155            var item = await _context.CatalogoItems
 156                .AsNoTracking()
 157                .Where(c => c.ClasificacionSiif == request.ClasificacionSiif && c.Vigente)
 158                .OrderBy(c => c.OrdenCalculo)
 159                .FirstOrDefaultAsync();
 160
 161            if (item is null)
 162                return BadRequest(ApiResponse<string>.ErrorResult(
 163                    $"No existe ítem de catálogo vigente con clasificación SIIF '{request.ClasificacionSiif}'."));
 164
 165            var gradoExiste = await _context.Grados.AnyAsync(g => g.Id == request.GradoId);
 166            if (!gradoExiste)
 167                return BadRequest(ApiResponse<string>.ErrorResult(
 168                    $"No existe el grado {request.GradoId}."));
 169
 170            static DateTime ToUtcDate(DateTime d) =>
 171                DateTime.SpecifyKind(d.Date, DateTimeKind.Utc);
 172
 173            var entidad = new RemuneracionGrado
 174            {
 175                GradoId = request.GradoId,
 176                ItemId = item.Id,
 177                Monto = request.Monto,
 178                VigenteDesde = ToUtcDate(request.VigenteDesde),
 179                VigenteHasta = request.VigenteHasta.HasValue ? ToUtcDate(request.VigenteHasta.Value) : null
 180            };
 181
 182            _context.RemuneracionesGrado.Add(entidad);
 183            await _context.SaveChangesAsync();
 184
 185            var dto = new RemuneracionGradoDto
 186            {
 187                Id = entidad.Id,
 188                GradoId = entidad.GradoId,
 189                ClasificacionSiif = request.ClasificacionSiif,
 190                Monto = entidad.Monto,
 191                VigenteDesde = entidad.VigenteDesde,
 192                VigenteHasta = entidad.VigenteHasta
 193            };
 194
 195            return Ok(ApiResponse<RemuneracionGradoDto>.SuccessResult(dto, "Remuneración por grado registrada correctame
 196        }
 197        catch (Exception ex)
 198        {
 199            _logger.LogError(ex, "Error al registrar remuneración por grado");
 200            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 201        }
 202    }
 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{
 17216    public string ClasificacionSiif { get; set; } = string.Empty; // ej. "015.000"
 8217    public long GradoId { get; set; }
 5218    public decimal Monto { get; set; }
 5219    public DateTime VigenteDesde { get; set; }
 1220    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}