246 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			246 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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}"); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } |