< Summary

Information
Class: FAU.API.Controllers.Form3100Controller
Assembly: FAU.API
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/Form3100Controller.cs
Line coverage
58%
Covered lines: 118
Uncovered lines: 83
Coverable lines: 201
Total lines: 519
Line coverage: 58.7%
Branch coverage
57%
Covered branches: 32
Total branches: 56
Branch coverage: 57.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Crear()70.83%282480.95%
GetHistorial()100%5466.66%
GetById()100%2262.5%
GetVigente()62.5%13858.33%
GetPersonasACargo()50%11650%
AgregarPersonaACargo()50%2280%
EliminarPersonaACargo()100%1130%
SubirArchivo()0%7280%
DescargarArchivo()0%4225%
MapToDto(...)50%2262.5%

File(s)

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

#LineLine coverage
 1using FAU.Entidades;
 2using FAU.Logica.DTOs;
 3using FAU.Logica.Exceptions;
 4using FAU.Logica.Services;
 5using Microsoft.AspNetCore.Authorization;
 6using Microsoft.AspNetCore.Http;
 7using Microsoft.AspNetCore.Mvc;
 8using System.ComponentModel.DataAnnotations;
 9
 10namespace FAU.API.Controllers;
 11
 12/// <summary>
 13/// Gestión del Formulario 3100 (deducciones IRPF) y personas a cargo
 14/// </summary>
 15[ApiController]
 16[Route("api/form3100")]
 17[Authorize]
 18[Produces("application/json")]
 19public class Form3100Controller : ControllerBase
 20{
 21    private readonly IForm3100Service _form3100Service;
 22    private readonly ICurrentUserContext _currentUser;
 23    private readonly ILogger<Form3100Controller> _logger;
 24
 1625    public Form3100Controller(
 1626        IForm3100Service form3100Service,
 1627        ICurrentUserContext currentUser,
 1628        ILogger<Form3100Controller> logger)
 29    {
 1630        _form3100Service = form3100Service;
 1631        _currentUser = currentUser;
 1632        _logger = logger;
 1633    }
 34
 35    /// <summary>
 36    /// Crear una nueva versión del Form 3100
 37    /// POST /api/form3100
 38    /// </summary>
 39    [HttpPost]
 40    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 41    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status201Created)]
 42    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 43    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 44    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 45    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 46    public async Task<IActionResult> Crear([FromBody] CrearForm3100Request request)
 47    {
 48        try
 49        {
 550            if (string.IsNullOrWhiteSpace(request.CedulaTitular))
 051                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 52
 553            if (request.VigenteDesdeMes < 1 || request.VigenteDesdeMes > 12)
 154                return BadRequest(ApiResponse<string>.ErrorResult("vigenteDesdeMes debe estar entre 1 y 12"));
 55
 456            if (request.VigenteDesdeAnio < 2000 || request.VigenteDesdeAnio > 2100)
 057                return BadRequest(ApiResponse<string>.ErrorResult("vigenteDesdeAnio inválido"));
 58
 459            if (request.CappuCategoria.HasValue && (request.CappuCategoria < 1 || request.CappuCategoria > 10))
 160                return BadRequest(ApiResponse<string>.ErrorResult("cappuCategoria debe estar entre 1 y 10"));
 61
 362            if (!string.IsNullOrEmpty(request.FondoSolidaridad) &&
 363                !new[] { "0.5_BPC", "1_BPC", "2_BPC" }.Contains(request.FondoSolidaridad))
 064                return BadRequest(ApiResponse<string>.ErrorResult("fondoSolidaridad debe ser '0.5_BPC', '1_BPC' o '2_BPC
 65
 366            var form = new Form3100
 367            {
 368                CappuCategoria = request.CappuCategoria,
 369                FondoSolidaridad = request.FondoSolidaridad,
 370                AdicionalFondoSolidaridad = request.AdicionalFondoSolidaridad,
 371                AplicaMinimoNoImponible = request.AplicaMinimoNoImponible,
 372                ConyugeNombre = request.ConyugeNombre,
 373                ConyugeApellido = request.ConyugeApellido,
 374                ConyugeFechaNacimiento = request.ConyugeFechaNacimiento,
 375                ConyugeCedula = request.ConyugeCedula,
 376                ConyugeSexo = request.ConyugeSexo,
 377                ConyugeNacionalidad = request.ConyugeNacionalidad,
 378                VigenteDesde_Mes = (short)request.VigenteDesdeMes,
 379                VigenteDesde_Anio = (short)request.VigenteDesdeAnio
 380            };
 81
 382            var usuarioId = _currentUser.UserId ?? 0;
 383            var creado = await _form3100Service.CrearAsync(request.CedulaTitular, form, usuarioId);
 84
 185            return CreatedAtAction(
 186                nameof(GetById),
 187                new { id = creado.Id },
 188                ApiResponse<object>.SuccessResult(new { id = creado.Id, creado = true }, "Form 3100 creado exitosamente"
 89        }
 190        catch (ArgumentException ex)
 91        {
 192            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 93        }
 094        catch (PeriodoBloqueadoException ex)
 95        {
 096            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 97        }
 198        catch (InvalidOperationException ex)
 99        {
 1100            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 101        }
 0102        catch (Exception ex)
 103        {
 0104            _logger.LogError(ex, "Error al crear Form 3100");
 0105            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 106        }
 5107    }
 108
 109    /// <summary>
 110    /// Obtener historial de versiones del Form 3100 por cédula
 111    /// GET /api/form3100?cedulaTitular=...&vigenteDesdeAnio=...&vigenteDesdeMes=...
 112    /// </summary>
 113    [HttpGet]
 114    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 115    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
 116    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 117    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 118    public async Task<IActionResult> GetHistorial(
 119        [FromQuery] string cedulaTitular,
 120        [FromQuery] int? vigenteDesdeAnio = null,
 121        [FromQuery] int? vigenteDesdeMes = null)
 122    {
 123        try
 124        {
 2125            if (string.IsNullOrWhiteSpace(cedulaTitular))
 1126                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 127
 1128            var historial = await _form3100Service.GetHistorialAsync(cedulaTitular, vigenteDesdeAnio, vigenteDesdeMes);
 129
 1130            var response = historial.Select(MapToDto);
 1131            return Ok(ApiResponse<object>.SuccessResult(response));
 132        }
 0133        catch (Exception ex)
 134        {
 0135            _logger.LogError(ex, "Error al obtener historial Form 3100");
 0136            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 137        }
 2138    }
 139
 140    /// <summary>
 141    /// Obtener versión específica del Form 3100 por id
 142    /// GET /api/form3100/{id}
 143    /// </summary>
 144    [HttpGet("{id}")]
 145    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 146    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
 147    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 148    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 149    public async Task<IActionResult> GetById(long id)
 150    {
 151        try
 152        {
 2153            var form = await _form3100Service.GetByIdAsync(id);
 2154            if (form == null)
 1155                return NotFound(ApiResponse<string>.ErrorResult("Form 3100 no encontrado"));
 156
 1157            return Ok(ApiResponse<object>.SuccessResult(MapToDto(form)));
 158        }
 0159        catch (Exception ex)
 160        {
 0161            _logger.LogError(ex, "Error al obtener Form 3100 {Id}", id);
 0162            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 163        }
 2164    }
 165
 166    /// <summary>
 167    /// Obtener versión vigente del Form 3100 para un período
 168    /// GET /api/form3100/vigente?cedulaTitular=...&anio=...&mes=...
 169    /// </summary>
 170    [HttpGet("vigente")]
 171    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 172    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
 173    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 174    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 175    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 176    public async Task<IActionResult> GetVigente(
 177        [FromQuery] string cedulaTitular,
 178        [FromQuery] int anio,
 179        [FromQuery] int mes)
 180    {
 181        try
 182        {
 2183            if (string.IsNullOrWhiteSpace(cedulaTitular))
 0184                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 2185            if (mes < 1 || mes > 12)
 0186                return BadRequest(ApiResponse<string>.ErrorResult("mes debe estar entre 1 y 12"));
 187
 2188            var form = await _form3100Service.GetVigenteParaPeriodoAsync(cedulaTitular, anio, mes);
 2189            if (form == null)
 1190                return NotFound(ApiResponse<string>.ErrorResult("No se encontró Form 3100 vigente para el período indica
 191
 1192            return Ok(ApiResponse<object>.SuccessResult(MapToDto(form)));
 193        }
 0194        catch (Exception ex)
 195        {
 0196            _logger.LogError(ex, "Error al obtener Form 3100 vigente");
 0197            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 198        }
 2199    }
 200
 201    /// <summary>
 202    /// Listar personas a cargo de una versión del Form 3100
 203    /// GET /api/form3100/{id}/personas-cargo
 204    /// </summary>
 205    [HttpGet("{id}/personas-cargo")]
 206    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 207    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
 208    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 209    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 210    public async Task<IActionResult> GetPersonasACargo(long id)
 211    {
 212        try
 213        {
 1214            var form = await _form3100Service.GetByIdAsync(id);
 1215            if (form == null)
 0216                return NotFound(ApiResponse<string>.ErrorResult("Form 3100 no encontrado"));
 217
 1218            var personas = form.PersonasACargo?.Select(p => new
 1219            {
 1220                id = p.Id, nombre = p.Nombre, apellido = p.Apellido,
 1221                fechaNacimiento = p.FechaNacimiento, cedula = p.Cedula,
 1222                sexo = p.Sexo, porcentajeAtribucion = p.PorcentajeAtribucion,
 1223                relacion = p.Relacion, discapacidad = p.Discapacidad
 1224            });
 1225            return Ok(ApiResponse<object>.SuccessResult(personas ?? Enumerable.Empty<object>()));
 226        }
 0227        catch (Exception ex)
 228        {
 0229            _logger.LogError(ex, "Error al listar personas a cargo de Form 3100 {Id}", id);
 0230            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 231        }
 1232    }
 233
 234    /// <summary>
 235    /// Agregar persona a cargo a una versión del Form 3100
 236    /// POST /api/form3100/{id}/personas-cargo
 237    /// </summary>
 238    [HttpPost("{id}/personas-cargo")]
 239    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 240    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status201Created)]
 241    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 242    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 243    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 244    public async Task<IActionResult> AgregarPersonaACargo(long id, [FromBody] AgregarPersonaACargoRequest request)
 245    {
 246        try
 247        {
 2248            var persona = new Form3100PersonaACargo
 2249            {
 2250                Nombre = request.Nombre,
 2251                Apellido = request.Apellido,
 2252                FechaNacimiento = request.FechaNacimiento.Kind == DateTimeKind.Unspecified
 2253                    ? DateTime.SpecifyKind(request.FechaNacimiento.Date, DateTimeKind.Utc)
 2254                    : request.FechaNacimiento.Date.ToUniversalTime(),
 2255                Cedula = request.Cedula,
 2256                Sexo = request.Sexo,
 2257                PorcentajeAtribucion = (short)request.PorcentajeAtribucion,
 2258                Relacion = request.Relacion,
 2259                Discapacidad = request.Discapacidad
 2260            };
 261
 2262            var creada = await _form3100Service.AgregarPersonaACargoAsync(id, persona);
 263
 1264            return StatusCode(201, ApiResponse<object>.SuccessResult(
 1265                new { id = creada.Id, creado = true },
 1266                "Persona a cargo agregada exitosamente"));
 267        }
 0268        catch (PeriodoBloqueadoException ex)
 269        {
 0270            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 271        }
 1272        catch (ArgumentException ex)
 273        {
 1274            return BadRequest(ApiResponse<string>.ErrorResult(ex.Message));
 275        }
 0276        catch (Exception ex)
 277        {
 0278            _logger.LogError(ex, "Error al agregar persona a cargo a Form 3100 {Id}", id);
 0279            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 280        }
 2281    }
 282
 283    /// <summary>
 284    /// Eliminar persona a cargo de una versión del Form 3100
 285    /// DELETE /api/form3100/{id}/personas-cargo/{personaCargoId}
 286    /// </summary>
 287    [HttpDelete("{id}/personas-cargo/{personaCargoId}")]
 288    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 289    [ProducesResponseType(StatusCodes.Status204NoContent)]
 290    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 291    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 292    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 293    public async Task<IActionResult> EliminarPersonaACargo(long id, long personaCargoId)
 294    {
 295        try
 296        {
 1297            await _form3100Service.EliminarPersonaACargoAsync(id, personaCargoId);
 1298            return NoContent();
 299        }
 0300        catch (PeriodoBloqueadoException ex)
 301        {
 0302            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 303        }
 0304        catch (ArgumentException ex)
 305        {
 0306            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 307        }
 0308        catch (Exception ex)
 309        {
 0310            _logger.LogError(ex, "Error al eliminar persona a cargo {PersonaCargoId} de Form 3100 {Id}", personaCargoId,
 0311            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 312        }
 1313    }
 314
 315    /// <summary>
 316    /// Subir archivo escaneado del Form 3100
 317    /// POST /api/form3100/{id}/archivo (multipart/form-data, campo "file")
 318    /// </summary>
 319    [HttpPost("{id}/archivo")]
 320    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 321    [RequestSizeLimit(20_000_000)] // 20 MB
 322    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
 323    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 324    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 325    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 326    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 327    public async Task<IActionResult> SubirArchivo(long id, IFormFile file)
 328    {
 329        try
 330        {
 0331            if (file == null || file.Length == 0)
 0332                return BadRequest(ApiResponse<string>.ErrorResult("Archivo requerido"));
 333
 0334            var usuarioId = _currentUser.UserId ?? 0;
 0335            await using var stream = file.OpenReadStream();
 0336            var rutaRelativa = await _form3100Service.SubirArchivoAsync(
 0337                id,
 0338                stream,
 0339                file.FileName,
 0340                file.ContentType,
 0341                usuarioId);
 342
 0343            return Ok(ApiResponse<object>.SuccessResult(
 0344                new { rutaRelativa, subido = true },
 0345                "Archivo subido exitosamente"));
 0346        }
 0347        catch (PeriodoBloqueadoException ex)
 348        {
 0349            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 350        }
 0351        catch (ArgumentException ex)
 352        {
 0353            if (ex.Message.Contains("no encontrado", StringComparison.OrdinalIgnoreCase))
 0354                return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 0355            return BadRequest(ApiResponse<string>.ErrorResult(ex.Message));
 356        }
 0357        catch (Exception ex)
 358        {
 0359            _logger.LogError(ex, "Error al subir archivo de Form 3100 {Id}", id);
 0360            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 361        }
 0362    }
 363
 364    /// <summary>
 365    /// Descargar archivo escaneado del Form 3100
 366    /// GET /api/form3100/{id}/archivo
 367    /// </summary>
 368    /// <summary>
 369    /// GET /api/form3100/{id}/archivo?inline=true
 370    /// inline=true  → Content-Disposition: inline  (para mostrar en visor del navegador)
 371    /// inline=false → Content-Disposition: attachment (para forzar descarga, default)
 372    /// </summary>
 373    [HttpGet("{id}/archivo")]
 374    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 375    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 376    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 377    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 378    public async Task<IActionResult> DescargarArchivo(long id, [FromQuery] bool inline = true)
 379    {
 380        try
 381        {
 1382            var (contenido, nombreOriginal, contentType) = await _form3100Service.ObtenerArchivoAsync(id);
 383
 0384            if (inline)
 385            {
 0386                Response.Headers.ContentDisposition =
 0387                    $"inline; filename=\"{nombreOriginal}\"; filename*=UTF-8''{Uri.EscapeDataString(nombreOriginal)}";
 0388                return File(contenido, contentType);
 389            }
 390
 0391            return File(contenido, contentType, nombreOriginal);
 392        }
 0393        catch (ArgumentException ex)
 394        {
 0395            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 396        }
 1397        catch (KeyNotFoundException ex)
 398        {
 1399            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 400        }
 0401        catch (StorageFileNotFoundException ex)
 402        {
 0403            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 404        }
 0405        catch (Exception ex)
 406        {
 0407            _logger.LogError(ex, "Error al descargar archivo de Form 3100 {Id}", id);
 0408            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 409        }
 1410    }
 411
 412    private static object MapToDto(Form3100 f)
 413    {
 2414        return new
 2415        {
 2416            id = f.Id,
 2417            personaId = f.PersonaId,
 2418            cappuCategoria = f.CappuCategoria,
 2419            fondoSolidaridad = f.FondoSolidaridad,
 2420            adicionalFondoSolidaridad = f.AdicionalFondoSolidaridad,
 2421            aplicaMinimoNoImponible = f.AplicaMinimoNoImponible,
 2422            conyugeNombre = f.ConyugeNombre,
 2423            conyugeApellido = f.ConyugeApellido,
 2424            conyugeFechaNacimiento = f.ConyugeFechaNacimiento,
 2425            conyugeCedula = f.ConyugeCedula,
 2426            conyugeSexo = f.ConyugeSexo,
 2427            conyugeNacionalidad = f.ConyugeNacionalidad,
 2428            vigenteDesdeMes = f.VigenteDesde_Mes,
 2429            vigenteDesdeAnio = f.VigenteDesde_Anio,
 2430            documentoId = f.DocumentoId,
 2431            fechaCreacion = f.FechaCreacion,
 2432            usuarioCreacionId = f.UsuarioCreacionId,
 0433            personasACargo = f.PersonasACargo?.Select(p => new
 0434            {
 0435                id = p.Id,
 0436                nombre = p.Nombre,
 0437                apellido = p.Apellido,
 0438                fechaNacimiento = p.FechaNacimiento,
 0439                cedula = p.Cedula,
 0440                sexo = p.Sexo,
 0441                porcentajeAtribucion = p.PorcentajeAtribucion,
 0442                relacion = p.Relacion,
 0443                discapacidad = p.Discapacidad
 0444            })
 2445        };
 446    }
 447}
 448
 449public class CrearForm3100Request
 450{
 451    [Required(ErrorMessage = "cedulaTitular es requerida")]
 452    [MaxLength(12)]
 453    public string CedulaTitular { get; set; } = string.Empty;
 454
 455    public short? CappuCategoria { get; set; }
 456
 457    [MaxLength(10)]
 458    public string? FondoSolidaridad { get; set; }
 459
 460    public bool AdicionalFondoSolidaridad { get; set; }
 461
 462    public bool AplicaMinimoNoImponible { get; set; }
 463
 464    [MaxLength(100)]
 465    public string? ConyugeNombre { get; set; }
 466
 467    [MaxLength(100)]
 468    public string? ConyugeApellido { get; set; }
 469
 470    public DateTime? ConyugeFechaNacimiento { get; set; }
 471
 472    [MaxLength(20)]
 473    public string? ConyugeCedula { get; set; }
 474
 475    [MaxLength(1)]
 476    public string? ConyugeSexo { get; set; }
 477
 478    [MaxLength(100)]
 479    public string? ConyugeNacionalidad { get; set; }
 480
 481    [Required]
 482    [Range(1, 12)]
 483    public int VigenteDesdeMes { get; set; }
 484
 485    [Required]
 486    [Range(2000, 2100)]
 487    public int VigenteDesdeAnio { get; set; }
 488}
 489
 490public class AgregarPersonaACargoRequest
 491{
 492    [Required]
 493    [MaxLength(100)]
 494    public string Nombre { get; set; } = string.Empty;
 495
 496    [Required]
 497    [MaxLength(100)]
 498    public string Apellido { get; set; } = string.Empty;
 499
 500    [Required]
 501    public DateTime FechaNacimiento { get; set; }
 502
 503    [MaxLength(20)]
 504    public string? Cedula { get; set; }
 505
 506    [Required]
 507    [MaxLength(1)]
 508    public string Sexo { get; set; } = string.Empty;
 509
 510    [Required]
 511    [Range(0, 100)]
 512    public int PorcentajeAtribucion { get; set; }
 513
 514    [Required]
 515    [MaxLength(100)]
 516    public string Relacion { get; set; } = string.Empty;
 517
 518    public bool Discapacidad { get; set; }
 519}