< 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: 483
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.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
 1623    public Form3100Controller(
 1624        IForm3100Service form3100Service,
 1625        ICurrentUserContext currentUser,
 1626        ILogger<Form3100Controller> logger)
 27    {
 1628        _form3100Service = form3100Service;
 1629        _currentUser = currentUser;
 1630        _logger = logger;
 1631    }
 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        {
 543            if (string.IsNullOrWhiteSpace(request.CedulaTitular))
 044                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 45
 546            if (request.VigenteDesdeMes < 1 || request.VigenteDesdeMes > 12)
 147                return BadRequest(ApiResponse<string>.ErrorResult("vigenteDesdeMes debe estar entre 1 y 12"));
 48
 449            if (request.VigenteDesdeAnio < 2000 || request.VigenteDesdeAnio > 2100)
 050                return BadRequest(ApiResponse<string>.ErrorResult("vigenteDesdeAnio inválido"));
 51
 452            if (request.CappuCategoria.HasValue && (request.CappuCategoria < 1 || request.CappuCategoria > 10))
 153                return BadRequest(ApiResponse<string>.ErrorResult("cappuCategoria debe estar entre 1 y 10"));
 54
 355            if (!string.IsNullOrEmpty(request.FondoSolidaridad) &&
 356                !new[] { "0.5_BPC", "1_BPC", "2_BPC" }.Contains(request.FondoSolidaridad))
 057                return BadRequest(ApiResponse<string>.ErrorResult("fondoSolidaridad debe ser '0.5_BPC', '1_BPC' o '2_BPC
 58
 359            var form = new Form3100
 360            {
 361                CappuCategoria = request.CappuCategoria,
 362                FondoSolidaridad = request.FondoSolidaridad,
 363                AdicionalFondoSolidaridad = request.AdicionalFondoSolidaridad,
 364                AplicaMinimoNoImponible = request.AplicaMinimoNoImponible,
 365                ConyugeNombre = request.ConyugeNombre,
 366                ConyugeApellido = request.ConyugeApellido,
 367                ConyugeFechaNacimiento = request.ConyugeFechaNacimiento,
 368                ConyugeCedula = request.ConyugeCedula,
 369                ConyugeSexo = request.ConyugeSexo,
 370                ConyugeNacionalidad = request.ConyugeNacionalidad,
 371                VigenteDesde_Mes = (short)request.VigenteDesdeMes,
 372                VigenteDesde_Anio = (short)request.VigenteDesdeAnio
 373            };
 74
 375            var usuarioId = _currentUser.UserId ?? 0;
 376            var creado = await _form3100Service.CrearAsync(request.CedulaTitular, form, usuarioId);
 77
 178            return CreatedAtAction(
 179                nameof(GetById),
 180                new { id = creado.Id },
 181                ApiResponse<object>.SuccessResult(new { id = creado.Id, creado = true }, "Form 3100 creado exitosamente"
 82        }
 183        catch (ArgumentException ex)
 84        {
 185            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 86        }
 087        catch (PeriodoBloqueadoException ex)
 88        {
 089            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 90        }
 191        catch (InvalidOperationException ex)
 92        {
 193            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 94        }
 095        catch (Exception ex)
 96        {
 097            _logger.LogError(ex, "Error al crear Form 3100");
 098            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 99        }
 5100    }
 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        {
 2115            if (string.IsNullOrWhiteSpace(cedulaTitular))
 1116                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 117
 1118            var historial = await _form3100Service.GetHistorialAsync(cedulaTitular, vigenteDesdeAnio, vigenteDesdeMes);
 119
 1120            var response = historial.Select(MapToDto);
 1121            return Ok(ApiResponse<object>.SuccessResult(response));
 122        }
 0123        catch (Exception ex)
 124        {
 0125            _logger.LogError(ex, "Error al obtener historial Form 3100");
 0126            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 127        }
 2128    }
 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        {
 2140            var form = await _form3100Service.GetByIdAsync(id);
 2141            if (form == null)
 1142                return NotFound(ApiResponse<string>.ErrorResult("Form 3100 no encontrado"));
 143
 1144            return Ok(ApiResponse<object>.SuccessResult(MapToDto(form)));
 145        }
 0146        catch (Exception ex)
 147        {
 0148            _logger.LogError(ex, "Error al obtener Form 3100 {Id}", id);
 0149            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 150        }
 2151    }
 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        {
 2166            if (string.IsNullOrWhiteSpace(cedulaTitular))
 0167                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 2168            if (mes < 1 || mes > 12)
 0169                return BadRequest(ApiResponse<string>.ErrorResult("mes debe estar entre 1 y 12"));
 170
 2171            var form = await _form3100Service.GetVigenteParaPeriodoAsync(cedulaTitular, anio, mes);
 2172            if (form == null)
 1173                return NotFound(ApiResponse<string>.ErrorResult("No se encontró Form 3100 vigente para el período indica
 174
 1175            return Ok(ApiResponse<object>.SuccessResult(MapToDto(form)));
 176        }
 0177        catch (Exception ex)
 178        {
 0179            _logger.LogError(ex, "Error al obtener Form 3100 vigente");
 0180            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 181        }
 2182    }
 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        {
 1194            var form = await _form3100Service.GetByIdAsync(id);
 1195            if (form == null)
 0196                return NotFound(ApiResponse<string>.ErrorResult("Form 3100 no encontrado"));
 197
 1198            var personas = form.PersonasACargo?.Select(p => new
 1199            {
 1200                id = p.Id, nombre = p.Nombre, apellido = p.Apellido,
 1201                fechaNacimiento = p.FechaNacimiento, cedula = p.Cedula,
 1202                sexo = p.Sexo, porcentajeAtribucion = p.PorcentajeAtribucion,
 1203                relacion = p.Relacion, discapacidad = p.Discapacidad
 1204            });
 1205            return Ok(ApiResponse<object>.SuccessResult(personas ?? Enumerable.Empty<object>()));
 206        }
 0207        catch (Exception ex)
 208        {
 0209            _logger.LogError(ex, "Error al listar personas a cargo de Form 3100 {Id}", id);
 0210            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 211        }
 1212    }
 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        {
 2224            var persona = new Form3100PersonaACargo
 2225            {
 2226                Nombre = request.Nombre,
 2227                Apellido = request.Apellido,
 2228                FechaNacimiento = request.FechaNacimiento.Kind == DateTimeKind.Unspecified
 2229                    ? DateTime.SpecifyKind(request.FechaNacimiento.Date, DateTimeKind.Utc)
 2230                    : request.FechaNacimiento.Date.ToUniversalTime(),
 2231                Cedula = request.Cedula,
 2232                Sexo = request.Sexo,
 2233                PorcentajeAtribucion = (short)request.PorcentajeAtribucion,
 2234                Relacion = request.Relacion,
 2235                Discapacidad = request.Discapacidad
 2236            };
 237
 2238            var creada = await _form3100Service.AgregarPersonaACargoAsync(id, persona);
 239
 1240            return StatusCode(201, ApiResponse<object>.SuccessResult(
 1241                new { id = creada.Id, creado = true },
 1242                "Persona a cargo agregada exitosamente"));
 243        }
 0244        catch (PeriodoBloqueadoException ex)
 245        {
 0246            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 247        }
 1248        catch (ArgumentException ex)
 249        {
 1250            return BadRequest(ApiResponse<string>.ErrorResult(ex.Message));
 251        }
 0252        catch (Exception ex)
 253        {
 0254            _logger.LogError(ex, "Error al agregar persona a cargo a Form 3100 {Id}", id);
 0255            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 256        }
 2257    }
 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        {
 1269            await _form3100Service.EliminarPersonaACargoAsync(id, personaCargoId);
 1270            return NoContent();
 271        }
 0272        catch (PeriodoBloqueadoException ex)
 273        {
 0274            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 275        }
 0276        catch (ArgumentException ex)
 277        {
 0278            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 279        }
 0280        catch (Exception ex)
 281        {
 0282            _logger.LogError(ex, "Error al eliminar persona a cargo {PersonaCargoId} de Form 3100 {Id}", personaCargoId,
 0283            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 284        }
 1285    }
 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        {
 0298            if (file == null || file.Length == 0)
 0299                return BadRequest(ApiResponse<string>.ErrorResult("Archivo requerido"));
 300
 0301            var usuarioId = _currentUser.UserId ?? 0;
 0302            await using var stream = file.OpenReadStream();
 0303            var rutaRelativa = await _form3100Service.SubirArchivoAsync(
 0304                id,
 0305                stream,
 0306                file.FileName,
 0307                file.ContentType,
 0308                usuarioId);
 309
 0310            return Ok(ApiResponse<object>.SuccessResult(
 0311                new { rutaRelativa, subido = true },
 0312                "Archivo subido exitosamente"));
 0313        }
 0314        catch (PeriodoBloqueadoException ex)
 315        {
 0316            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 317        }
 0318        catch (ArgumentException ex)
 319        {
 0320            if (ex.Message.Contains("no encontrado", StringComparison.OrdinalIgnoreCase))
 0321                return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 0322            return BadRequest(ApiResponse<string>.ErrorResult(ex.Message));
 323        }
 0324        catch (Exception ex)
 325        {
 0326            _logger.LogError(ex, "Error al subir archivo de Form 3100 {Id}", id);
 0327            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 328        }
 0329    }
 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        {
 1346            var (contenido, nombreOriginal, contentType) = await _form3100Service.ObtenerArchivoAsync(id);
 347
 0348            if (inline)
 349            {
 0350                Response.Headers.ContentDisposition =
 0351                    $"inline; filename=\"{nombreOriginal}\"; filename*=UTF-8''{Uri.EscapeDataString(nombreOriginal)}";
 0352                return File(contenido, contentType);
 353            }
 354
 0355            return File(contenido, contentType, nombreOriginal);
 356        }
 0357        catch (ArgumentException ex)
 358        {
 0359            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 360        }
 1361        catch (KeyNotFoundException ex)
 362        {
 1363            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 364        }
 0365        catch (StorageFileNotFoundException ex)
 366        {
 0367            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 368        }
 0369        catch (Exception ex)
 370        {
 0371            _logger.LogError(ex, "Error al descargar archivo de Form 3100 {Id}", id);
 0372            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 373        }
 1374    }
 375
 376    private static object MapToDto(Form3100 f)
 377    {
 2378        return new
 2379        {
 2380            id = f.Id,
 2381            personaId = f.PersonaId,
 2382            cappuCategoria = f.CappuCategoria,
 2383            fondoSolidaridad = f.FondoSolidaridad,
 2384            adicionalFondoSolidaridad = f.AdicionalFondoSolidaridad,
 2385            aplicaMinimoNoImponible = f.AplicaMinimoNoImponible,
 2386            conyugeNombre = f.ConyugeNombre,
 2387            conyugeApellido = f.ConyugeApellido,
 2388            conyugeFechaNacimiento = f.ConyugeFechaNacimiento,
 2389            conyugeCedula = f.ConyugeCedula,
 2390            conyugeSexo = f.ConyugeSexo,
 2391            conyugeNacionalidad = f.ConyugeNacionalidad,
 2392            vigenteDesdeMes = f.VigenteDesde_Mes,
 2393            vigenteDesdeAnio = f.VigenteDesde_Anio,
 2394            documentoId = f.DocumentoId,
 2395            fechaCreacion = f.FechaCreacion,
 2396            usuarioCreacionId = f.UsuarioCreacionId,
 0397            personasACargo = f.PersonasACargo?.Select(p => new
 0398            {
 0399                id = p.Id,
 0400                nombre = p.Nombre,
 0401                apellido = p.Apellido,
 0402                fechaNacimiento = p.FechaNacimiento,
 0403                cedula = p.Cedula,
 0404                sexo = p.Sexo,
 0405                porcentajeAtribucion = p.PorcentajeAtribucion,
 0406                relacion = p.Relacion,
 0407                discapacidad = p.Discapacidad
 0408            })
 2409        };
 410    }
 411}
 412
 413public class CrearForm3100Request
 414{
 415    [Required(ErrorMessage = "cedulaTitular es requerida")]
 416    [MaxLength(12)]
 417    public string CedulaTitular { get; set; } = string.Empty;
 418
 419    public short? CappuCategoria { get; set; }
 420
 421    [MaxLength(10)]
 422    public string? FondoSolidaridad { get; set; }
 423
 424    public bool AdicionalFondoSolidaridad { get; set; }
 425
 426    public bool AplicaMinimoNoImponible { get; set; }
 427
 428    [MaxLength(100)]
 429    public string? ConyugeNombre { get; set; }
 430
 431    [MaxLength(100)]
 432    public string? ConyugeApellido { get; set; }
 433
 434    public DateTime? ConyugeFechaNacimiento { get; set; }
 435
 436    [MaxLength(20)]
 437    public string? ConyugeCedula { get; set; }
 438
 439    [MaxLength(1)]
 440    public string? ConyugeSexo { get; set; }
 441
 442    [MaxLength(100)]
 443    public string? ConyugeNacionalidad { get; set; }
 444
 445    [Required]
 446    [Range(1, 12)]
 447    public int VigenteDesdeMes { get; set; }
 448
 449    [Required]
 450    [Range(2000, 2100)]
 451    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}