< 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: 466
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    /// <summary>
 319    /// GET /api/form3100/{id}/archivo?inline=true
 320    /// inline=true  → Content-Disposition: inline  (para mostrar en visor del navegador)
 321    /// inline=false → Content-Disposition: attachment (para forzar descarga, default)
 322    /// </summary>
 323    [HttpGet("{id}/archivo")]
 324    [FAU.API.Security.RequirePermission(PermisoEnum.VerForm3100)]
 325    public async Task<IActionResult> DescargarArchivo(long id, [FromQuery] bool inline = true)
 326    {
 327        try
 328        {
 329            var (contenido, nombreOriginal, contentType) = await _form3100Service.ObtenerArchivoAsync(id);
 330
 331            if (inline)
 332            {
 333                Response.Headers.ContentDisposition =
 334                    $"inline; filename=\"{nombreOriginal}\"; filename*=UTF-8''{Uri.EscapeDataString(nombreOriginal)}";
 335                return File(contenido, contentType);
 336            }
 337
 338            return File(contenido, contentType, nombreOriginal);
 339        }
 340        catch (ArgumentException ex)
 341        {
 342            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 343        }
 344        catch (KeyNotFoundException ex)
 345        {
 346            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 347        }
 348        catch (StorageFileNotFoundException ex)
 349        {
 350            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 351        }
 352        catch (Exception ex)
 353        {
 354            _logger.LogError(ex, "Error al descargar archivo de Form 3100 {Id}", id);
 355            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 356        }
 357    }
 358
 359    private static object MapToDto(Form3100 f)
 360    {
 361        return new
 362        {
 363            id = f.Id,
 364            personaId = f.PersonaId,
 365            cappuCategoria = f.CappuCategoria,
 366            fondoSolidaridad = f.FondoSolidaridad,
 367            adicionalFondoSolidaridad = f.AdicionalFondoSolidaridad,
 368            aplicaMinimoNoImponible = f.AplicaMinimoNoImponible,
 369            conyugeNombre = f.ConyugeNombre,
 370            conyugeApellido = f.ConyugeApellido,
 371            conyugeFechaNacimiento = f.ConyugeFechaNacimiento,
 372            conyugeCedula = f.ConyugeCedula,
 373            conyugeSexo = f.ConyugeSexo,
 374            conyugeNacionalidad = f.ConyugeNacionalidad,
 375            vigenteDesdeMes = f.VigenteDesde_Mes,
 376            vigenteDesdeAnio = f.VigenteDesde_Anio,
 377            documentoId = f.DocumentoId,
 378            fechaCreacion = f.FechaCreacion,
 379            usuarioCreacionId = f.UsuarioCreacionId,
 380            personasACargo = f.PersonasACargo?.Select(p => new
 381            {
 382                id = p.Id,
 383                nombre = p.Nombre,
 384                apellido = p.Apellido,
 385                fechaNacimiento = p.FechaNacimiento,
 386                cedula = p.Cedula,
 387                sexo = p.Sexo,
 388                porcentajeAtribucion = p.PorcentajeAtribucion,
 389                relacion = p.Relacion,
 390                discapacidad = p.Discapacidad
 391            })
 392        };
 393    }
 394}
 395
 396public class CrearForm3100Request
 397{
 398    [Required(ErrorMessage = "cedulaTitular es requerida")]
 399    [MaxLength(12)]
 400    public string CedulaTitular { get; set; } = string.Empty;
 401
 402    public short? CappuCategoria { get; set; }
 403
 404    [MaxLength(10)]
 405    public string? FondoSolidaridad { get; set; }
 406
 407    public bool AdicionalFondoSolidaridad { get; set; }
 408
 409    public bool AplicaMinimoNoImponible { get; set; }
 410
 411    [MaxLength(100)]
 412    public string? ConyugeNombre { get; set; }
 413
 414    [MaxLength(100)]
 415    public string? ConyugeApellido { get; set; }
 416
 417    public DateTime? ConyugeFechaNacimiento { get; set; }
 418
 419    [MaxLength(20)]
 420    public string? ConyugeCedula { get; set; }
 421
 422    [MaxLength(1)]
 423    public string? ConyugeSexo { get; set; }
 424
 425    [MaxLength(100)]
 426    public string? ConyugeNacionalidad { get; set; }
 427
 428    [Required]
 429    [Range(1, 12)]
 430    public int VigenteDesdeMes { get; set; }
 431
 432    [Required]
 433    [Range(2000, 2100)]
 434    public int VigenteDesdeAnio { get; set; }
 435}
 436
 437public class AgregarPersonaACargoRequest
 438{
 439    [Required]
 440    [MaxLength(100)]
 6441    public string Nombre { get; set; } = string.Empty;
 442
 443    [Required]
 444    [MaxLength(100)]
 6445    public string Apellido { get; set; } = string.Empty;
 446
 447    [Required]
 6448    public DateTime FechaNacimiento { get; set; }
 449
 450    [MaxLength(20)]
 2451    public string? Cedula { get; set; }
 452
 453    [Required]
 454    [MaxLength(1)]
 6455    public string Sexo { get; set; } = string.Empty;
 456
 457    [Required]
 458    [Range(0, 100)]
 4459    public int PorcentajeAtribucion { get; set; }
 460
 461    [Required]
 462    [MaxLength(100)]
 6463    public string Relacion { get; set; } = string.Empty;
 464
 2465    public bool Discapacidad { get; set; }
 466}