feat: Implementación CRUD Canillitas, Distribuidores y Precios de Publicación
Backend API:
- Canillitas (`dist_dtCanillas`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Lógica para manejo de `Accionista`, `Baja`, `FechaBaja`.
- Auditoría en `dist_dtCanillas_H`.
- Validación de legajo único y lógica de empresa vs accionista.
- Distribuidores (`dist_dtDistribuidores`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Auditoría en `dist_dtDistribuidores_H`.
- Creación de saldos iniciales para el nuevo distribuidor en todas las empresas.
- Verificación de NroDoc único y Nombre opcionalmente único.
- Precios de Publicación (`dist_Precios`):
- Implementado CRUD básico (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/precios`.
- Lógica de negocio para cerrar período de precio anterior al crear uno nuevo.
- Lógica de negocio para reabrir período de precio anterior al eliminar el último.
- Auditoría en `dist_Precios_H`.
- Auditoría en Eliminación de Publicaciones:
- Extendido `PublicacionService.EliminarAsync` para eliminar en cascada registros de precios, recargos, porcentajes de pago (distribuidores y canillitas) y secciones de publicación.
- Repositorios correspondientes (`PrecioRepository`, `RecargoZonaRepository`, `PorcPagoRepository`, `PorcMonCanillaRepository`, `PubliSeccionRepository`) actualizados con métodos `DeleteByPublicacionIdAsync` que registran en sus respectivas tablas `_H` (si existen y se implementó la lógica).
- Asegurada la correcta propagación del `idUsuario` para la auditoría en cascada.
- Correcciones de Nulabilidad:
- Ajustados los métodos `MapToDto` y su uso en `CanillaService` y `PublicacionService` para manejar correctamente tipos anulables.
Frontend React:
- Canillitas:
- `canillaService.ts`.
- `CanillaFormModal.tsx` con selectores para Zona y Empresa, y lógica de Accionista.
- `GestionarCanillitasPage.tsx` con filtros, paginación, y acciones (editar, toggle baja).
- Distribuidores:
- `distribuidorService.ts`.
- `DistribuidorFormModal.tsx` con múltiples campos y selector de Zona.
- `GestionarDistribuidoresPage.tsx` con filtros, paginación, y acciones (editar, eliminar).
- Precios de Publicación:
- `precioService.ts`.
- `PrecioFormModal.tsx` para crear/editar períodos de precios (VigenciaD, VigenciaH opcional, precios por día).
- `GestionarPreciosPublicacionPage.tsx` accesible desde la gestión de publicaciones, para listar y gestionar los períodos de precios de una publicación específica.
- Layout:
- Reemplazado el uso de `Grid` por `Box` con Flexbox en `CanillaFormModal`, `GestionarCanillitasPage` (filtros), `DistribuidorFormModal` y `PrecioFormModal` para resolver problemas de tipos y mejorar la consistencia del layout de formularios.
- Navegación:
- Actualizadas las rutas y pestañas para los nuevos módulos y sub-módulos.
This commit is contained in:
174
Backend/GestionIntegral.Api/Services/Usuarios/AuthService.cs
Normal file
174
Backend/GestionIntegral.Api/Services/Usuarios/AuthService.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
||||
using GestionIntegral.Api.Dtos;
|
||||
using GestionIntegral.Api.Models.Usuarios;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly IAuthRepository _authRepository;
|
||||
private readonly PasswordHasherService _passwordHasher;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<AuthService> _logger; // Añadir logger
|
||||
|
||||
public AuthService(
|
||||
IAuthRepository authRepository,
|
||||
PasswordHasherService passwordHasher,
|
||||
IConfiguration configuration,
|
||||
ILogger<AuthService> logger) // Inyectar logger
|
||||
{
|
||||
_authRepository = authRepository;
|
||||
_passwordHasher = passwordHasher;
|
||||
_configuration = configuration;
|
||||
_logger = logger; // Asignar logger
|
||||
}
|
||||
|
||||
public async Task<LoginResponseDto?> LoginAsync(LoginRequestDto loginRequest)
|
||||
{
|
||||
var user = await _authRepository.GetUserByUsernameAsync(loginRequest.Username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("Login attempt failed: User {Username} not found.", loginRequest.Username);
|
||||
return null;
|
||||
}
|
||||
if (!user.Habilitada)
|
||||
{
|
||||
_logger.LogWarning("Login attempt failed: User {Username} is disabled.", loginRequest.Username);
|
||||
return null;
|
||||
}
|
||||
|
||||
bool isPasswordValid = _passwordHasher.VerifyPassword(loginRequest.Password, user.ClaveHash, user.ClaveSalt);
|
||||
if (!isPasswordValid)
|
||||
{
|
||||
_logger.LogWarning("Login attempt failed: Invalid password for user {Username}.", loginRequest.Username);
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- OBTENER PERMISOS ---
|
||||
IEnumerable<string> permisosDelUsuario = new List<string>();
|
||||
if (!user.SupAdmin && user.IdPerfil > 0) // Solo si no es SuperAdmin y tiene un perfil válido
|
||||
{
|
||||
permisosDelUsuario = await _authRepository.GetPermisosCodAccByPerfilIdAsync(user.IdPerfil);
|
||||
}
|
||||
// --- FIN OBTENER PERMISOS ---
|
||||
|
||||
var token = GenerateJwtToken(user, permisosDelUsuario); // Pasar permisos
|
||||
bool debeCambiar = user.DebeCambiarClave;
|
||||
|
||||
_logger.LogInformation("User {Username} logged in successfully.", loginRequest.Username);
|
||||
|
||||
return new LoginResponseDto
|
||||
{
|
||||
Token = token,
|
||||
UserId = user.Id,
|
||||
Username = user.User,
|
||||
NombreCompleto = $"{user.Nombre} {user.Apellido}",
|
||||
EsSuperAdmin = user.SupAdmin,
|
||||
DebeCambiarClave = debeCambiar
|
||||
// No necesitamos pasar la lista de permisos aquí, ya irán en el token
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> ChangePasswordAsync(int userId, ChangePasswordRequestDto changePasswordRequest)
|
||||
{
|
||||
// 1. Obtener el usuario actual (necesitamos su hash/salt actual)
|
||||
var user = await _authRepository.GetUserByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("ChangePassword attempt failed: User with ID {UserId} not found.", userId);
|
||||
return false; // Usuario no encontrado
|
||||
}
|
||||
|
||||
// 2. Verificar la contraseña ACTUAL
|
||||
if (!_passwordHasher.VerifyPassword(changePasswordRequest.CurrentPassword, user.ClaveHash, user.ClaveSalt))
|
||||
{
|
||||
_logger.LogWarning("ChangePassword attempt failed: Invalid current password for user ID {UserId}.", userId);
|
||||
return false; // Contraseña actual incorrecta
|
||||
}
|
||||
|
||||
// 3. Verificar que la nueva contraseña no sea igual al nombre de usuario
|
||||
if (user.User == changePasswordRequest.NewPassword)
|
||||
{
|
||||
_logger.LogWarning("ChangePassword attempt failed: New password cannot be the same as username for user ID {UserId}.", userId);
|
||||
// Podrías lanzar una excepción o devolver un código de error específico
|
||||
// Por simplicidad, devolvemos false
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Generar nuevo hash y salt para la NUEVA contraseña
|
||||
(string newHash, string newSalt) = _passwordHasher.HashPassword(changePasswordRequest.NewPassword);
|
||||
|
||||
// 5. Actualizar en la base de datos (el repositorio también pone DebeCambiarClave = 0)
|
||||
bool success = await _authRepository.UpdatePasswordAsync(userId, newHash, newSalt);
|
||||
|
||||
if (success)
|
||||
{
|
||||
_logger.LogInformation("Password changed successfully for user ID {UserId}.", userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Failed to update password in database for user ID {UserId}.", userId);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// --- GenerateJwtToken sin cambios ---
|
||||
private string GenerateJwtToken(Usuario user, IEnumerable<string> permisosCodAcc) // Recibir permisos
|
||||
{
|
||||
var jwtSettings = _configuration.GetSection("Jwt");
|
||||
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"]
|
||||
?? throw new ArgumentNullException("Jwt:Key", "JWT Key not configured"));
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Name, user.User),
|
||||
new Claim(JwtRegisteredClaimNames.GivenName, user.Nombre),
|
||||
new Claim(JwtRegisteredClaimNames.FamilyName, user.Apellido),
|
||||
new Claim("idPerfil", user.IdPerfil.ToString()),
|
||||
new Claim("debeCambiarClave", user.DebeCambiarClave.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
};
|
||||
|
||||
// Añadir rol basado en SupAdmin o IdPerfil
|
||||
if (user.SupAdmin)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, "SuperAdmin"));
|
||||
// Opcional: Si SuperAdmin tiene todos los permisos, podrías añadir un claim especial
|
||||
// claims.Add(new Claim("permission", "*"));
|
||||
}
|
||||
else
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, $"Perfil_{user.IdPerfil}"));
|
||||
// Añadir cada código de permiso como un claim "permission"
|
||||
if (permisosCodAcc != null)
|
||||
{
|
||||
foreach (var codAcc in permisosCodAcc)
|
||||
{
|
||||
claims.Add(new Claim("permission", codAcc)); // Usar "permission" como tipo de claim
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.UtcNow.AddHours(Convert.ToInt32(jwtSettings["DurationInHours"] ?? "1")),
|
||||
Issuer = jwtSettings["Issuer"],
|
||||
Audience = jwtSettings["Audience"],
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using GestionIntegral.Api.Dtos;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public interface IAuthService
|
||||
{
|
||||
Task<LoginResponseDto?> LoginAsync(LoginRequestDto loginRequest);
|
||||
Task<bool> ChangePasswordAsync(int userId, ChangePasswordRequestDto changePasswordRequest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using GestionIntegral.Api.Dtos.Usuarios;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public interface IPerfilService
|
||||
{
|
||||
Task<IEnumerable<PerfilDto>> ObtenerTodosAsync(string? nombreFilter);
|
||||
Task<PerfilDto?> ObtenerPorIdAsync(int id);
|
||||
Task<(PerfilDto? Perfil, string? Error)> CrearAsync(CreatePerfilDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdatePerfilDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
||||
Task<IEnumerable<PermisoAsignadoDto>> ObtenerPermisosAsignadosAsync(int idPerfil);
|
||||
Task<(bool Exito, string? Error)> ActualizarPermisosAsignadosAsync(int idPerfil, ActualizarPermisosPerfilRequestDto request, int idUsuarioModificador);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using GestionIntegral.Api.Dtos.Usuarios;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public interface IPermisoService
|
||||
{
|
||||
Task<IEnumerable<PermisoDto>> ObtenerTodosAsync(string? moduloFilter, string? codAccFilter);
|
||||
Task<PermisoDto?> ObtenerPorIdAsync(int id);
|
||||
Task<(PermisoDto? Permiso, string? Error)> CrearAsync(CreatePermisoDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdatePermisoDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using GestionIntegral.Api.Dtos.Usuarios;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public interface IUsuarioService
|
||||
{
|
||||
Task<IEnumerable<UsuarioDto>> ObtenerTodosAsync(string? userFilter, string? nombreFilter);
|
||||
Task<UsuarioDto?> ObtenerPorIdAsync(int id);
|
||||
Task<(UsuarioDto? Usuario, string? Error)> CrearAsync(CreateUsuarioRequestDto createDto, int idUsuarioCreador);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateUsuarioRequestDto updateDto, int idUsuarioModificador);
|
||||
Task<(bool Exito, string? Error)> SetPasswordAsync(int userId, SetPasswordRequestDto setPasswordDto, int idUsuarioModificador);
|
||||
// Habilitar/Deshabilitar podría ser un método separado o parte de UpdateAsync
|
||||
Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public class PasswordHasherService
|
||||
{
|
||||
private const int SaltSize = 16; // 128 bit
|
||||
private const int HashSize = 32; // 256 bit
|
||||
private const int Iterations = 10000; // Número de iteraciones (ajustable)
|
||||
|
||||
// Genera un hash y una salt para una contraseña dada
|
||||
public (string hash, string salt) HashPassword(string password)
|
||||
{
|
||||
// Generar una salt aleatoria
|
||||
byte[] saltBytes = RandomNumberGenerator.GetBytes(SaltSize);
|
||||
|
||||
// Crear el hash usando PBKDF2
|
||||
var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, Iterations, HashAlgorithmName.SHA256);
|
||||
byte[] hashBytes = pbkdf2.GetBytes(HashSize);
|
||||
|
||||
// Convertir bytes a strings Base64 para almacenamiento
|
||||
string saltString = Convert.ToBase64String(saltBytes);
|
||||
string hashString = Convert.ToBase64String(hashBytes);
|
||||
|
||||
return (hashString, saltString);
|
||||
}
|
||||
|
||||
// Verifica si una contraseña coincide con un hash y salt almacenados
|
||||
public bool VerifyPassword(string password, string storedHash, string storedSalt)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Convertir strings Base64 de vuelta a bytes
|
||||
byte[] saltBytes = Convert.FromBase64String(storedSalt);
|
||||
byte[] storedHashBytes = Convert.FromBase64String(storedHash);
|
||||
|
||||
// Crear el hash de la contraseña ingresada usando la misma salt e iteraciones
|
||||
var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, Iterations, HashAlgorithmName.SHA256);
|
||||
byte[] testHashBytes = pbkdf2.GetBytes(HashSize);
|
||||
|
||||
// Comparar los hashes de forma segura (evita timing attacks)
|
||||
return CryptographicOperations.FixedTimeEquals(storedHashBytes, testHashBytes);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Manejar el caso donde las strings almacenadas no son Base64 válidas
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Loggear la excepción si es necesario
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
225
Backend/GestionIntegral.Api/Services/Usuarios/PerfilService.cs
Normal file
225
Backend/GestionIntegral.Api/Services/Usuarios/PerfilService.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using GestionIntegral.Api.Data; // Para DbConnectionFactory
|
||||
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
||||
using GestionIntegral.Api.Dtos.Usuarios;
|
||||
using GestionIntegral.Api.Models.Usuarios;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Data; // Para IsolationLevel
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public class PerfilService : IPerfilService
|
||||
{
|
||||
private readonly IPerfilRepository _perfilRepository;
|
||||
private readonly DbConnectionFactory _connectionFactory; // Necesario para transacciones
|
||||
private readonly ILogger<PerfilService> _logger;
|
||||
private readonly IPermisoRepository _permisoRepository;
|
||||
|
||||
public PerfilService(IPerfilRepository perfilRepository, IPermisoRepository permisoRepository, DbConnectionFactory connectionFactory, ILogger<PerfilService> logger)
|
||||
{
|
||||
_perfilRepository = perfilRepository;
|
||||
_permisoRepository = permisoRepository;
|
||||
_connectionFactory = connectionFactory; // Inyectar DbConnectionFactory
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private PerfilDto MapToDto(Perfil perfil) => new PerfilDto
|
||||
{
|
||||
Id = perfil.Id,
|
||||
NombrePerfil = perfil.NombrePerfil,
|
||||
Descripcion = perfil.Descripcion
|
||||
};
|
||||
|
||||
public async Task<IEnumerable<PerfilDto>> ObtenerTodosAsync(string? nombreFilter)
|
||||
{
|
||||
var perfiles = await _perfilRepository.GetAllAsync(nombreFilter);
|
||||
return perfiles.Select(MapToDto);
|
||||
}
|
||||
|
||||
public async Task<PerfilDto?> ObtenerPorIdAsync(int id)
|
||||
{
|
||||
var perfil = await _perfilRepository.GetByIdAsync(id);
|
||||
return perfil == null ? null : MapToDto(perfil);
|
||||
}
|
||||
|
||||
public async Task<(PerfilDto? Perfil, string? Error)> CrearAsync(CreatePerfilDto createDto, int idUsuario)
|
||||
{
|
||||
if (await _perfilRepository.ExistsByNameAsync(createDto.NombrePerfil))
|
||||
{
|
||||
return (null, "El nombre del perfil ya existe.");
|
||||
}
|
||||
|
||||
var nuevoPerfil = new Perfil { NombrePerfil = createDto.NombrePerfil, Descripcion = createDto.Descripcion };
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
try
|
||||
{
|
||||
var perfilCreado = await _perfilRepository.CreateAsync(nuevoPerfil, idUsuario, transaction);
|
||||
if (perfilCreado == null) throw new DataException("La creación en el repositorio devolvió null.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Perfil ID {IdPerfil} creado por Usuario ID {IdUsuario}.", perfilCreado.Id, idUsuario);
|
||||
return (MapToDto(perfilCreado), null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback CrearAsync Perfil."); }
|
||||
_logger.LogError(ex, "Error CrearAsync Perfil. Nombre: {NombrePerfil}", createDto.NombrePerfil);
|
||||
return (null, $"Error interno al crear el perfil: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdatePerfilDto updateDto, int idUsuario)
|
||||
{
|
||||
// Verificar existencia ANTES de iniciar la transacción para evitar trabajo innecesario
|
||||
var perfilExistente = await _perfilRepository.GetByIdAsync(id);
|
||||
if (perfilExistente == null) return (false, "Perfil no encontrado.");
|
||||
|
||||
if (await _perfilRepository.ExistsByNameAsync(updateDto.NombrePerfil, id))
|
||||
{
|
||||
return (false, "El nombre del perfil ya existe para otro registro.");
|
||||
}
|
||||
|
||||
var perfilAActualizar = new Perfil { Id = id, NombrePerfil = updateDto.NombrePerfil, Descripcion = updateDto.Descripcion };
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
try
|
||||
{
|
||||
var actualizado = await _perfilRepository.UpdateAsync(perfilAActualizar, idUsuario, transaction);
|
||||
if (!actualizado)
|
||||
{
|
||||
// El repositorio ahora lanza KeyNotFoundException si no lo encuentra DENTRO de la tx.
|
||||
// Si devuelve false sin excepción, podría ser otro error.
|
||||
throw new DataException("La operación de actualización no afectó ninguna fila o falló.");
|
||||
}
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Perfil ID {IdPerfil} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException knfex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarAsync Perfil (KeyNotFound)."); }
|
||||
_logger.LogWarning(knfex, "Intento de actualizar Perfil ID: {Id} no encontrado dentro de la transacción.", id);
|
||||
return (false, "Perfil no encontrado.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarAsync Perfil."); }
|
||||
_logger.LogError(ex, "Error ActualizarAsync Perfil ID: {Id}", id);
|
||||
return (false, $"Error interno al actualizar el perfil: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
|
||||
{
|
||||
// Verificar existencia y si está en uso ANTES de la transacción
|
||||
var perfilExistente = await _perfilRepository.GetByIdAsync(id);
|
||||
if (perfilExistente == null) return (false, "Perfil no encontrado.");
|
||||
|
||||
if (await _perfilRepository.IsInUseAsync(id))
|
||||
{
|
||||
return (false, "No se puede eliminar. El perfil está asignado a usuarios o permisos.");
|
||||
}
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
try
|
||||
{
|
||||
var eliminado = await _perfilRepository.DeleteAsync(id, idUsuario, transaction);
|
||||
if (!eliminado)
|
||||
{
|
||||
throw new DataException("La operación de eliminación no afectó ninguna fila o falló.");
|
||||
}
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Perfil ID {IdPerfil} eliminado por Usuario ID {IdUsuario}.", id, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException knfex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback EliminarAsync Perfil (KeyNotFound)."); }
|
||||
_logger.LogWarning(knfex, "Intento de eliminar Perfil ID: {Id} no encontrado dentro de la transacción.", id);
|
||||
return (false, "Perfil no encontrado.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback EliminarAsync Perfil."); }
|
||||
_logger.LogError(ex, "Error EliminarAsync Perfil ID: {Id}", id);
|
||||
return (false, $"Error interno al eliminar el perfil: {ex.Message}");
|
||||
}
|
||||
}
|
||||
public async Task<IEnumerable<PermisoAsignadoDto>> ObtenerPermisosAsignadosAsync(int idPerfil)
|
||||
{
|
||||
// 1. Obtener todos los permisos definidos en el sistema
|
||||
var todosLosPermisos = await _permisoRepository.GetAllAsync(null, null); // Sin filtros
|
||||
|
||||
// 2. Obtener los IDs de los permisos actualmente asignados a este perfil
|
||||
var idsPermisosAsignados = (await _perfilRepository.GetPermisoIdsByPerfilIdAsync(idPerfil)).ToHashSet();
|
||||
|
||||
// 3. Mapear a DTO, marcando 'Asignado'
|
||||
var resultado = todosLosPermisos.Select(p => new PermisoAsignadoDto
|
||||
{
|
||||
Id = p.Id,
|
||||
Modulo = p.Modulo,
|
||||
DescPermiso = p.DescPermiso,
|
||||
CodAcc = p.CodAcc,
|
||||
Asignado = idsPermisosAsignados.Contains(p.Id)
|
||||
}).OrderBy(p => p.Modulo).ThenBy(p => p.DescPermiso); // Ordenar para la UI
|
||||
|
||||
return resultado;
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarPermisosAsignadosAsync(
|
||||
int idPerfil,
|
||||
ActualizarPermisosPerfilRequestDto request,
|
||||
int idUsuarioModificador)
|
||||
{
|
||||
// Validación: Verificar que el perfil exista
|
||||
var perfil = await _perfilRepository.GetByIdAsync(idPerfil);
|
||||
if (perfil == null)
|
||||
{
|
||||
return (false, "Perfil no encontrado.");
|
||||
}
|
||||
|
||||
// Validación opcional: Verificar que todos los IDs de permisos en la solicitud sean válidos
|
||||
if (request.PermisosIds != null && request.PermisosIds.Any())
|
||||
{
|
||||
var permisosValidos = await _permisoRepository.GetPermisosByIdsAsync(request.PermisosIds);
|
||||
if (permisosValidos.Count() != request.PermisosIds.Distinct().Count()) // Compara counts para detectar IDs inválidos
|
||||
{
|
||||
return (false, "Uno o más IDs de permisos proporcionados son inválidos.");
|
||||
}
|
||||
}
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
try
|
||||
{
|
||||
// El idUsuarioModificador no se usa directamente en UpdatePermisosByPerfilIdAsync
|
||||
// porque no estamos grabando en una tabla _H para gral_PermisosPerfiles.
|
||||
// Si se necesitara auditoría de esta acción específica, se debería añadir.
|
||||
await _perfilRepository.UpdatePermisosByPerfilIdAsync(idPerfil, request.PermisosIds ?? new List<int>(), transaction);
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Permisos actualizados para Perfil ID {IdPerfil} por Usuario ID {IdUsuarioModificador}.", idPerfil, idUsuarioModificador);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarPermisosAsignadosAsync."); }
|
||||
_logger.LogError(ex, "Error al actualizar permisos para Perfil ID: {IdPerfil}", idPerfil);
|
||||
return (false, $"Error interno al actualizar los permisos del perfil: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
163
Backend/GestionIntegral.Api/Services/Usuarios/PermisoService.cs
Normal file
163
Backend/GestionIntegral.Api/Services/Usuarios/PermisoService.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
||||
using GestionIntegral.Api.Dtos.Usuarios;
|
||||
using GestionIntegral.Api.Models.Usuarios;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public class PermisoService : IPermisoService
|
||||
{
|
||||
private readonly IPermisoRepository _permisoRepository;
|
||||
private readonly DbConnectionFactory _connectionFactory; // Inyectar para transacciones
|
||||
private readonly ILogger<PermisoService> _logger;
|
||||
|
||||
public PermisoService(IPermisoRepository permisoRepository, DbConnectionFactory connectionFactory, ILogger<PermisoService> logger)
|
||||
{
|
||||
_permisoRepository = permisoRepository;
|
||||
_connectionFactory = connectionFactory; // Asignar
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private PermisoDto MapToDto(Permiso permiso) => new PermisoDto
|
||||
{
|
||||
Id = permiso.Id,
|
||||
Modulo = permiso.Modulo,
|
||||
DescPermiso = permiso.DescPermiso,
|
||||
CodAcc = permiso.CodAcc
|
||||
};
|
||||
|
||||
public async Task<IEnumerable<PermisoDto>> ObtenerTodosAsync(string? moduloFilter, string? codAccFilter)
|
||||
{
|
||||
var permisos = await _permisoRepository.GetAllAsync(moduloFilter, codAccFilter);
|
||||
return permisos.Select(MapToDto);
|
||||
}
|
||||
|
||||
public async Task<PermisoDto?> ObtenerPorIdAsync(int id)
|
||||
{
|
||||
var permiso = await _permisoRepository.GetByIdAsync(id);
|
||||
return permiso == null ? null : MapToDto(permiso);
|
||||
}
|
||||
|
||||
public async Task<(PermisoDto? Permiso, string? Error)> CrearAsync(CreatePermisoDto createDto, int idUsuario)
|
||||
{
|
||||
if (await _permisoRepository.ExistsByCodAccAsync(createDto.CodAcc))
|
||||
{
|
||||
return (null, "El código de acceso (CodAcc) ya existe.");
|
||||
}
|
||||
|
||||
var nuevoPermiso = new Permiso
|
||||
{
|
||||
Modulo = createDto.Modulo,
|
||||
DescPermiso = createDto.DescPermiso,
|
||||
CodAcc = createDto.CodAcc
|
||||
};
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
try
|
||||
{
|
||||
var permisoCreado = await _permisoRepository.CreateAsync(nuevoPermiso, idUsuario, transaction);
|
||||
if (permisoCreado == null) throw new DataException("La creación en el repositorio devolvió null.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Permiso ID {IdPermiso} creado por Usuario ID {IdUsuario}.", permisoCreado.Id, idUsuario);
|
||||
return (MapToDto(permisoCreado), null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback CrearAsync Permiso."); }
|
||||
_logger.LogError(ex, "Error CrearAsync Permiso. CodAcc: {CodAcc}", createDto.CodAcc);
|
||||
return (null, $"Error interno al crear el permiso: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdatePermisoDto updateDto, int idUsuario)
|
||||
{
|
||||
var permisoExistente = await _permisoRepository.GetByIdAsync(id); // Verificar existencia fuera de la TX
|
||||
if (permisoExistente == null) return (false, "Permiso no encontrado.");
|
||||
|
||||
if (await _permisoRepository.ExistsByCodAccAsync(updateDto.CodAcc, id))
|
||||
{
|
||||
return (false, "El código de acceso (CodAcc) ya existe para otro permiso.");
|
||||
}
|
||||
|
||||
var permisoAActualizar = new Permiso
|
||||
{
|
||||
Id = id,
|
||||
Modulo = updateDto.Modulo,
|
||||
DescPermiso = updateDto.DescPermiso,
|
||||
CodAcc = updateDto.CodAcc
|
||||
};
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
try
|
||||
{
|
||||
var actualizado = await _permisoRepository.UpdateAsync(permisoAActualizar, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("La operación de actualización no afectó ninguna fila.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Permiso ID {IdPermiso} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException knfex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarAsync Permiso (KeyNotFound)."); }
|
||||
_logger.LogWarning(knfex, "Intento de actualizar Permiso ID: {Id} no encontrado dentro de la transacción.", id);
|
||||
return (false, "Permiso no encontrado.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarAsync Permiso."); }
|
||||
_logger.LogError(ex, "Error ActualizarAsync Permiso ID: {Id}", id);
|
||||
return (false, $"Error interno al actualizar el permiso: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
|
||||
{
|
||||
var permisoExistente = await _permisoRepository.GetByIdAsync(id); // Verificar existencia fuera de la TX
|
||||
if (permisoExistente == null) return (false, "Permiso no encontrado.");
|
||||
|
||||
if (await _permisoRepository.IsInUseAsync(id))
|
||||
{
|
||||
return (false, "No se puede eliminar. El permiso está asignado a uno o más perfiles.");
|
||||
}
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
try
|
||||
{
|
||||
var eliminado = await _permisoRepository.DeleteAsync(id, idUsuario, transaction);
|
||||
if (!eliminado) throw new DataException("La operación de eliminación no afectó ninguna fila.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Permiso ID {IdPermiso} eliminado por Usuario ID {IdUsuario}.", id, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException knfex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback EliminarAsync Permiso (KeyNotFound)."); }
|
||||
_logger.LogWarning(knfex, "Intento de eliminar Permiso ID: {Id} no encontrado dentro de la transacción.", id);
|
||||
return (false, "Permiso no encontrado.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback EliminarAsync Permiso."); }
|
||||
_logger.LogError(ex, "Error EliminarAsync Permiso ID: {Id}", id);
|
||||
return (false, $"Error interno al eliminar el permiso: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
246
Backend/GestionIntegral.Api/Services/Usuarios/UsuarioService.cs
Normal file
246
Backend/GestionIntegral.Api/Services/Usuarios/UsuarioService.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
||||
using GestionIntegral.Api.Dtos.Usuarios;
|
||||
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Usuarios
|
||||
{
|
||||
public class UsuarioService : IUsuarioService
|
||||
{
|
||||
private readonly IUsuarioRepository _usuarioRepository;
|
||||
private readonly IPerfilRepository _perfilRepository;
|
||||
private readonly PasswordHasherService _passwordHasher;
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<UsuarioService> _logger;
|
||||
|
||||
public UsuarioService(
|
||||
IUsuarioRepository usuarioRepository,
|
||||
IPerfilRepository perfilRepository,
|
||||
PasswordHasherService passwordHasher,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<UsuarioService> logger)
|
||||
{
|
||||
_usuarioRepository = usuarioRepository;
|
||||
_perfilRepository = perfilRepository;
|
||||
_passwordHasher = passwordHasher;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// CORREGIDO: MapToDto ahora acepta una tupla con tipos anulables
|
||||
private UsuarioDto? MapToDto((Usuario? usuario, string? nombrePerfil) data)
|
||||
{
|
||||
if (data.usuario == null) return null; // Si el usuario es null, no se puede mapear
|
||||
|
||||
return new UsuarioDto
|
||||
{
|
||||
Id = data.usuario.Id,
|
||||
User = data.usuario.User,
|
||||
Habilitada = data.usuario.Habilitada,
|
||||
SupAdmin = data.usuario.SupAdmin,
|
||||
Nombre = data.usuario.Nombre,
|
||||
Apellido = data.usuario.Apellido,
|
||||
IdPerfil = data.usuario.IdPerfil,
|
||||
NombrePerfil = data.nombrePerfil ?? "N/A", // Manejar null para nombrePerfil
|
||||
DebeCambiarClave = data.usuario.DebeCambiarClave,
|
||||
VerLog = data.usuario.VerLog
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<UsuarioDto>> ObtenerTodosAsync(string? userFilter, string? nombreFilter)
|
||||
{
|
||||
var usuariosConPerfil = await _usuarioRepository.GetAllWithProfileNameAsync(userFilter, nombreFilter);
|
||||
// Filtrar los nulos que MapToDto podría devolver (aunque no debería en este caso si GetAllWithProfileNameAsync no devuelve usuarios nulos en la tupla)
|
||||
return usuariosConPerfil.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!);
|
||||
}
|
||||
|
||||
public async Task<UsuarioDto?> ObtenerPorIdAsync(int id)
|
||||
{
|
||||
var data = await _usuarioRepository.GetByIdWithProfileNameAsync(id);
|
||||
// MapToDto ya maneja el caso donde data.Usuario es null y devuelve null.
|
||||
return MapToDto(data);
|
||||
}
|
||||
|
||||
|
||||
public async Task<(UsuarioDto? Usuario, string? Error)> CrearAsync(CreateUsuarioRequestDto createDto, int idUsuarioCreador)
|
||||
{
|
||||
if (await _usuarioRepository.UserExistsAsync(createDto.User))
|
||||
{
|
||||
return (null, "El nombre de usuario ya existe.");
|
||||
}
|
||||
var perfilSeleccionado = await _perfilRepository.GetByIdAsync(createDto.IdPerfil);
|
||||
if (perfilSeleccionado == null)
|
||||
{
|
||||
return (null, "El perfil seleccionado no es válido.");
|
||||
}
|
||||
if(createDto.User.Equals(createDto.Password, System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (null, "La contraseña no puede ser igual al nombre de usuario.");
|
||||
}
|
||||
|
||||
(string hash, string salt) = _passwordHasher.HashPassword(createDto.Password);
|
||||
|
||||
var nuevoUsuario = new Usuario
|
||||
{
|
||||
User = createDto.User,
|
||||
ClaveHash = hash,
|
||||
ClaveSalt = salt,
|
||||
Habilitada = createDto.Habilitada,
|
||||
SupAdmin = createDto.SupAdmin,
|
||||
Nombre = createDto.Nombre,
|
||||
Apellido = createDto.Apellido,
|
||||
IdPerfil = createDto.IdPerfil,
|
||||
VerLog = createDto.VerLog,
|
||||
DebeCambiarClave = createDto.DebeCambiarClave
|
||||
};
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
var usuarioCreado = await _usuarioRepository.CreateAsync(nuevoUsuario, idUsuarioCreador, transaction);
|
||||
if (usuarioCreado == null) throw new DataException("Error al crear el usuario en el repositorio.");
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
// Construir el DTO para la respuesta
|
||||
var dto = new UsuarioDto {
|
||||
Id = usuarioCreado.Id, User = usuarioCreado.User, Habilitada = usuarioCreado.Habilitada, SupAdmin = usuarioCreado.SupAdmin,
|
||||
Nombre = usuarioCreado.Nombre, Apellido = usuarioCreado.Apellido, IdPerfil = usuarioCreado.IdPerfil,
|
||||
NombrePerfil = perfilSeleccionado.NombrePerfil, // Usamos el nombre del perfil ya obtenido
|
||||
DebeCambiarClave = usuarioCreado.DebeCambiarClave, VerLog = usuarioCreado.VerLog
|
||||
};
|
||||
_logger.LogInformation("Usuario ID {UsuarioId} creado por Usuario ID {CreadorId}.", usuarioCreado.Id, idUsuarioCreador);
|
||||
return (dto, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { /* Log */ }
|
||||
_logger.LogError(ex, "Error CrearAsync Usuario. User: {User}", createDto.User);
|
||||
return (null, $"Error interno al crear el usuario: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateUsuarioRequestDto updateDto, int idUsuarioModificador)
|
||||
{
|
||||
var usuarioExistente = await _usuarioRepository.GetByIdAsync(id);
|
||||
if (usuarioExistente == null) return (false, "Usuario no encontrado.");
|
||||
|
||||
if (await _perfilRepository.GetByIdAsync(updateDto.IdPerfil) == null)
|
||||
{
|
||||
return (false, "El perfil seleccionado no es válido.");
|
||||
}
|
||||
|
||||
usuarioExistente.Nombre = updateDto.Nombre;
|
||||
usuarioExistente.Apellido = updateDto.Apellido;
|
||||
usuarioExistente.IdPerfil = updateDto.IdPerfil;
|
||||
usuarioExistente.Habilitada = updateDto.Habilitada;
|
||||
usuarioExistente.SupAdmin = updateDto.SupAdmin;
|
||||
usuarioExistente.DebeCambiarClave = updateDto.DebeCambiarClave;
|
||||
usuarioExistente.VerLog = updateDto.VerLog;
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
var actualizado = await _usuarioRepository.UpdateAsync(usuarioExistente, idUsuarioModificador, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar el usuario en el repositorio.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Usuario ID {UsuarioId} actualizado por Usuario ID {ModificadorId}.", id, idUsuarioModificador);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) {
|
||||
try { transaction.Rollback(); } catch { /* Log */ }
|
||||
return (false, "Usuario no encontrado durante la actualización.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { /* Log */ }
|
||||
_logger.LogError(ex, "Error ActualizarAsync Usuario ID: {UsuarioId}", id);
|
||||
return (false, $"Error interno al actualizar el usuario: {ex.Message}");
|
||||
}
|
||||
}
|
||||
public async Task<(bool Exito, string? Error)> SetPasswordAsync(int userId, SetPasswordRequestDto setPasswordDto, int idUsuarioModificador)
|
||||
{
|
||||
var usuario = await _usuarioRepository.GetByIdAsync(userId);
|
||||
if (usuario == null) return (false, "Usuario no encontrado.");
|
||||
|
||||
if(usuario.User.Equals(setPasswordDto.NewPassword, System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (false, "La nueva contraseña no puede ser igual al nombre de usuario.");
|
||||
}
|
||||
|
||||
(string hash, string salt) = _passwordHasher.HashPassword(setPasswordDto.NewPassword);
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var success = await _usuarioRepository.SetPasswordAsync(userId, hash, salt, setPasswordDto.ForceChangeOnNextLogin, idUsuarioModificador, transaction);
|
||||
if(!success) throw new DataException("Error al actualizar la contraseña en el repositorio.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Contraseña establecida para Usuario ID {TargetUserId} por Usuario ID {AdminUserId}.", userId, idUsuarioModificador);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) {
|
||||
try { transaction.Rollback(); } catch { /* Log */ }
|
||||
return (false, "Usuario no encontrado durante el cambio de contraseña.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { /* Log */ }
|
||||
_logger.LogError(ex, "Error SetPasswordAsync para Usuario ID {TargetUserId}.", userId);
|
||||
return (false, $"Error interno al establecer la contraseña: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador)
|
||||
{
|
||||
var usuario = await _usuarioRepository.GetByIdAsync(userId);
|
||||
if (usuario == null) return (false, "Usuario no encontrado.");
|
||||
|
||||
if (usuario.Habilitada == habilitar)
|
||||
{
|
||||
return (true, null); // No hay cambio necesario
|
||||
}
|
||||
|
||||
usuario.Habilitada = habilitar;
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var actualizado = await _usuarioRepository.UpdateAsync(usuario, idUsuarioModificador, transaction);
|
||||
if (!actualizado) throw new DataException("Error al cambiar estado de habilitación del usuario en el repositorio.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Estado de habilitación cambiado a {Estado} para Usuario ID {TargetUserId} por Usuario ID {AdminUserId}.", habilitar, userId, idUsuarioModificador);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) {
|
||||
try { transaction.Rollback(); } catch { /* Log */ }
|
||||
return (false, "Usuario no encontrado durante el cambio de estado.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { /* Log */ }
|
||||
_logger.LogError(ex, "Error al cambiar estado de habilitación para Usuario ID {TargetUserId}.", userId);
|
||||
return (false, $"Error interno al cambiar estado de habilitación: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user