using GestionIntegral.Api.Data; // Para DbConnectionFactory using GestionIntegral.Api.Data.Repositories.Usuarios; using GestionIntegral.Api.Dtos.Auditoria; 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 _logger; private readonly IPermisoRepository _permisoRepository; public PerfilService(IPerfilRepository perfilRepository, IPermisoRepository permisoRepository, DbConnectionFactory connectionFactory, ILogger 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> ObtenerTodosAsync(string? nombreFilter) { var perfiles = await _perfilRepository.GetAllAsync(nombreFilter); return perfiles.Select(MapToDto); } public async Task 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> 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."); } // --- INICIO DE LA LÓGICA DE AUDITORÍA --- 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 { // 1. Obtener los IDs de permisos actuales DENTRO de la transacción para evitar race conditions var idsActuales = (await _perfilRepository.GetPermisoIdsByPerfilIdAsync(idPerfil)).ToHashSet(); var idsNuevos = (request.PermisosIds ?? new List()).ToHashSet(); // 2. Calcular las diferencias var permisosAñadidos = idsNuevos.Where(id => !idsActuales.Contains(id)).ToList(); var permisosQuitados = idsActuales.Where(id => !idsNuevos.Contains(id)).ToList(); // 3. Registrar en el historial foreach (var idPermisoAñadido in permisosAñadidos) { await _perfilRepository.LogPermisoAsignacionHistorialAsync(idPerfil, idPermisoAñadido, idUsuarioModificador, "Asignado", transaction); } foreach (var idPermisoQuitado in permisosQuitados) { await _perfilRepository.LogPermisoAsignacionHistorialAsync(idPerfil, idPermisoQuitado, idUsuarioModificador, "Removido", transaction); } // --- FIN DE LA LÓGICA DE AUDITORÍA --- // 4. Actualizar la tabla principal (tu lógica actual) await _perfilRepository.UpdatePermisosByPerfilIdAsync(idPerfil, idsNuevos, transaction); transaction.Commit(); _logger.LogInformation("Permisos actualizados para Perfil ID {IdPerfil} por Usuario ID {IdUsuarioModificador}. Añadidos: {CountAdded}, Quitados: {CountRemoved}", idPerfil, idUsuarioModificador, permisosAñadidos.Count, permisosQuitados.Count); 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}"); } } public async Task> ObtenerHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, int? idPerfilAfectado) { var historialData = await _perfilRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPerfilAfectado); return historialData.Select(h => new PerfilHistorialDto { IdPerfil = h.Historial.IdPerfil, Perfil = h.Historial.Perfil, DescPerfil = h.Historial.DescPerfil, Id_Usuario = h.Historial.Id_Usuario, NombreUsuarioModifico = h.NombreUsuarioModifico, FechaMod = h.Historial.FechaMod, TipoMod = h.Historial.TipoMod }).ToList(); } public async Task> ObtenerPermisosAsignadosHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, int? idPerfilAfectado, int? idPermisoAfectado) { var historialData = await _perfilRepository.GetPermisosAsignadosHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPerfilAfectado, idPermisoAfectado); return historialData.Select(h => new PermisosPerfilesHistorialDto { IdHistorial = h.Historial.Id, IdPerfil = h.Historial.idPerfil, NombrePerfil = h.NombrePerfil, IdPermiso = h.Historial.idPermiso, DescPermiso = h.DescPermiso, CodAccPermiso = h.CodAccPermiso, Id_Usuario = h.Historial.Id_Usuario, NombreUsuarioModifico = h.NombreUsuarioModifico, FechaMod = h.Historial.FechaMod, TipoMod = h.Historial.TipoMod }).ToList(); } } }