using Dapper; using GestionIntegral.Api.Models.Usuarios; using GestionIntegral.Api.Dtos.Usuarios.Auditoria; using System.Data; using System.Text; namespace GestionIntegral.Api.Data.Repositories.Usuarios { public class UsuarioRepository : IUsuarioRepository { private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public UsuarioRepository(DbConnectionFactory connectionFactory, ILogger logger) { _connectionFactory = connectionFactory; _logger = logger; } public async Task> GetAllAsync(string? userFilter, string? nombreFilter) { var sqlBuilder = new StringBuilder("SELECT * FROM dbo.gral_Usuarios WHERE 1=1"); var parameters = new DynamicParameters(); if (!string.IsNullOrWhiteSpace(userFilter)) { sqlBuilder.Append(" AND [User] LIKE @UserParam"); parameters.Add("UserParam", $"%{userFilter}%"); } if (!string.IsNullOrWhiteSpace(nombreFilter)) { sqlBuilder.Append(" AND (Nombre LIKE @NombreParam OR Apellido LIKE @NombreParam)"); parameters.Add("NombreParam", $"%{nombreFilter}%"); } sqlBuilder.Append(" ORDER BY [User];"); try { using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener todos los Usuarios."); return Enumerable.Empty(); } } public async Task> GetAllWithProfileNameAsync(string? userFilter, string? nombreFilter) { var sqlBuilder = new StringBuilder(@" SELECT u.*, p.perfil AS NombrePerfil FROM dbo.gral_Usuarios u INNER JOIN dbo.gral_Perfiles p ON u.IdPerfil = p.id WHERE 1=1"); var parameters = new DynamicParameters(); if (!string.IsNullOrWhiteSpace(userFilter)) { sqlBuilder.Append(" AND u.[User] LIKE @UserParam"); parameters.Add("UserParam", $"%{userFilter}%"); } if (!string.IsNullOrWhiteSpace(nombreFilter)) { sqlBuilder.Append(" AND (u.Nombre LIKE @NombreParam OR u.Apellido LIKE @NombreParam)"); parameters.Add("NombreParam", $"%{nombreFilter}%"); } sqlBuilder.Append(" ORDER BY u.[User];"); try { using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync( sqlBuilder.ToString(), (usuario, nombrePerfil) => (usuario, nombrePerfil), parameters, splitOn: "NombrePerfil" ); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener todos los Usuarios con nombre de perfil."); return Enumerable.Empty<(Usuario, string)>(); } } public async Task GetByIdAsync(int id) { const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id"; try { using var connection = _connectionFactory.CreateConnection(); return await connection.QuerySingleOrDefaultAsync(sql, new { Id = id }); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener Usuario por ID: {UsuarioId}", id); return null; } } public async Task> GetByIdsAsync(IEnumerable ids) { // 1. Validar si la lista de IDs está vacía para evitar una consulta innecesaria a la BD. if (ids == null || !ids.Any()) { return Enumerable.Empty(); } // 2. Definir la consulta. Dapper manejará la expansión de la cláusula IN de forma segura. const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE Id IN @Ids"; try { // 3. Crear conexión y ejecutar la consulta. using var connection = _connectionFactory.CreateConnection(); // 4. Pasar la colección de IDs como parámetro. El nombre 'Ids' debe coincidir con el placeholder '@Ids'. return await connection.QueryAsync(sql, new { Ids = ids }); } catch (Exception ex) { // 5. Registrar el error y devolver una lista vacía en caso de fallo para no romper la aplicación. _logger.LogError(ex, "Error al obtener Usuarios por lista de IDs."); return Enumerable.Empty(); } } public async Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id) { const string sql = @" SELECT u.*, p.perfil AS NombrePerfil FROM dbo.gral_Usuarios u INNER JOIN dbo.gral_Perfiles p ON u.IdPerfil = p.id WHERE u.Id = @Id"; try { using var connection = _connectionFactory.CreateConnection(); var result = await connection.QueryAsync( sql, (usuario, nombrePerfil) => (usuario, nombrePerfil), new { Id = id }, splitOn: "NombrePerfil" ); return result.SingleOrDefault(); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener Usuario por ID con nombre de perfil: {UsuarioId}", id); return (null, null); } } public async Task GetByUsernameAsync(string username) { // Esta es la misma que en AuthRepository, si se unifican, se puede eliminar una. const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE [User] = @Username"; try { using var connection = _connectionFactory.CreateConnection(); return await connection.QuerySingleOrDefaultAsync(sql, new { Username = username }); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener Usuario por Username: {Username}", username); return null; } } public async Task UserExistsAsync(string username, int? excludeId = null) { var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.gral_Usuarios WHERE [User] = @Username"); var parameters = new DynamicParameters(); parameters.Add("Username", username); if (excludeId.HasValue) { sqlBuilder.Append(" AND Id != @ExcludeId"); parameters.Add("ExcludeId", excludeId.Value); } try { using var connection = _connectionFactory.CreateConnection(); return await connection.ExecuteScalarAsync(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _logger.LogError(ex, "Error en UserExistsAsync para username: {Username}", username); return true; // Asumir que existe para prevenir duplicados } } public async Task CreateAsync(Usuario nuevoUsuario, int idUsuarioCreador, IDbTransaction transaction) { const string sqlInsert = @" INSERT INTO dbo.gral_Usuarios ([User], ClaveHash, ClaveSalt, Habilitada, SupAdmin, Nombre, Apellido, IdPerfil, VerLog, DebeCambiarClave) OUTPUT INSERTED.* VALUES (@User, @ClaveHash, @ClaveSalt, @Habilitada, @SupAdmin, @Nombre, @Apellido, @IdPerfil, @VerLog, @DebeCambiarClave);"; const string sqlInsertHistorico = @" INSERT INTO dbo.gral_Usuarios_H (IdUsuario, UserNvo, HabilitadaNva, SupAdminNvo, NombreNvo, ApellidoNvo, IdPerfilNvo, DebeCambiarClaveNva, Id_UsuarioMod, FechaMod, TipoMod) VALUES (@IdUsuarioHist, @UserNvoHist, @HabilitadaNvaHist, @SupAdminNvoHist, @NombreNvoHist, @ApellidoNvoHist, @IdPerfilNvoHist, @DebeCambiarClaveNvaHist, @IdUsuarioModHist, @FechaModHist, @TipoModHist);"; var connection = transaction.Connection!; var insertedUsuario = await connection.QuerySingleAsync(sqlInsert, nuevoUsuario, transaction); if (insertedUsuario == null) throw new DataException("No se pudo crear el usuario."); await connection.ExecuteAsync(sqlInsertHistorico, new { IdUsuarioHist = insertedUsuario.Id, UserNvoHist = insertedUsuario.User, HabilitadaNvaHist = insertedUsuario.Habilitada, SupAdminNvoHist = insertedUsuario.SupAdmin, NombreNvoHist = insertedUsuario.Nombre, ApellidoNvoHist = insertedUsuario.Apellido, IdPerfilNvoHist = insertedUsuario.IdPerfil, DebeCambiarClaveNvaHist = insertedUsuario.DebeCambiarClave, IdUsuarioModHist = idUsuarioCreador, FechaModHist = DateTime.Now, TipoModHist = "Creado" }, transaction); return insertedUsuario; } public async Task UpdateAsync(Usuario usuarioAActualizar, int idUsuarioModificador, IDbTransaction transaction) { var connection = transaction.Connection!; var usuarioActual = await connection.QuerySingleOrDefaultAsync( "SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id", new { usuarioAActualizar.Id }, transaction); if (usuarioActual == null) throw new KeyNotFoundException("Usuario no encontrado para actualizar."); // User (nombre de usuario) no se actualiza aquí const string sqlUpdate = @" UPDATE dbo.gral_Usuarios SET Habilitada = @Habilitada, SupAdmin = @SupAdmin, Nombre = @Nombre, Apellido = @Apellido, IdPerfil = @IdPerfil, VerLog = @VerLog, DebeCambiarClave = @DebeCambiarClave WHERE Id = @Id;"; const string sqlInsertHistorico = @" INSERT INTO dbo.gral_Usuarios_H (IdUsuario, UserAnt, UserNvo, HabilitadaAnt, HabilitadaNva, SupAdminAnt, SupAdminNvo, NombreAnt, NombreNvo, ApellidoAnt, ApellidoNvo, IdPerfilAnt, IdPerfilNvo, DebeCambiarClaveAnt, DebeCambiarClaveNva, Id_UsuarioMod, FechaMod, TipoMod) VALUES (@IdUsuarioHist, @UserAntHist, @UserNvoHist, @HabilitadaAntHist, @HabilitadaNvaHist, @SupAdminAntHist, @SupAdminNvoHist, @NombreAntHist, @NombreNvoHist, @ApellidoAntHist, @ApellidoNvoHist, @IdPerfilAntHist, @IdPerfilNvoHist, @DebeCambiarClaveAntHist, @DebeCambiarClaveNvaHist, @IdUsuarioModHist, @FechaModHist, @TipoModHist);"; await connection.ExecuteAsync(sqlInsertHistorico, new { IdUsuarioHist = usuarioActual.Id, UserAntHist = usuarioActual.User, UserNvoHist = usuarioAActualizar.User, // Aunque no cambiemos User, lo registramos HabilitadaAntHist = usuarioActual.Habilitada, HabilitadaNvaHist = usuarioAActualizar.Habilitada, SupAdminAntHist = usuarioActual.SupAdmin, SupAdminNvoHist = usuarioAActualizar.SupAdmin, NombreAntHist = usuarioActual.Nombre, NombreNvoHist = usuarioAActualizar.Nombre, ApellidoAntHist = usuarioActual.Apellido, ApellidoNvoHist = usuarioAActualizar.Apellido, IdPerfilAntHist = usuarioActual.IdPerfil, IdPerfilNvoHist = usuarioAActualizar.IdPerfil, DebeCambiarClaveAntHist = usuarioActual.DebeCambiarClave, DebeCambiarClaveNvaHist = usuarioAActualizar.DebeCambiarClave, IdUsuarioModHist = idUsuarioModificador, FechaModHist = DateTime.Now, TipoModHist = "Actualizado" }, transaction); var rowsAffected = await connection.ExecuteAsync(sqlUpdate, usuarioAActualizar, transaction); return rowsAffected == 1; } public async Task SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction) { var connection = transaction.Connection!; var usuarioActual = await connection.QuerySingleOrDefaultAsync( "SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id", new { Id = userId }, transaction); if (usuarioActual == null) throw new KeyNotFoundException("Usuario no encontrado para cambiar contraseña."); const string sqlUpdate = @"UPDATE dbo.gral_Usuarios SET ClaveHash = @ClaveHash, ClaveSalt = @ClaveSalt, DebeCambiarClave = @DebeCambiarClave WHERE Id = @UserId"; const string sqlInsertHistorico = @" INSERT INTO dbo.gral_Usuarios_H (IdUsuario, UserNvo, HabilitadaNva, SupAdminNvo, NombreNvo, ApellidoNvo, IdPerfilNvo, DebeCambiarClaveAnt, DebeCambiarClaveNva, Id_UsuarioMod, FechaMod, TipoMod) VALUES (@IdUsuarioHist, @UserNvoHist, @HabilitadaNvaHist, @SupAdminNvoHist, @NombreNvoHist, @ApellidoNvoHist, @IdPerfilNvoHist, @DebeCambiarClaveAntHist, @DebeCambiarClaveNvaHist, @IdUsuarioModHist, @FechaModHist, @TipoModHist);"; await connection.ExecuteAsync(sqlInsertHistorico, new { IdUsuarioHist = usuarioActual.Id, UserNvoHist = usuarioActual.User, // No cambia el user HabilitadaNvaHist = usuarioActual.Habilitada, SupAdminNvoHist = usuarioActual.SupAdmin, NombreNvoHist = usuarioActual.Nombre, ApellidoNvoHist = usuarioActual.Apellido, IdPerfilNvoHist = usuarioActual.IdPerfil, DebeCambiarClaveAntHist = usuarioActual.DebeCambiarClave, // Estado anterior de este flag DebeCambiarClaveNvaHist = debeCambiarClave, // Nuevo estado de este flag IdUsuarioModHist = idUsuarioModificador, FechaModHist = DateTime.Now, TipoModHist = "Clave Cambiada" // O "Clave Reseteada" }, transaction); var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { ClaveHash = newHash, ClaveSalt = newSalt, DebeCambiarClave = debeCambiarClave, UserId = userId }, transaction); return rowsAffected == 1; } public async Task> GetHistorialByUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta) { var sqlBuilder = new StringBuilder(@" SELECT h.IdHist, h.IdUsuario AS IdUsuarioAfectado, uPrincipal.[User] AS UserAfectado, -- DELIMITADO CON [] h.UserAnt, h.UserNvo, h.HabilitadaAnt, h.HabilitadaNva, h.SupAdminAnt, h.SupAdminNvo, h.NombreAnt, h.NombreNvo, h.ApellidoAnt, h.ApellidoNvo, h.IdPerfilAnt, h.IdPerfilNvo, pAnt.perfil AS NombrePerfilAnt, pNvo.perfil AS NombrePerfilNvo, h.DebeCambiarClaveAnt, h.DebeCambiarClaveNva, h.Id_UsuarioMod AS IdUsuarioModifico, ISNULL(uMod.Nombre + ' ' + uMod.Apellido, 'Sistema') AS NombreUsuarioModifico, h.FechaMod AS FechaModificacion, h.TipoMod AS TipoModificacion FROM dbo.gral_Usuarios_H h INNER JOIN dbo.gral_Usuarios uPrincipal ON h.IdUsuario = uPrincipal.Id -- No necesita delimitador aquí si 'Id' es el nombre de la columna PK en gral_Usuarios LEFT JOIN dbo.gral_Usuarios uMod ON h.Id_UsuarioMod = uMod.Id LEFT JOIN dbo.gral_Perfiles pAnt ON h.IdPerfilAnt = pAnt.id LEFT JOIN dbo.gral_Perfiles pNvo ON h.IdPerfilNvo = pNvo.id WHERE h.IdUsuario = @IdUsuarioAfectadoParam"); var parameters = new DynamicParameters(); parameters.Add("IdUsuarioAfectadoParam", idUsuarioAfectado); if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); // Hasta el final del día } sqlBuilder.Append(" ORDER BY h.FechaMod DESC, h.IdHist DESC;"); try { using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener historial para Usuario ID: {IdUsuarioAfectado}", idUsuarioAfectado); return Enumerable.Empty(); } } public async Task> GetAllHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter) { var sqlBuilder = new StringBuilder(@" SELECT h.IdHist, h.IdUsuario AS IdUsuarioAfectado, uPrincipal.[User] AS UserAfectado, -- DELIMITADO CON [] h.UserAnt, h.UserNvo, h.HabilitadaAnt, h.HabilitadaNva, h.SupAdminAnt, h.SupAdminNvo, h.NombreAnt, h.NombreNvo, h.ApellidoAnt, h.ApellidoNvo, h.IdPerfilAnt, h.IdPerfilNvo, pAnt.perfil AS NombrePerfilAnt, pNvo.perfil AS NombrePerfilNvo, h.DebeCambiarClaveAnt, h.DebeCambiarClaveNva, h.Id_UsuarioMod AS IdUsuarioModifico, ISNULL(uMod.Nombre + ' ' + uMod.Apellido, 'Sistema') AS NombreUsuarioModifico, h.FechaMod AS FechaModificacion, h.TipoMod AS TipoModificacion FROM dbo.gral_Usuarios_H h INNER JOIN dbo.gral_Usuarios uPrincipal ON h.IdUsuario = uPrincipal.Id LEFT JOIN dbo.gral_Usuarios uMod ON h.Id_UsuarioMod = uMod.Id LEFT JOIN dbo.gral_Perfiles pAnt ON h.IdPerfilAnt = pAnt.id LEFT JOIN dbo.gral_Perfiles pNvo ON h.IdPerfilNvo = pNvo.id WHERE 1=1"); var parameters = new DynamicParameters(); if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); } if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); } if (idUsuarioModificoFilter.HasValue) { sqlBuilder.Append(" AND h.Id_UsuarioMod = @IdUsuarioModFilterParam"); parameters.Add("IdUsuarioModFilterParam", idUsuarioModificoFilter.Value); } if (!string.IsNullOrWhiteSpace(tipoModFilter)) { sqlBuilder.Append(" AND h.TipoMod LIKE @TipoModFilterParam"); parameters.Add("TipoModFilterParam", $"%{tipoModFilter}%"); } sqlBuilder.Append(" ORDER BY h.FechaMod DESC, h.IdHist DESC;"); try { using var connection = _connectionFactory.CreateConnection(); return await connection.QueryAsync(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener todo el historial de Usuarios."); return Enumerable.Empty(); } } } }