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 _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."); } // 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(), 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}"); } } } }