< Summary

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