< 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
62%
Covered lines: 118
Uncovered lines: 71
Coverable lines: 189
Total lines: 453
Line coverage: 62.4%
Branch coverage
59%
Covered branches: 32
Total branches: 54
Branch coverage: 59.2%
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%262485%
GetHistorial()100%5466.66%
GetById()100%2262.5%
GetVigente()62.5%13858.33%
GetPersonasACargo()50%11650%
AgregarPersonaACargo()50%2286.95%
EliminarPersonaACargo()100%1137.5%
SubirArchivo()0%7280%
DescargarArchivo()100%1133.33%
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.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
 1622    public Form3100Controller(
 1623        IForm3100Service form3100Service,
 1624        ICurrentUserContext currentUser,
 1625        ILogger<Form3100Controller> logger)
 26    {
 1627        _form3100Service = form3100Service;
 1628        _currentUser = currentUser;
 1629        _logger = logger;
 1630    }
 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        {
 542            if (string.IsNullOrWhiteSpace(request.CedulaTitular))
 043                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 44
 545            if (request.VigenteDesdeMes < 1 || request.VigenteDesdeMes > 12)
 146                return BadRequest(ApiResponse<string>.ErrorResult("vigenteDesdeMes debe estar entre 1 y 12"));
 47
 448            if (request.VigenteDesdeAnio < 2000 || request.VigenteDesdeAnio > 2100)
 049                return BadRequest(ApiResponse<string>.ErrorResult("vigenteDesdeAnio inválido"));
 50
 451            if (request.CappuCategoria.HasValue && (request.CappuCategoria < 1 || request.CappuCategoria > 10))
 152                return BadRequest(ApiResponse<string>.ErrorResult("cappuCategoria debe estar entre 1 y 10"));
 53
 354            if (!string.IsNullOrEmpty(request.FondoSolidaridad) &&
 355                !new[] { "0.5_BPC", "1_BPC", "2_BPC" }.Contains(request.FondoSolidaridad))
 056                return BadRequest(ApiResponse<string>.ErrorResult("fondoSolidaridad debe ser '0.5_BPC', '1_BPC' o '2_BPC
 57
 358            var form = new Form3100
 359            {
 360                CappuCategoria = request.CappuCategoria,
 361                FondoSolidaridad = request.FondoSolidaridad,
 362                AdicionalFondoSolidaridad = request.AdicionalFondoSolidaridad,
 363                AplicaMinimoNoImponible = request.AplicaMinimoNoImponible,
 364                ConyugeNombre = request.ConyugeNombre,
 365                ConyugeApellido = request.ConyugeApellido,
 366                ConyugeFechaNacimiento = request.ConyugeFechaNacimiento,
 367                ConyugeCedula = request.ConyugeCedula,
 368                ConyugeSexo = request.ConyugeSexo,
 369                ConyugeNacionalidad = request.ConyugeNacionalidad,
 370                VigenteDesde_Mes = (short)request.VigenteDesdeMes,
 371                VigenteDesde_Anio = (short)request.VigenteDesdeAnio
 372            };
 73
 374            var usuarioId = _currentUser.UserId ?? 0;
 375            var creado = await _form3100Service.CrearAsync(request.CedulaTitular, form, usuarioId);
 76
 177            return CreatedAtAction(
 178                nameof(GetById),
 179                new { id = creado.Id },
 180                ApiResponse<object>.SuccessResult(new { id = creado.Id, creado = true }, "Form 3100 creado exitosamente"
 81        }
 182        catch (ArgumentException ex)
 83        {
 184            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 85        }
 186        catch (InvalidOperationException ex)
 87        {
 188            return Conflict(ApiResponse<string>.ErrorResult(ex.Message));
 89        }
 090        catch (Exception ex)
 91        {
 092            _logger.LogError(ex, "Error al crear Form 3100");
 093            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 94        }
 595    }
 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        {
 2110            if (string.IsNullOrWhiteSpace(cedulaTitular))
 1111                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 112
 1113            var historial = await _form3100Service.GetHistorialAsync(cedulaTitular, vigenteDesdeAnio, vigenteDesdeMes);
 114
 1115            var response = historial.Select(MapToDto);
 1116            return Ok(ApiResponse<object>.SuccessResult(response));
 117        }
 0118        catch (Exception ex)
 119        {
 0120            _logger.LogError(ex, "Error al obtener historial Form 3100");
 0121            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 122        }
 2123    }
 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        {
 2135            var form = await _form3100Service.GetByIdAsync(id);
 2136            if (form == null)
 1137                return NotFound(ApiResponse<string>.ErrorResult("Form 3100 no encontrado"));
 138
 1139            return Ok(ApiResponse<object>.SuccessResult(MapToDto(form)));
 140        }
 0141        catch (Exception ex)
 142        {
 0143            _logger.LogError(ex, "Error al obtener Form 3100 {Id}", id);
 0144            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 145        }
 2146    }
 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        {
 2161            if (string.IsNullOrWhiteSpace(cedulaTitular))
 0162                return BadRequest(ApiResponse<string>.ErrorResult("cedulaTitular es requerida"));
 2163            if (mes < 1 || mes > 12)
 0164                return BadRequest(ApiResponse<string>.ErrorResult("mes debe estar entre 1 y 12"));
 165
 2166            var form = await _form3100Service.GetVigenteParaPeriodoAsync(cedulaTitular, anio, mes);
 2167            if (form == null)
 1168                return NotFound(ApiResponse<string>.ErrorResult("No se encontró Form 3100 vigente para el período indica
 169
 1170            return Ok(ApiResponse<object>.SuccessResult(MapToDto(form)));
 171        }
 0172        catch (Exception ex)
 173        {
 0174            _logger.LogError(ex, "Error al obtener Form 3100 vigente");
 0175            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 176        }
 2177    }
 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        {
 1189            var form = await _form3100Service.GetByIdAsync(id);
 1190            if (form == null)
 0191                return NotFound(ApiResponse<string>.ErrorResult("Form 3100 no encontrado"));
 192
 1193            var personas = form.PersonasACargo?.Select(p => new
 1194            {
 1195                id = p.Id, nombre = p.Nombre, apellido = p.Apellido,
 1196                fechaNacimiento = p.FechaNacimiento, cedula = p.Cedula,
 1197                sexo = p.Sexo, porcentajeAtribucion = p.PorcentajeAtribucion,
 1198                relacion = p.Relacion, discapacidad = p.Discapacidad
 1199            });
 1200            return Ok(ApiResponse<object>.SuccessResult(personas ?? Enumerable.Empty<object>()));
 201        }
 0202        catch (Exception ex)
 203        {
 0204            _logger.LogError(ex, "Error al listar personas a cargo de Form 3100 {Id}", id);
 0205            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 206        }
 1207    }
 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        {
 2219            var persona = new Form3100PersonaACargo
 2220            {
 2221                Nombre = request.Nombre,
 2222                Apellido = request.Apellido,
 2223                FechaNacimiento = request.FechaNacimiento.Kind == DateTimeKind.Unspecified
 2224                    ? DateTime.SpecifyKind(request.FechaNacimiento.Date, DateTimeKind.Utc)
 2225                    : request.FechaNacimiento.Date.ToUniversalTime(),
 2226                Cedula = request.Cedula,
 2227                Sexo = request.Sexo,
 2228                PorcentajeAtribucion = (short)request.PorcentajeAtribucion,
 2229                Relacion = request.Relacion,
 2230                Discapacidad = request.Discapacidad
 2231            };
 232
 2233            var creada = await _form3100Service.AgregarPersonaACargoAsync(id, persona);
 234
 1235            return StatusCode(201, ApiResponse<object>.SuccessResult(
 1236                new { id = creada.Id, creado = true },
 1237                "Persona a cargo agregada exitosamente"));
 238        }
 1239        catch (ArgumentException ex)
 240        {
 1241            return BadRequest(ApiResponse<string>.ErrorResult(ex.Message));
 242        }
 0243        catch (Exception ex)
 244        {
 0245            _logger.LogError(ex, "Error al agregar persona a cargo a Form 3100 {Id}", id);
 0246            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 247        }
 2248    }
 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        {
 1260            await _form3100Service.EliminarPersonaACargoAsync(id, personaCargoId);
 1261            return NoContent();
 262        }
 0263        catch (ArgumentException ex)
 264        {
 0265            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 266        }
 0267        catch (Exception ex)
 268        {
 0269            _logger.LogError(ex, "Error al eliminar persona a cargo {PersonaCargoId} de Form 3100 {Id}", personaCargoId,
 0270            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 271        }
 1272    }
 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        {
 0285            if (file == null || file.Length == 0)
 0286                return BadRequest(ApiResponse<string>.ErrorResult("Archivo requerido"));
 287
 0288            var usuarioId = _currentUser.UserId ?? 0;
 0289            await using var stream = file.OpenReadStream();
 0290            var rutaRelativa = await _form3100Service.SubirArchivoAsync(
 0291                id,
 0292                stream,
 0293                file.FileName,
 0294                file.ContentType,
 0295                usuarioId);
 296
 0297            return Ok(ApiResponse<object>.SuccessResult(
 0298                new { rutaRelativa, subido = true },
 0299                "Archivo subido exitosamente"));
 0300        }
 0301        catch (ArgumentException ex)
 302        {
 0303            if (ex.Message.Contains("no encontrado", StringComparison.OrdinalIgnoreCase))
 0304                return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 0305            return BadRequest(ApiResponse<string>.ErrorResult(ex.Message));
 306        }
 0307        catch (Exception ex)
 308        {
 0309            _logger.LogError(ex, "Error al subir archivo de Form 3100 {Id}", id);
 0310            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 311        }
 0312    }
 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        {
 1324            var (contenido, nombreOriginal, contentType) = await _form3100Service.ObtenerArchivoAsync(id);
 0325            return File(contenido, contentType, nombreOriginal);
 326        }
 0327        catch (ArgumentException ex)
 328        {
 0329            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 330        }
 1331        catch (KeyNotFoundException ex)
 332        {
 1333            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 334        }
 0335        catch (StorageFileNotFoundException ex)
 336        {
 0337            return NotFound(ApiResponse<string>.ErrorResult(ex.Message));
 338        }
 0339        catch (Exception ex)
 340        {
 0341            _logger.LogError(ex, "Error al descargar archivo de Form 3100 {Id}", id);
 0342            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 343        }
 1344    }
 345
 346    private static object MapToDto(Form3100 f)
 347    {
 2348        return new
 2349        {
 2350            id = f.Id,
 2351            personaId = f.PersonaId,
 2352            cappuCategoria = f.CappuCategoria,
 2353            fondoSolidaridad = f.FondoSolidaridad,
 2354            adicionalFondoSolidaridad = f.AdicionalFondoSolidaridad,
 2355            aplicaMinimoNoImponible = f.AplicaMinimoNoImponible,
 2356            conyugeNombre = f.ConyugeNombre,
 2357            conyugeApellido = f.ConyugeApellido,
 2358            conyugeFechaNacimiento = f.ConyugeFechaNacimiento,
 2359            conyugeCedula = f.ConyugeCedula,
 2360            conyugeSexo = f.ConyugeSexo,
 2361            conyugeNacionalidad = f.ConyugeNacionalidad,
 2362            vigenteDesdeMes = f.VigenteDesde_Mes,
 2363            vigenteDesdeAnio = f.VigenteDesde_Anio,
 2364            documentoId = f.DocumentoId,
 2365            fechaCreacion = f.FechaCreacion,
 2366            usuarioCreacionId = f.UsuarioCreacionId,
 0367            personasACargo = f.PersonasACargo?.Select(p => new
 0368            {
 0369                id = p.Id,
 0370                nombre = p.Nombre,
 0371                apellido = p.Apellido,
 0372                fechaNacimiento = p.FechaNacimiento,
 0373                cedula = p.Cedula,
 0374                sexo = p.Sexo,
 0375                porcentajeAtribucion = p.PorcentajeAtribucion,
 0376                relacion = p.Relacion,
 0377                discapacidad = p.Discapacidad
 0378            })
 2379        };
 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)]
 428    public string Nombre { get; set; } = string.Empty;
 429
 430    [Required]
 431    [MaxLength(100)]
 432    public string Apellido { get; set; } = string.Empty;
 433
 434    [Required]
 435    public DateTime FechaNacimiento { get; set; }
 436
 437    [MaxLength(20)]
 438    public string? Cedula { get; set; }
 439
 440    [Required]
 441    [MaxLength(1)]
 442    public string Sexo { get; set; } = string.Empty;
 443
 444    [Required]
 445    [Range(0, 100)]
 446    public int PorcentajeAtribucion { get; set; }
 447
 448    [Required]
 449    [MaxLength(100)]
 450    public string Relacion { get; set; } = string.Empty;
 451
 452    public bool Discapacidad { get; set; }
 453}