< Summary

Information
Class: FAU.API.Controllers.AgregarPersonaACargoRequest
Assembly: FAU.API
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/Form3100Controller.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 453
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_Nombre()100%11100%
get_Apellido()100%11100%
get_FechaNacimiento()100%11100%
get_Cedula()100%11100%
get_Sexo()100%11100%
get_PorcentajeAtribucion()100%11100%
get_Relacion()100%11100%
get_Discapacidad()100%11100%

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.Services;
 4using Microsoft.AspNetCore.Authorization;
 5using Microsoft.AspNetCore.Mvc;
 6using System.ComponentModel.DataAnnotations;
 7
 8namespace FAU.API.Controllers;
 9
 10/// <summary>
 11/// Gestión del Formulario 3100 (deducciones IRPF) y personas a cargo
 12/// </summary>
 13[ApiController]
 14[Route("api/form3100")]
 15[Authorize]
 16public class Form3100Controller : ControllerBase
 17{
 18    private readonly IForm3100Service _form3100Service;
 19    private readonly ICurrentUserContext _currentUser;
 20    private readonly ILogger<Form3100Controller> _logger;
 21
 22    public Form3100Controller(
 23        IForm3100Service form3100Service,
 24        ICurrentUserContext currentUser,
 25        ILogger<Form3100Controller> logger)
 26    {
 27        _form3100Service = form3100Service;
 28        _currentUser = currentUser;
 29        _logger = logger;
 30    }
 31
 32    /// <summary>
 33    /// Crear una nueva versión del Form 3100
 34    /// POST /api/form3100
 35    /// </summary>
 36    [HttpPost]
 37    [FAU.API.Security.RequirePermission(PermisoEnum.CrearForm3100)]
 38    public async Task<IActionResult> Crear([FromBody] CrearForm3100Request request)
 39    {
 40        try
 41        {
 42            if (string.IsNullOrWhiteSpace(request.CedulaTitular))
 43                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 44
 45            if (request.VigenteDesdeMes < 1 || request.VigenteDesdeMes > 12)
 46                return BadRequest(ApiResponse<string>.ErrorResult("vigenteDesdeMes debe estar entre 1 y 12"));
 47
 48            if (request.VigenteDesdeAnio < 2000 || request.VigenteDesdeAnio > 2100)
 49                return BadRequest(ApiResponse<string>.ErrorResult("vigenteDesdeAnio inválido"));
 50
 51            if (request.CappuCategoria.HasValue && (request.CappuCategoria < 1 || request.CappuCategoria > 10))
 52                return BadRequest(ApiResponse<string>.ErrorResult("cappuCategoria debe estar entre 1 y 10"));
 53
 54            if (!string.IsNullOrEmpty(request.FondoSolidaridad) &&
 55                !new[] { "0.5_BPC", "1_BPC", "2_BPC" }.Contains(request.FondoSolidaridad))
 56                return BadRequest(ApiResponse<string>.ErrorResult("fondoSolidaridad debe ser '0.5_BPC', '1_BPC' o '2_BPC
 57
 58            var form = new Form3100
 59            {
 60                CappuCategoria = request.CappuCategoria,
 61                FondoSolidaridad = request.FondoSolidaridad,
 62                AdicionalFondoSolidaridad = request.AdicionalFondoSolidaridad,
 63                AplicaMinimoNoImponible = request.AplicaMinimoNoImponible,
 64                ConyugeNombre = request.ConyugeNombre,
 65                ConyugeApellido = request.ConyugeApellido,
 66                ConyugeFechaNacimiento = request.ConyugeFechaNacimiento,
 67                ConyugeCedula = request.ConyugeCedula,
 68                ConyugeSexo = request.ConyugeSexo,
 69                ConyugeNacionalidad = request.ConyugeNacionalidad,
 70                VigenteDesde_Mes = (short)request.VigenteDesdeMes,
 71                VigenteDesde_Anio = (short)request.VigenteDesdeAnio
 72            };
 73
 74            var usuarioId = _currentUser.UserId ?? 0;
 75            var creado = await _form3100Service.CrearAsync(request.CedulaTitular, form, usuarioId);
 76
 77            return CreatedAtAction(
 78                nameof(GetById),
 79                new { id = creado.Id },
 80                ApiResponse<object>.SuccessResult(new { id = creado.Id, creado = true }, "Form 3100 creado exitosamente"
 81        }
 82        catch (ArgumentException ex)
 83        {
 84            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 85        }
 86        catch (InvalidOperationException ex)
 87        {
 88            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 89        }
 90        catch (Exception ex)
 91        {
 92            _logger.LogError(ex, "Error al crear Form 3100");
 93            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 94        }
 95    }
 96
 97    /// <summary>
 98    /// Obtener historial de versiones del Form 3100 por cédula
 99    /// GET /api/form3100?cedulaTitular=...&vigenteDesdeAnio=...&vigenteDesdeMes=...
 100    /// </summary>
 101    [HttpGet]
 102    [FAU.API.Security.RequirePermission(PermisoEnum.VerForm3100)]
 103    public async Task<IActionResult> GetHistorial(
 104        [FromQuery] string cedulaTitular,
 105        [FromQuery] int? vigenteDesdeAnio = null,
 106        [FromQuery] int? vigenteDesdeMes = null)
 107    {
 108        try
 109        {
 110            if (string.IsNullOrWhiteSpace(cedulaTitular))
 111                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 112
 113            var historial = await _form3100Service.GetHistorialAsync(cedulaTitular, vigenteDesdeAnio, vigenteDesdeMes);
 114
 115            var response = historial.Select(MapToDto);
 116            return Ok(ApiResponse<object>.SuccessResult(response));
 117        }
 118        catch (Exception ex)
 119        {
 120            _logger.LogError(ex, "Error al obtener historial Form 3100");
 121            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 122        }
 123    }
 124
 125    /// <summary>
 126    /// Obtener versión específica del Form 3100 por id
 127    /// GET /api/form3100/{id}
 128    /// </summary>
 129    [HttpGet("{id}")]
 130    [FAU.API.Security.RequirePermission(PermisoEnum.VerForm3100)]
 131    public async Task<IActionResult> GetById(long id)
 132    {
 133        try
 134        {
 135            var form = await _form3100Service.GetByIdAsync(id);
 136            if (form == null)
 137                return NotFound(ApiResponse<string>.ErrorResult("Form 3100 no encontrado"));
 138
 139            return Ok(ApiResponse<object>.SuccessResult(MapToDto(form)));
 140        }
 141        catch (Exception ex)
 142        {
 143            _logger.LogError(ex, "Error al obtener Form 3100 {Id}", id);
 144            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 145        }
 146    }
 147
 148    /// <summary>
 149    /// Obtener versión vigente del Form 3100 para un período
 150    /// GET /api/form3100/vigente?cedulaTitular=...&anio=...&mes=...
 151    /// </summary>
 152    [HttpGet("vigente")]
 153    [FAU.API.Security.RequirePermission(PermisoEnum.VerForm3100)]
 154    public async Task<IActionResult> GetVigente(
 155        [FromQuery] string cedulaTitular,
 156        [FromQuery] int anio,
 157        [FromQuery] int mes)
 158    {
 159        try
 160        {
 161            if (string.IsNullOrWhiteSpace(cedulaTitular))
 162                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 163            if (mes < 1 || mes > 12)
 164                return BadRequest(ApiResponse<string>.ErrorResult("mes debe estar entre 1 y 12"));
 165
 166            var form = await _form3100Service.GetVigenteParaPeriodoAsync(cedulaTitular, anio, mes);
 167            if (form == null)
 168                return NotFound(ApiResponse<string>.ErrorResult("No se encontró Form 3100 vigente para el período indica
 169
 170            return Ok(ApiResponse<object>.SuccessResult(MapToDto(form)));
 171        }
 172        catch (Exception ex)
 173        {
 174            _logger.LogError(ex, "Error al obtener Form 3100 vigente");
 175            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 176        }
 177    }
 178
 179    /// <summary>
 180    /// Listar personas a cargo de una versión del Form 3100
 181    /// GET /api/form3100/{id}/personas-cargo
 182    /// </summary>
 183    [HttpGet("{id}/personas-cargo")]
 184    [FAU.API.Security.RequirePermission(PermisoEnum.VerForm3100)]
 185    public async Task<IActionResult> GetPersonasACargo(long id)
 186    {
 187        try
 188        {
 189            var form = await _form3100Service.GetByIdAsync(id);
 190            if (form == null)
 191                return NotFound(ApiResponse<string>.ErrorResult("Form 3100 no encontrado"));
 192
 193            var personas = form.PersonasACargo?.Select(p => new
 194            {
 195                id = p.Id, nombre = p.Nombre, apellido = p.Apellido,
 196                fechaNacimiento = p.FechaNacimiento, cedula = p.Cedula,
 197                sexo = p.Sexo, porcentajeAtribucion = p.PorcentajeAtribucion,
 198                relacion = p.Relacion, discapacidad = p.Discapacidad
 199            });
 200            return Ok(ApiResponse<object>.SuccessResult(personas ?? Enumerable.Empty<object>()));
 201        }
 202        catch (Exception ex)
 203        {
 204            _logger.LogError(ex, "Error al listar personas a cargo de Form 3100 {Id}", id);
 205            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 206        }
 207    }
 208
 209    /// <summary>
 210    /// Agregar persona a cargo a una versión del Form 3100
 211    /// POST /api/form3100/{id}/personas-cargo
 212    /// </summary>
 213    [HttpPost("{id}/personas-cargo")]
 214    [FAU.API.Security.RequirePermission(PermisoEnum.CrearForm3100)]
 215    public async Task<IActionResult> AgregarPersonaACargo(long id, [FromBody] AgregarPersonaACargoRequest request)
 216    {
 217        try
 218        {
 219            var persona = new Form3100PersonaACargo
 220            {
 221                Nombre = request.Nombre,
 222                Apellido = request.Apellido,
 223                FechaNacimiento = request.FechaNacimiento.Kind == DateTimeKind.Unspecified
 224                    ? DateTime.SpecifyKind(request.FechaNacimiento.Date, DateTimeKind.Utc)
 225                    : request.FechaNacimiento.Date.ToUniversalTime(),
 226                Cedula = request.Cedula,
 227                Sexo = request.Sexo,
 228                PorcentajeAtribucion = (short)request.PorcentajeAtribucion,
 229                Relacion = request.Relacion,
 230                Discapacidad = request.Discapacidad
 231            };
 232
 233            var creada = await _form3100Service.AgregarPersonaACargoAsync(id, persona);
 234
 235            return StatusCode(201, ApiResponse<object>.SuccessResult(
 236                new { id = creada.Id, creado = true },
 237                "Persona a cargo agregada exitosamente"));
 238        }
 239        catch (ArgumentException ex)
 240        {
 241            return BadRequest(ApiResponse<string>.ErrorResult(ex.Message));
 242        }
 243        catch (Exception ex)
 244        {
 245            _logger.LogError(ex, "Error al agregar persona a cargo a Form 3100 {Id}", id);
 246            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 247        }
 248    }
 249
 250    /// <summary>
 251    /// Eliminar persona a cargo de una versión del Form 3100
 252    /// DELETE /api/form3100/{id}/personas-cargo/{personaCargoId}
 253    /// </summary>
 254    [HttpDelete("{id}/personas-cargo/{personaCargoId}")]
 255    [FAU.API.Security.RequirePermission(PermisoEnum.CrearForm3100)]
 256    public async Task<IActionResult> EliminarPersonaACargo(long id, long personaCargoId)
 257    {
 258        try
 259        {
 260            await _form3100Service.EliminarPersonaACargoAsync(id, personaCargoId);
 261            return NoContent();
 262        }
 263        catch (ArgumentException ex)
 264        {
 265            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 266        }
 267        catch (Exception ex)
 268        {
 269            _logger.LogError(ex, "Error al eliminar persona a cargo {PersonaCargoId} de Form 3100 {Id}", personaCargoId,
 270            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 271        }
 272    }
 273
 274    /// <summary>
 275    /// Subir archivo escaneado del Form 3100
 276    /// POST /api/form3100/{id}/archivo (multipart/form-data, campo "file")
 277    /// </summary>
 278    [HttpPost("{id}/archivo")]
 279    [FAU.API.Security.RequirePermission(PermisoEnum.CrearForm3100)]
 280    [RequestSizeLimit(20_000_000)] // 20 MB
 281    public async Task<IActionResult> SubirArchivo(long id, IFormFile file)
 282    {
 283        try
 284        {
 285            if (file == null || file.Length == 0)
 286                return BadRequest(ApiResponse<string>.ErrorResult("Archivo requerido"));
 287
 288            var usuarioId = _currentUser.UserId ?? 0;
 289            await using var stream = file.OpenReadStream();
 290            var rutaRelativa = await _form3100Service.SubirArchivoAsync(
 291                id,
 292                stream,
 293                file.FileName,
 294                file.ContentType,
 295                usuarioId);
 296
 297            return Ok(ApiResponse<object>.SuccessResult(
 298                new { rutaRelativa, subido = true },
 299                "Archivo subido exitosamente"));
 300        }
 301        catch (ArgumentException ex)
 302        {
 303            if (ex.Message.Contains("no encontrado", StringComparison.OrdinalIgnoreCase))
 304                return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 305            return BadRequest(ApiResponse<string>.ErrorResult(ex.Message));
 306        }
 307        catch (Exception ex)
 308        {
 309            _logger.LogError(ex, "Error al subir archivo de Form 3100 {Id}", id);
 310            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 311        }
 312    }
 313
 314    /// <summary>
 315    /// Descargar archivo escaneado del Form 3100
 316    /// GET /api/form3100/{id}/archivo
 317    /// </summary>
 318    [HttpGet("{id}/archivo")]
 319    [FAU.API.Security.RequirePermission(PermisoEnum.VerForm3100)]
 320    public async Task<IActionResult> DescargarArchivo(long id)
 321    {
 322        try
 323        {
 324            var (contenido, nombreOriginal, contentType) = await _form3100Service.ObtenerArchivoAsync(id);
 325            return File(contenido, contentType, nombreOriginal);
 326        }
 327        catch (ArgumentException ex)
 328        {
 329            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 330        }
 331        catch (KeyNotFoundException ex)
 332        {
 333            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 334        }
 335        catch (StorageFileNotFoundException ex)
 336        {
 337            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 338        }
 339        catch (Exception ex)
 340        {
 341            _logger.LogError(ex, "Error al descargar archivo de Form 3100 {Id}", id);
 342            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 343        }
 344    }
 345
 346    private static object MapToDto(Form3100 f)
 347    {
 348        return new
 349        {
 350            id = f.Id,
 351            personaId = f.PersonaId,
 352            cappuCategoria = f.CappuCategoria,
 353            fondoSolidaridad = f.FondoSolidaridad,
 354            adicionalFondoSolidaridad = f.AdicionalFondoSolidaridad,
 355            aplicaMinimoNoImponible = f.AplicaMinimoNoImponible,
 356            conyugeNombre = f.ConyugeNombre,
 357            conyugeApellido = f.ConyugeApellido,
 358            conyugeFechaNacimiento = f.ConyugeFechaNacimiento,
 359            conyugeCedula = f.ConyugeCedula,
 360            conyugeSexo = f.ConyugeSexo,
 361            conyugeNacionalidad = f.ConyugeNacionalidad,
 362            vigenteDesdeMes = f.VigenteDesde_Mes,
 363            vigenteDesdeAnio = f.VigenteDesde_Anio,
 364            documentoId = f.DocumentoId,
 365            fechaCreacion = f.FechaCreacion,
 366            usuarioCreacionId = f.UsuarioCreacionId,
 367            personasACargo = f.PersonasACargo?.Select(p => new
 368            {
 369                id = p.Id,
 370                nombre = p.Nombre,
 371                apellido = p.Apellido,
 372                fechaNacimiento = p.FechaNacimiento,
 373                cedula = p.Cedula,
 374                sexo = p.Sexo,
 375                porcentajeAtribucion = p.PorcentajeAtribucion,
 376                relacion = p.Relacion,
 377                discapacidad = p.Discapacidad
 378            })
 379        };
 380    }
 381}
 382
 383public class CrearForm3100Request
 384{
 385    [Required(ErrorMessage = "cedulaTitular es requerida")]
 386    [MaxLength(12)]
 387    public string CedulaTitular { get; set; } = string.Empty;
 388
 389    public short? CappuCategoria { get; set; }
 390
 391    [MaxLength(10)]
 392    public string? FondoSolidaridad { get; set; }
 393
 394    public bool AdicionalFondoSolidaridad { get; set; }
 395
 396    public bool AplicaMinimoNoImponible { get; set; }
 397
 398    [MaxLength(100)]
 399    public string? ConyugeNombre { get; set; }
 400
 401    [MaxLength(100)]
 402    public string? ConyugeApellido { get; set; }
 403
 404    public DateTime? ConyugeFechaNacimiento { get; set; }
 405
 406    [MaxLength(20)]
 407    public string? ConyugeCedula { get; set; }
 408
 409    [MaxLength(1)]
 410    public string? ConyugeSexo { get; set; }
 411
 412    [MaxLength(100)]
 413    public string? ConyugeNacionalidad { get; set; }
 414
 415    [Required]
 416    [Range(1, 12)]
 417    public int VigenteDesdeMes { get; set; }
 418
 419    [Required]
 420    [Range(2000, 2100)]
 421    public int VigenteDesdeAnio { get; set; }
 422}
 423
 424public class AgregarPersonaACargoRequest
 425{
 426    [Required]
 427    [MaxLength(100)]
 6428    public string Nombre { get; set; } = string.Empty;
 429
 430    [Required]
 431    [MaxLength(100)]
 6432    public string Apellido { get; set; } = string.Empty;
 433
 434    [Required]
 6435    public DateTime FechaNacimiento { get; set; }
 436
 437    [MaxLength(20)]
 2438    public string? Cedula { get; set; }
 439
 440    [Required]
 441    [MaxLength(1)]
 6442    public string Sexo { get; set; } = string.Empty;
 443
 444    [Required]
 445    [Range(0, 100)]
 4446    public int PorcentajeAtribucion { get; set; }
 447
 448    [Required]
 449    [MaxLength(100)]
 6450    public string Relacion { get; set; } = string.Empty;
 451
 2452    public bool Discapacidad { get; set; }
 453}