Feat: Se agregan servicios y controladores para ABM de suscriptores

This commit is contained in:
2025-07-30 09:48:05 -03:00
parent 19e7192a16
commit f09c795fb0
13 changed files with 623 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/formaspago")]
[ApiController]
[Authorize] // Solo usuarios logueados pueden ver esto
public class FormasDePagoController : ControllerBase
{
private readonly IFormaPagoService _formaPagoService;
public FormasDePagoController(IFormaPagoService formaPagoService)
{
_formaPagoService = formaPagoService;
}
// GET: api/formaspago
[HttpGet]
public async Task<IActionResult> GetAll()
{
var formasDePago = await _formaPagoService.ObtenerTodos();
return Ok(formasDePago);
}
}
}

View File

@@ -0,0 +1,153 @@
using GestionIntegral.Api.Dtos.Suscripciones;
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/suscriptores")]
[ApiController]
[Authorize]
public class SuscriptoresController : ControllerBase
{
private readonly ISuscriptorService _suscriptorService;
private readonly ILogger<SuscriptoresController> _logger;
// Permisos para Suscriptores
private const string PermisoVer = "SU001";
private const string PermisoCrear = "SU002";
private const string PermisoModificar = "SU003";
private const string PermisoActivarDesactivar = "SU004";
public SuscriptoresController(ISuscriptorService suscriptorService, ILogger<SuscriptoresController> logger)
{
_suscriptorService = suscriptorService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
_logger.LogWarning("No se pudo obtener el UserId del token JWT en SuscriptoresController.");
return null;
}
// GET: api/suscriptores
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<SuscriptorDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAll([FromQuery] string? nombre, [FromQuery] string? nroDoc, [FromQuery] bool soloActivos = true)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var suscriptores = await _suscriptorService.ObtenerTodos(nombre, nroDoc, soloActivos);
return Ok(suscriptores);
}
// GET: api/suscriptores/{id}
[HttpGet("{id:int}", Name = "GetSuscriptorById")]
[ProducesResponseType(typeof(SuscriptorDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var suscriptor = await _suscriptorService.ObtenerPorId(id);
if (suscriptor == null) return NotFound();
return Ok(suscriptor);
}
// POST: api/suscriptores
[HttpPost]
[ProducesResponseType(typeof(SuscriptorDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Create([FromBody] CreateSuscriptorDto createDto)
{
if (!TienePermiso(PermisoCrear)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _suscriptorService.Crear(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear el suscriptor.");
return CreatedAtRoute("GetSuscriptorById", new { id = dto.IdSuscriptor }, dto);
}
// PUT: api/suscriptores/{id}
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateSuscriptorDto updateDto)
{
if (!TienePermiso(PermisoModificar)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _suscriptorService.Actualizar(id, updateDto, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/suscriptores/{id} (Desactivar)
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Deactivate(int id)
{
if (!TienePermiso(PermisoActivarDesactivar)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _suscriptorService.Desactivar(id, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// POST: api/suscriptores/{id}/activar
[HttpPost("{id:int}/activar")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Activate(int id)
{
if (!TienePermiso(PermisoActivarDesactivar)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _suscriptorService.Activar(id, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Suscripciones
{
public class CreateSuscripcionDto
{
[Required]
public int IdSuscriptor { get; set; }
[Required]
public int IdPublicacion { get; set; }
[Required]
public DateTime FechaInicio { get; set; }
public DateTime? FechaFin { get; set; }
[Required]
public string Estado { get; set; } = "Activa";
[Required(ErrorMessage = "Debe especificar los días de entrega.")]
public List<string> DiasEntrega { get; set; } = new List<string>(); // "L", "M", "X"...
[StringLength(250)]
public string? Observaciones { get; set; }
}
}
// Nota: Por ahora, el DTO de actualización puede ser similar al de creación.
// Si se necesita una lógica diferente, se crearía un UpdateSuscripcionDto.

View File

@@ -0,0 +1,39 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Suscripciones
{
public class CreateSuscriptorDto
{
[Required(ErrorMessage = "El nombre completo es obligatorio.")]
[StringLength(150)]
public string NombreCompleto { get; set; } = string.Empty;
[EmailAddress(ErrorMessage = "El formato del email no es válido.")]
[StringLength(100)]
public string? Email { get; set; }
[StringLength(50)]
public string? Telefono { get; set; }
[Required(ErrorMessage = "La dirección es obligatoria.")]
[StringLength(200)]
public string Direccion { get; set; } = string.Empty;
[Required(ErrorMessage = "El tipo de documento es obligatorio.")]
[StringLength(4)]
public string TipoDocumento { get; set; } = string.Empty;
[Required(ErrorMessage = "El número de documento es obligatorio.")]
[StringLength(11)]
public string NroDocumento { get; set; } = string.Empty;
[StringLength(22, MinimumLength = 22, ErrorMessage = "El CBU debe tener 22 dígitos.")]
public string? CBU { get; set; }
[Required(ErrorMessage = "La forma de pago es obligatoria.")]
public int IdFormaPagoPreferida { get; set; }
[StringLength(250)]
public string? Observaciones { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace GestionIntegral.Api.Dtos.Suscripciones
{
public class FormaPagoDto
{
public int IdFormaPago { get; set; }
public string Nombre { get; set; } = string.Empty;
public bool RequiereCBU { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
namespace GestionIntegral.Api.Dtos.Suscripciones
{
public class SuscripcionDto
{
public int IdSuscripcion { get; set; }
public int IdSuscriptor { get; set; }
public int IdPublicacion { get; set; }
public string NombrePublicacion { get; set; } = string.Empty; // Para UI
public string FechaInicio { get; set; } = string.Empty; // Formato "yyyy-MM-dd"
public string? FechaFin { get; set; }
public string Estado { get; set; } = string.Empty;
public string DiasEntrega { get; set; } = string.Empty;
public string? Observaciones { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
namespace GestionIntegral.Api.Dtos.Suscripciones
{
// DTO para mostrar la información de un suscriptor
public class SuscriptorDto
{
public int IdSuscriptor { get; set; }
public string NombreCompleto { get; set; } = string.Empty;
public string? Email { get; set; }
public string? Telefono { get; set; }
public string Direccion { get; set; } = string.Empty;
public string TipoDocumento { get; set; } = string.Empty;
public string NroDocumento { get; set; } = string.Empty;
public string? CBU { get; set; }
public int IdFormaPagoPreferida { get; set; }
public string NombreFormaPagoPreferida { get; set; } = string.Empty; // Para UI
public string? Observaciones { get; set; }
public bool Activo { get; set; }
}
}

View File

@@ -0,0 +1,40 @@
using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Suscripciones
{
// Es idéntico al CreateDto, pero se mantiene separado por si las reglas de validación cambian.
public class UpdateSuscriptorDto
{
[Required(ErrorMessage = "El nombre completo es obligatorio.")]
[StringLength(150)]
public string NombreCompleto { get; set; } = string.Empty;
[EmailAddress(ErrorMessage = "El formato del email no es válido.")]
[StringLength(100)]
public string? Email { get; set; }
[StringLength(50)]
public string? Telefono { get; set; }
[Required(ErrorMessage = "La dirección es obligatoria.")]
[StringLength(200)]
public string Direccion { get; set; } = string.Empty;
[Required(ErrorMessage = "El tipo de documento es obligatorio.")]
[StringLength(4)]
public string TipoDocumento { get; set; } = string.Empty;
[Required(ErrorMessage = "El número de documento es obligatorio.")]
[StringLength(11)]
public string NroDocumento { get; set; } = string.Empty;
[StringLength(22, MinimumLength = 22, ErrorMessage = "El CBU debe tener 22 dígitos.")]
public string? CBU { get; set; }
[Required(ErrorMessage = "La forma de pago es obligatoria.")]
public int IdFormaPagoPreferida { get; set; }
[StringLength(250)]
public string? Observaciones { get; set; }
}
}

View File

@@ -18,6 +18,8 @@ using GestionIntegral.Api.Services.Reportes;
using GestionIntegral.Api.Services.Pdf;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using GestionIntegral.Api.Services.Anomalia;
using GestionIntegral.Api.Data.Repositories.Suscripciones;
using GestionIntegral.Api.Services.Suscripciones;
var builder = WebApplication.CreateBuilder(args);
@@ -100,6 +102,19 @@ builder.Services.AddScoped<IQuestPdfGenerator, QuestPdfGenerator>();
// Servicio de Alertas
builder.Services.AddScoped<IAlertaService, AlertaService>();
// --- Suscripciones ---
// Repositorios
builder.Services.AddScoped<IFormaPagoRepository, FormaPagoRepository>();
builder.Services.AddScoped<ISuscriptorRepository, SuscriptorRepository>();
builder.Services.AddScoped<ISuscripcionRepository, SuscripcionRepository>();
builder.Services.AddScoped<IFacturaRepository, FacturaRepository>();
builder.Services.AddScoped<ILoteDebitoRepository, LoteDebitoRepository>();
builder.Services.AddScoped<IPagoRepository, PagoRepository>();
// Servicios
builder.Services.AddScoped<IFormaPagoService, FormaPagoService>();
builder.Services.AddScoped<ISuscriptorService, SuscriptorService>();
// --- SERVICIO DE HEALTH CHECKS ---
// Añadimos una comprobación específica para SQL Server.
// El sistema usará la cadena de conexión configurada en appsettings.json o variables de entorno.

View File

@@ -0,0 +1,26 @@
using GestionIntegral.Api.Data.Repositories.Suscripciones;
using GestionIntegral.Api.Dtos.Suscripciones;
namespace GestionIntegral.Api.Services.Suscripciones
{
public class FormaPagoService : IFormaPagoService
{
private readonly IFormaPagoRepository _formaPagoRepository;
public FormaPagoService(IFormaPagoRepository formaPagoRepository)
{
_formaPagoRepository = formaPagoRepository;
}
public async Task<IEnumerable<FormaPagoDto>> ObtenerTodos()
{
var formasDePago = await _formaPagoRepository.GetAllAsync();
return formasDePago.Select(fp => new FormaPagoDto
{
IdFormaPago = fp.IdFormaPago,
Nombre = fp.Nombre,
RequiereCBU = fp.RequiereCBU
});
}
}
}

View File

@@ -0,0 +1,9 @@
using GestionIntegral.Api.Dtos.Suscripciones;
namespace GestionIntegral.Api.Services.Suscripciones
{
public interface IFormaPagoService
{
Task<IEnumerable<FormaPagoDto>> ObtenerTodos();
}
}

View File

@@ -0,0 +1,16 @@
// Archivo: GestionIntegral.Api/Services/Suscripciones/ISuscriptorService.cs
using GestionIntegral.Api.Dtos.Suscripciones;
namespace GestionIntegral.Api.Services.Suscripciones
{
public interface ISuscriptorService
{
Task<IEnumerable<SuscriptorDto>> ObtenerTodos(string? nombreFilter, string? nroDocFilter, bool soloActivos);
Task<SuscriptorDto?> ObtenerPorId(int id);
Task<(SuscriptorDto? Suscriptor, string? Error)> Crear(CreateSuscriptorDto createDto, int idUsuario);
Task<(bool Exito, string? Error)> Actualizar(int id, UpdateSuscriptorDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> Desactivar(int id, int idUsuario);
Task<(bool Exito, string? Error)> Activar(int id, int idUsuario);
}
}

View File

@@ -0,0 +1,225 @@
// Archivo: GestionIntegral.Api/Services/Suscripciones/SuscriptorService.cs
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Suscripciones;
using GestionIntegral.Api.Dtos.Suscripciones;
using GestionIntegral.Api.Models.Suscripciones;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Suscripciones
{
public class SuscriptorService : ISuscriptorService
{
private readonly ISuscriptorRepository _suscriptorRepository;
private readonly IFormaPagoRepository _formaPagoRepository;
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<SuscriptorService> _logger;
public SuscriptorService(
ISuscriptorRepository suscriptorRepository,
IFormaPagoRepository formaPagoRepository,
DbConnectionFactory connectionFactory,
ILogger<SuscriptorService> logger)
{
_suscriptorRepository = suscriptorRepository;
_formaPagoRepository = formaPagoRepository;
_connectionFactory = connectionFactory;
_logger = logger;
}
// Helper para mapear Modelo -> DTO, enriqueciendo con el nombre de la forma de pago
private async Task<SuscriptorDto?> MapToDto(Suscriptor suscriptor)
{
if (suscriptor == null) return null;
var formaPago = await _formaPagoRepository.GetByIdAsync(suscriptor.IdFormaPagoPreferida);
return new SuscriptorDto
{
IdSuscriptor = suscriptor.IdSuscriptor,
NombreCompleto = suscriptor.NombreCompleto,
Email = suscriptor.Email,
Telefono = suscriptor.Telefono,
Direccion = suscriptor.Direccion,
TipoDocumento = suscriptor.TipoDocumento,
NroDocumento = suscriptor.NroDocumento,
CBU = suscriptor.CBU,
IdFormaPagoPreferida = suscriptor.IdFormaPagoPreferida,
NombreFormaPagoPreferida = formaPago?.Nombre ?? "Desconocida",
Observaciones = suscriptor.Observaciones,
Activo = suscriptor.Activo
};
}
public async Task<IEnumerable<SuscriptorDto>> ObtenerTodos(string? nombreFilter, string? nroDocFilter, bool soloActivos)
{
var suscriptores = await _suscriptorRepository.GetAllAsync(nombreFilter, nroDocFilter, soloActivos);
var dtosTasks = suscriptores.Select(s => MapToDto(s));
var dtos = await Task.WhenAll(dtosTasks);
return dtos.Where(dto => dto != null).Select(dto => dto!);
}
public async Task<SuscriptorDto?> ObtenerPorId(int id)
{
var suscriptor = await _suscriptorRepository.GetByIdAsync(id);
if (suscriptor == null)
return null;
return await MapToDto(suscriptor);
}
public async Task<(SuscriptorDto? Suscriptor, string? Error)> Crear(CreateSuscriptorDto createDto, int idUsuario)
{
// Validación de Lógica de Negocio
if (await _suscriptorRepository.ExistsByDocumentoAsync(createDto.TipoDocumento, createDto.NroDocumento))
{
return (null, "Ya existe un suscriptor con el mismo tipo y número de documento.");
}
var formaPago = await _formaPagoRepository.GetByIdAsync(createDto.IdFormaPagoPreferida);
if (formaPago == null || !formaPago.Activo)
{
return (null, "La forma de pago seleccionada no es válida o está inactiva.");
}
if (formaPago.RequiereCBU && string.IsNullOrWhiteSpace(createDto.CBU))
{
return (null, "El CBU es obligatorio para la forma de pago seleccionada.");
}
var nuevoSuscriptor = new Suscriptor
{
NombreCompleto = createDto.NombreCompleto,
Email = createDto.Email,
Telefono = createDto.Telefono,
Direccion = createDto.Direccion,
TipoDocumento = createDto.TipoDocumento,
NroDocumento = createDto.NroDocumento,
CBU = createDto.CBU,
IdFormaPagoPreferida = createDto.IdFormaPagoPreferida,
Observaciones = createDto.Observaciones,
IdUsuarioAlta = idUsuario
};
using var connection = _connectionFactory.CreateConnection();
await (connection as System.Data.Common.DbConnection)!.OpenAsync();
using var transaction = connection.BeginTransaction();
try
{
var suscriptorCreado = await _suscriptorRepository.CreateAsync(nuevoSuscriptor, transaction);
if (suscriptorCreado == null) throw new DataException("La creación en el repositorio devolvió null.");
transaction.Commit();
_logger.LogInformation("Suscriptor ID {IdSuscriptor} creado por Usuario ID {IdUsuario}.", suscriptorCreado.IdSuscriptor, idUsuario);
var dtoCreado = await MapToDto(suscriptorCreado);
return (dtoCreado, null);
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error al crear suscriptor: {Nombre}", createDto.NombreCompleto);
return (null, $"Error interno al crear el suscriptor: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> Actualizar(int id, UpdateSuscriptorDto updateDto, int idUsuario)
{
var suscriptorExistente = await _suscriptorRepository.GetByIdAsync(id);
if (suscriptorExistente == null) return (false, "Suscriptor no encontrado.");
if (await _suscriptorRepository.ExistsByDocumentoAsync(updateDto.TipoDocumento, updateDto.NroDocumento, id))
{
return (false, "El tipo y número de documento ya pertenecen a otro suscriptor.");
}
var formaPago = await _formaPagoRepository.GetByIdAsync(updateDto.IdFormaPagoPreferida);
if (formaPago == null || !formaPago.Activo)
{
return (false, "La forma de pago seleccionada no es válida o está inactiva.");
}
if (formaPago.RequiereCBU && string.IsNullOrWhiteSpace(updateDto.CBU))
{
return (false, "El CBU es obligatorio para la forma de pago seleccionada.");
}
// Mapeo DTO -> Modelo
suscriptorExistente.NombreCompleto = updateDto.NombreCompleto;
suscriptorExistente.Email = updateDto.Email;
suscriptorExistente.Telefono = updateDto.Telefono;
suscriptorExistente.Direccion = updateDto.Direccion;
suscriptorExistente.TipoDocumento = updateDto.TipoDocumento;
suscriptorExistente.NroDocumento = updateDto.NroDocumento;
suscriptorExistente.CBU = updateDto.CBU;
suscriptorExistente.IdFormaPagoPreferida = updateDto.IdFormaPagoPreferida;
suscriptorExistente.Observaciones = updateDto.Observaciones;
suscriptorExistente.IdUsuarioMod = idUsuario;
suscriptorExistente.FechaMod = DateTime.Now;
using var connection = _connectionFactory.CreateConnection();
await (connection as System.Data.Common.DbConnection)!.OpenAsync();
using var transaction = connection.BeginTransaction();
try
{
var actualizado = await _suscriptorRepository.UpdateAsync(suscriptorExistente, transaction);
if (!actualizado) throw new DataException("La actualización en el repositorio devolvió false.");
transaction.Commit();
_logger.LogInformation("Suscriptor ID {IdSuscriptor} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
return (true, null);
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error al actualizar suscriptor ID: {IdSuscriptor}", id);
return (false, $"Error interno al actualizar: {ex.Message}");
}
}
private async Task<(bool Exito, string? Error)> CambiarEstadoActivo(int id, bool activar, int idUsuario)
{
var suscriptor = await _suscriptorRepository.GetByIdAsync(id);
if (suscriptor == null) return (false, "Suscriptor no encontrado.");
if (!activar && await _suscriptorRepository.IsInUseAsync(id))
{
return (false, "No se puede desactivar un suscriptor con suscripciones activas.");
}
using var connection = _connectionFactory.CreateConnection();
await (connection as System.Data.Common.DbConnection)!.OpenAsync();
using var transaction = connection.BeginTransaction();
try
{
var actualizado = await _suscriptorRepository.ToggleActivoAsync(id, activar, idUsuario, transaction);
if (!actualizado) throw new DataException("No se pudo cambiar el estado del suscriptor.");
transaction.Commit();
_logger.LogInformation("El estado del Suscriptor ID {IdSuscriptor} se cambió a {Estado} por el Usuario ID {IdUsuario}.", id, activar ? "Activo" : "Inactivo", idUsuario);
return (true, null);
}
catch(Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error al cambiar estado del suscriptor ID: {IdSuscriptor}", id);
return (false, $"Error interno: {ex.Message}");
}
}
public Task<(bool Exito, string? Error)> Desactivar(int id, int idUsuario)
{
return CambiarEstadoActivo(id, false, idUsuario);
}
public Task<(bool Exito, string? Error)> Activar(int id, int idUsuario)
{
return CambiarEstadoActivo(id, true, idUsuario);
}
}
}