From ee46d9fcf929f3258fa90a6e04f202e2bd60f3f6 Mon Sep 17 00:00:00 2001 From: "Luis.Aguilar" Date: Fri, 16 May 2025 22:41:29 -0600 Subject: [PATCH] =?UTF-8?q?Resuelve=20conflictos=20despu=C3=A9s=20de=20git?= =?UTF-8?q?=20pull?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/alumnos-cursos.php | 115 ++ api/alumnos.php | 121 ++ api/cursos.php | 119 +- api/diploma.php | 44 + api/login.php | 65 +- assets/css/students-management.css | 77 ++ assets/css/styles.css | 1068 +++++++++++++----- assets/js/main.js | 1649 +++++++++++++++++++--------- dashboard.php | 62 +- hash.php | 14 + includes/config.php | 5 +- index.php | 9 +- sql/diplomaster (1).sql | 147 ++- 13 files changed, 2502 insertions(+), 993 deletions(-) create mode 100644 api/alumnos-cursos.php create mode 100644 api/alumnos.php create mode 100644 api/diploma.php create mode 100644 assets/css/students-management.css create mode 100644 hash.php diff --git a/api/alumnos-cursos.php b/api/alumnos-cursos.php new file mode 100644 index 0000000..32165b3 --- /dev/null +++ b/api/alumnos-cursos.php @@ -0,0 +1,115 @@ + false, 'error' => 'No autenticado']); + exit; +} + +$method = $_SERVER['REQUEST_METHOD']; +$profesorId = $_SESSION['profesor']['id'] ?? null; + +try { + switch ($method) { + case 'GET': + $alumnoId = $_GET['alumno_id'] ?? null; + + if ($alumnoId) { + // Obtener cursos de un alumno específico + $query = "SELECT c.* FROM cursos c + JOIN alumnos_cursos ac ON c.id = ac.curso_id + WHERE ac.alumno_id = ? AND c.profesor_id = ?"; + $stmt = $pdo->prepare($query); + $stmt->execute([$alumnoId, $profesorId]); + } else { + // Obtener todos los alumnos + $query = "SELECT * FROM alumnos"; + $stmt = $pdo->prepare($query); + $stmt->execute(); + } + + echo json_encode([ + 'success' => true, + 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC) + ]); + break; + + case 'POST': + $data = json_decode(file_get_contents('php://input'), true); + $cursoId = $data['curso_id'] ?? null; + $alumnos = $data['alumnos'] ?? []; + + if (empty($cursoId) || empty($alumnos)) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Datos incompletos']); + exit; + } + + // Verificar que el curso pertenece al profesor + $stmt = $pdo->prepare("SELECT id FROM cursos WHERE id = ? AND profesor_id = ?"); + $stmt->execute([$cursoId, $profesorId]); + + if (!$stmt->fetch()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'No autorizado']); + exit; + } + + foreach ($alumnos as $alumnoId) { + $stmt = $pdo->prepare(" + INSERT INTO alumnos_cursos (alumno_id, curso_id, estado) + VALUES (?, ?, 'cursando') + ON DUPLICATE KEY UPDATE estado = 'cursando' + "); + $stmt->execute([$alumnoId, $cursoId]); + } + + echo json_encode([ + 'success' => true, + 'message' => 'Alumnos asignados correctamente' + ]); + break; + + + + case 'DELETE': + $alumnoId = $_GET['alumno_id'] ?? null; + $cursoId = $_GET['curso_id'] ?? null; + + if (!$alumnoId || !$cursoId) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'IDs requeridos']); + exit; + } + + // Verificar que el curso pertenece al profesor + $stmt = $pdo->prepare("SELECT id FROM cursos WHERE id = ? AND profesor_id = ?"); + $stmt->execute([$cursoId, $profesorId]); + + if (!$stmt->fetch()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'No autorizado']); + exit; + } + + // Eliminar asignación + $stmt = $pdo->prepare("DELETE FROM alumnos_cursos WHERE alumno_id = ? AND curso_id = ?"); + $stmt->execute([$alumnoId, $cursoId]); + + echo json_encode([ + 'success' => true, + 'message' => 'Alumno desvinculado del curso correctamente' + ]); + break; + + default: + http_response_code(405); + echo json_encode(['success' => false, 'error' => 'Método no permitido']); + } +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Error en la base de datos: ' . $e->getMessage()]); +} +?> \ No newline at end of file diff --git a/api/alumnos.php b/api/alumnos.php new file mode 100644 index 0000000..27adf55 --- /dev/null +++ b/api/alumnos.php @@ -0,0 +1,121 @@ + false, 'error' => 'No autenticado']); + exit; +} + +$method = $_SERVER['REQUEST_METHOD']; + +try { + switch ($method) { + case 'GET': + // Obtener todos los alumnos + $query = "SELECT * FROM alumnos ORDER BY nombre ASC"; + $stmt = $pdo->prepare($query); + $stmt->execute(); + + $alumnos = $stmt->fetchAll(PDO::FETCH_ASSOC); + + echo json_encode([ + 'success' => true, + 'data' => $alumnos, + 'count' => count($alumnos) + ]); + break; + + case 'POST': + $data = json_decode(file_get_contents('php://input'), true); + + if (empty($data['nombre']) || empty($data['email'])) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Nombre y email son requeridos']); + exit; + } + + $stmt = $pdo->prepare(" + INSERT INTO alumnos (nombre, email, telefono) + VALUES (?, ?, ?) + "); + $stmt->execute([ + $data['nombre'], + $data['email'], + $data['telefono'] ?? null + ]); + + echo json_encode([ + 'success' => true, + 'id' => $pdo->lastInsertId(), + 'message' => 'Alumno creado exitosamente' + ]); + break; + + case 'PUT': + $data = json_decode(file_get_contents('php://input'), true); + + if (empty($data['id']) || empty($data['nombre']) || empty($data['email'])) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Datos incompletos']); + exit; + } + + $stmt = $pdo->prepare(" + UPDATE alumnos SET + nombre = ?, + email = ?, + telefono = ? + WHERE id = ? + "); + $stmt->execute([ + $data['nombre'], + $data['email'], + $data['telefono'] ?? null, + $data['id'] + ]); + + echo json_encode([ + 'success' => true, + 'message' => 'Alumno actualizado exitosamente' + ]); + break; + + case 'DELETE': + $id = $_GET['id'] ?? null; + if (!$id) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'ID de alumno no proporcionado']); + exit; + } + + // Verificar si el alumno está asignado a algún curso primero + $stmt = $pdo->prepare("SELECT COUNT(*) FROM alumnos_cursos WHERE alumno_id = ?"); + $stmt->execute([$id]); + $tieneCursos = $stmt->fetchColumn(); + + if ($tieneCursos > 0) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'No se puede eliminar, el alumno está asignado a cursos']); + exit; + } + + $stmt = $pdo->prepare("DELETE FROM alumnos WHERE id = ?"); + $stmt->execute([$id]); + + echo json_encode([ + 'success' => true, + 'message' => 'Alumno eliminado exitosamente' + ]); + break; + + default: + http_response_code(405); + echo json_encode(['success' => false, 'error' => 'Método no permitido']); + } +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Error en la base de datos: ' . $e->getMessage()]); +} +?> \ No newline at end of file diff --git a/api/cursos.php b/api/cursos.php index 7f30f4f..a29f6ea 100644 --- a/api/cursos.php +++ b/api/cursos.php @@ -9,30 +9,123 @@ if (!is_logged_in()) { } $method = $_SERVER['REQUEST_METHOD']; +$profesorId = $_SESSION['profesor']['id']; switch ($method) { case 'GET': - $stmt = $pdo->query("SELECT * FROM cursos"); - echo json_encode($stmt->fetchAll()); + try { + $query = "SELECT * FROM cursos WHERE profesor_id = ?"; + $stmt = $pdo->prepare($query); + $stmt->execute([$profesorId]); + echo json_encode($stmt->fetchAll()); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Error al cargar cursos']); + } break; case 'POST': $data = json_decode(file_get_contents('php://input'), true); - $stmt = $pdo->prepare(" - INSERT INTO cursos (nombre, tipo, competencias) - VALUES (?, ?, ?) - "); - $stmt->execute([ - $data['nombre'], - $data['tipo'], - $data['competencias'] ?? null - ]); + if (empty($data['nombre']) || empty($data['tipo'])) { + http_response_code(400); + echo json_encode(['error' => 'Nombre y tipo son requeridos']); + exit; + } - echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]); + try { + $stmt = $pdo->prepare(" + INSERT INTO cursos (nombre, descripcion, tipo, estado, profesor_id) + VALUES (?, ?, ?, 'activo', ?) + "); + $stmt->execute([ + $data['nombre'], + $data['descripcion'] ?? null, + $data['tipo'], + $profesorId + ]); + + echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Error al crear curso']); + } + break; + + case 'PUT': + $data = json_decode(file_get_contents('php://input'), true); + + if (empty($data['id']) || empty($data['nombre']) || empty($data['tipo'])) { + http_response_code(400); + echo json_encode(['error' => 'Datos incompletos']); + exit; + } + + try { + // Verificar que el curso pertenece al profesor + $stmt = $pdo->prepare("SELECT id FROM cursos WHERE id = ? AND profesor_id = ?"); + $stmt->execute([$data['id'], $profesorId]); + + if (!$stmt->fetch()) { + http_response_code(403); + echo json_encode(['error' => 'No autorizado']); + exit; + } + + $stmt = $pdo->prepare(" + UPDATE cursos SET + nombre = ?, + descripcion = ?, + tipo = ?, + estado = ? + WHERE id = ? + "); + $stmt->execute([ + $data['nombre'], + $data['descripcion'] ?? null, + $data['tipo'], + $data['estado'] ?? 'activo', + $data['id'] + ]); + + echo json_encode(['success' => true]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Error al actualizar curso']); + } + break; + + case 'DELETE': + $id = $_GET['id'] ?? null; + if (!$id) { + http_response_code(400); + echo json_encode(['error' => 'ID de curso no proporcionado']); + exit; + } + + try { + // Verificar que el curso pertenece al profesor + $stmt = $pdo->prepare("SELECT id FROM cursos WHERE id = ? AND profesor_id = ?"); + $stmt->execute([$id, $profesorId]); + + if (!$stmt->fetch()) { + http_response_code(403); + echo json_encode(['error' => 'No autorizado']); + exit; + } + + $stmt = $pdo->prepare("DELETE FROM cursos WHERE id = ?"); + $stmt->execute([$id]); + + echo json_encode(['success' => true]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Error al eliminar curso']); + } break; default: http_response_code(405); echo json_encode(['error' => 'Método no permitido']); -} \ No newline at end of file +} +?> \ No newline at end of file diff --git a/api/diploma.php b/api/diploma.php new file mode 100644 index 0000000..c2a0cc2 --- /dev/null +++ b/api/diploma.php @@ -0,0 +1,44 @@ + 'No autenticado']); + exit; +} +try { + $profesorId = $_GET['profesor_id'] ?? null; + + $query = " + SELECT d.*, a.nombre AS alumno_nombre, c.nombre AS curso_nombre + FROM diplomas d + JOIN alumnos_cursos ac ON d.alumno_curso_id = ac.id + JOIN alumnos a ON ac.alumno_id = a.id + JOIN cursos c ON ac.curso_id = c.id + "; + + $params = []; + if ($profesorId) { + $query .= " WHERE c.profesor_id = ?"; + $params[] = $profesorId; + } + + $query .= " ORDER BY d.fecha_emision DESC"; + + $stmt = $pdo->prepare($query); + $stmt->execute($params); + + $diplomas = $stmt->fetchAll(); + + echo json_encode([ + 'success' => true, + 'data' => $diplomas, + 'count' => count($diplomas) + ]); + +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} +?> \ No newline at end of file diff --git a/api/login.php b/api/login.php index b62b8f4..aa72057 100644 --- a/api/login.php +++ b/api/login.php @@ -2,80 +2,55 @@ header('Content-Type: application/json'); require '../includes/config.php'; -// Habilitar reporte de errores para depuración (quitar en producción) error_reporting(E_ALL); ini_set('display_errors', 1); -// Verificar si la solicitud es POST if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success' => false, 'message' => 'Método no permitido']); exit; } -// Obtener datos del formulario -$username = trim($_POST['username'] ?? ''); +$email = trim($_POST['email'] ?? ''); $password = $_POST['password'] ?? ''; -// Validaciones básicas -if (empty($username) || empty($password)) { - echo json_encode(['success' => false, 'message' => 'Usuario y contraseña son requeridos']); +if (empty($email) || empty($password)) { + echo json_encode(['success' => false, 'message' => 'Email y contraseña son requeridos']); exit; } try { - // Buscar usuario en la base de datos - $stmt = $pdo->prepare("SELECT * FROM usuarios WHERE username = ?"); - $stmt->execute([$username]); - $user = $stmt->fetch(); + // Buscar profesor en la base de datos + $stmt = $pdo->prepare("SELECT * FROM usuarios WHERE email = ? AND aprobado = 1"); + $stmt->execute([$email]); + $profesor = $stmt->fetch(); - if (!$user) { - echo json_encode(['success' => false, 'message' => 'Usuario no encontrado']); + if (!$profesor) { + echo json_encode(['success' => false, 'message' => 'Profesor no encontrado o no aprobado']); exit; } - // Verificar contraseña - if (!password_verify($password, $user['password'])) { + if (!password_verify($password, $profesor['password'])) { echo json_encode(['success' => false, 'message' => 'Contraseña incorrecta']); exit; } - // Obtener cursos del usuario - $stmt = $pdo->prepare(" - SELECT c.*, uc.estado, uc.fecha_inicio, uc.fecha_fin, uc.profesor - FROM usuario_cursos uc - JOIN cursos c ON uc.curso_id = c.id - WHERE uc.usuario_id = ? - "); - $stmt->execute([$user['id']]); - $cursos = $stmt->fetchAll(); - - // Configurar sesión de usuario - $_SESSION['user'] = [ - 'id' => $user['id'], - 'username' => $user['username'], - 'nombre' => $user['nombre'], - 'email' => $user['email'], - 'rol' => $user['rol'], - 'cursos' => $cursos + // Configurar sesión de profesor + $_SESSION['profesor'] = [ + 'id' => $profesor['id'], + 'nombre' => $profesor['nombre'], + 'email' => $profesor['email'] ]; - // Respuesta exitosa echo json_encode([ - 'success' => true, - 'redirect' => 'dashboard.php', - 'user' => [ - 'id' => $user['id'], - 'nombre' => $user['nombre'], - 'rol' => $user['rol'] - ] - ]); + 'success' => true, + 'redirect' => 'dashboard.php', + 'profesor_id' => $profesor['id'] +]); + } catch (PDOException $e) { - // Registrar error en archivo de log error_log('Error en login.php: ' . $e->getMessage()); - - // Respuesta de error genérico (no mostrar detalles internos al usuario) echo json_encode([ 'success' => false, 'message' => 'Error en el servidor. Por favor, intente más tarde.' diff --git a/assets/css/students-management.css b/assets/css/students-management.css new file mode 100644 index 0000000..ce66b30 --- /dev/null +++ b/assets/css/students-management.css @@ -0,0 +1,77 @@ +/* Badges de cursos */ +.course-badges { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.course-badge { + display: inline-flex; + align-items: center; + background-color: #e0e7ff; + color: #2563eb; + padding: 0.25rem 0.5rem; + border-radius: 12px; + font-size: 0.85rem; +} + +.badge-remove { + background: none; + border: none; + color: #2563eb; + margin-left: 0.25rem; + cursor: pointer; + font-size: 1rem; + line-height: 1; + padding: 0 0.25rem; +} + +.badge-remove:hover { + color: #1e40af; +} + +/* Loader pequeño */ +.loader-sm { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid #e2e8f0; + border-radius: 50%; + border-top-color: #2563eb; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Modal de asignación */ +.form-select { + width: 100%; + padding: 0.75rem; + border: 1px solid #e2e8f0; + border-radius: 6px; + margin-bottom: 1rem; + font-family: "Inter", sans-serif; +} + +/* Header acciones */ +.header-actions { + display: flex; + gap: 1rem; + align-items: center; +} + +@media (max-width: 768px) { + .header-actions { + flex-direction: column; + align-items: flex-start; + width: 100%; + } + + .search-box { + width: 100%; + } +} diff --git a/assets/css/styles.css b/assets/css/styles.css index a2ab14d..f5512f6 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -1,21 +1,4 @@ -/* --- Nueva paleta de colores --- */ -:root { - --primary-dark: #002b5c; - --primary-main: #003f7d; - --primary-light: #0066cc; - --primary-lighter: #e1f0ff; - --accent-main: #4facfe; - --accent-light: #00f2fe; - --text-primary: #1a1a1a; - --text-secondary: #4a5568; - --background: #f8fafc; - --surface: #ffffff; - --border: #e2e8f0; - --error: #e53e3e; - --success: #38a169; -} - -/* --- Reset y tipografía --- */ +/* Reset y estilos base */ * { box-sizing: border-box; margin: 0; @@ -23,74 +6,68 @@ } body { - font-family: 'Inter', sans-serif; - color: var(--text-primary); - background-color: var(--background); - line-height: 1.6; + font-family: "Inter", sans-serif; height: 100vh; overflow: hidden; } -/* --- Login - Versión elegante --- */ +/* Estilos para el contenedor del login */ .login-container { display: flex; justify-content: center; align-items: center; height: 100vh; width: 100vw; - background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-main) 100%); + background: linear-gradient(135deg, #003f7d 0%, #0066cc 100%); padding: 20px; } .login-card { - background: var(--surface); - border-radius: 12px; + background: white; + border-radius: 16px; padding: 2.5rem; width: 100%; - max-width: 420px; - box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2); + max-width: 380px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); position: relative; overflow: hidden; - border: 1px solid rgba(255, 255, 255, 0.1); } .login-card::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; width: 100%; - height: 6px; - background: linear-gradient(90deg, var(--accent-main) 0%, var(--accent-light) 100%); + height: 5px; + background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%); } .logo-container { - text-align: center; - margin-bottom: 2rem; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1rem; } .logo-icon { - width: 48px; - height: 48px; - color: var(--primary-light); - margin: 0 auto 1rem; - display: block; + width: 40px; + height: 40px; + color: #2563eb; + margin-right: 10px; } .login-card h1 { - color: var(--primary-dark); + color: #1e293b; margin: 0; - font-size: 2rem; - font-weight: 600; - letter-spacing: -0.5px; + font-size: 1.8rem; } .welcome-text { - color: var(--text-secondary); + color: #64748b; text-align: center; margin-bottom: 2rem; - font-size: 1rem; - font-weight: 400; + font-size: 0.95rem; } .input-group { @@ -100,58 +77,76 @@ body { .input-icon { position: absolute; - left: 16px; + left: 12px; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; - color: var(--text-secondary); + color: #94a3b8; z-index: 2; } .login-card input { width: 100%; - padding: 0.85rem 0.85rem 0.85rem 48px; - border: 1px solid var(--border); + padding: 0.75rem 0.75rem 0.75rem 40px; + border: 1px solid #e2e8f0; border-radius: 8px; - font-size: 1rem; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - background-color: var(--surface); - color: var(--text-primary); + font-size: 0.95rem; + transition: all 0.3s ease; + background-color: #f8fafc; } .login-card input:focus { - border-color: var(--primary-light); - box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); - outline: none; + border-color: #2563eb; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); + background-color: white; } .login-btn { width: 100%; - padding: 1rem; + padding: 0.85rem; font-size: 1rem; font-weight: 600; display: flex; align-items: center; justify-content: center; - gap: 10px; - margin-top: 1.5rem; - background: linear-gradient(90deg, var(--primary-main) 0%, var(--primary-light) 100%); - color: white; + gap: 8px; + margin-top: 1rem; + background: linear-gradient(90deg, #2563eb 0%, #1d4ed8 100%); border: none; border-radius: 8px; cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 4px 6px rgba(0, 63, 125, 0.1); + transition: all 0.3s ease; } .login-btn:hover { - background: linear-gradient(90deg, var(--primary-dark) 0%, var(--primary-main) 100%); - transform: translateY(-2px); - box-shadow: 0 6px 12px rgba(0, 63, 125, 0.15); + background: linear-gradient(90deg, #1d4ed8 0%, #1e40af 100%); + transform: translateY(-1px); } -/* --- Dashboard - Versión elegante --- */ +.btn-icon { + width: 20px; + height: 20px; +} + +.footer-links { + display: flex; + justify-content: space-between; + margin-top: 1.5rem; + font-size: 0.85rem; +} + +.footer-links a { + color: #64748b; + text-decoration: none; + transition: color 0.2s ease; +} + +.footer-links a:hover { + color: #2563eb; +} + +/* Estilos para la aplicación principal */ #app-content { display: flex; flex-direction: column; @@ -163,39 +158,65 @@ header { display: flex; justify-content: space-between; align-items: center; - padding: 1rem 2.5rem; - background-color: var(--primary-dark); + padding: 1rem 2rem; + background-color: #003f7d; color: white; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); position: relative; - z-index: 10; } header h1 { position: absolute; left: 50%; transform: translateX(-50%); - font-weight: 600; - font-size: 1.5rem; - letter-spacing: 0.5px; +} + +header h1 { + position: absolute; + left: 50%; + transform: translateX(-50%); } #user-info { - margin-left: auto; + order: -1; display: flex; align-items: center; - gap: 1.5rem; + gap: 1rem; +} + +/* Estilos para la tabla en el dashboard */ +.stats { + margin-bottom: 1.5rem; +} + +.stats p { + margin: 0.5rem 0; +} + +/* Paginación */ +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + margin-top: 1rem; +} + +.pagination-btn { + padding: 0.5rem 1rem; + min-width: 100px; +} + +#page-info { + font-size: 0.9rem; + color: #64748b; } #current-user { - font-weight: 500; + font-weight: 600; background: rgba(255, 255, 255, 0.1); - padding: 0.5rem 1.25rem; + padding: 0.5rem 1rem; border-radius: 20px; - font-size: 0.95rem; - display: flex; - align-items: center; - gap: 0.5rem; } .main-container { @@ -205,12 +226,11 @@ header h1 { } .sidebar { - width: 280px; - background-color: var(--surface); - padding: 1.5rem 0; - border-right: 1px solid var(--border); + width: 250px; + background-color: #f8fafc; + padding: 1.5rem 1rem; + border-right: 1px solid #e2e8f0; overflow-y: auto; - box-shadow: 2px 0 10px rgba(0, 0, 0, 0.03); } .sidebar-menu { @@ -220,279 +240,723 @@ header h1 { } .sidebar-menu li { - padding: 0.85rem 1.5rem; - margin: 0.25rem 1rem; - border-radius: 8px; + padding: 0.75rem 1rem; + margin-bottom: 0.5rem; + border-radius: 6px; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; - gap: 0.85rem; - font-weight: 500; - color: var(--text-secondary); + gap: 0.75rem; } .sidebar-menu li:hover { - background-color: var(--primary-lighter); - color: var(--primary-main); + background-color: #e0e7ff; } .sidebar-menu li.active { - background-color: var(--primary-main); + background-color: #2563eb; color: white; } -.sidebar-menu li svg { - width: 20px; - height: 20px; -} - .content { flex: 1; - padding: 2.5rem; + padding: 2rem; overflow-y: auto; - background-color: var(--background); + background-color: #f9fafb; +} + +.content-section { + display: none; +} + +.content-section.active { + display: block; } .card { - background: var(--surface); - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); - padding: 2rem; - margin-bottom: 2rem; - border-left: none; - transition: transform 0.3s ease, box-shadow 0.3s ease; -} - -.card:hover { - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); - transform: translateY(-2px); + background: white; + border-left: 4px solid #2563eb; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + padding: 1.5rem; + margin-bottom: 1.5rem; } .card h2 { margin-top: 0; - margin-bottom: 1.5rem; - color: var(--primary-dark); - font-size: 1.5rem; - font-weight: 600; - padding-bottom: 0.75rem; - border-bottom: 1px solid var(--border); + color: #003f7d; + font-size: 1.25rem; +} + +.btn { + background-color: #2563eb; + color: white; + padding: 0.6rem 1.25rem; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + font-family: "Inter", sans-serif; + transition: background-color 0.2s ease; +} + +.btn:hover { + background-color: #1d4ed8; +} + +input, +select { + width: 100%; + padding: 0.75rem; + margin-top: 0.5rem; + margin-bottom: 1rem; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-family: "Inter", sans-serif; +} + +input:focus, +select:focus { + outline: none; + border-color: #2563eb; + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2); +} + +.oculto { + display: none; +} + +/* Estilos para el contenido específico */ +.course-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1.5rem; +} + +.course-card { + background: white; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + padding: 1.25rem; + transition: transform 0.2s ease; +} + +.course-card:hover { + transform: translateY(-2px); +} + +.diploma-preview { + max-width: 600px; + margin: 0 auto; + background: white; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + text-align: center; +} + +.diploma-preview img { + max-width: 100%; + height: auto; + margin: 1rem 0; +} + +/* Estilos para el buscador y tabla */ +.search-container { + display: flex; + gap: 10px; + margin-bottom: 20px; +} + +.search-input { + flex: 1; + padding: 0.75rem; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-family: "Inter", sans-serif; } -/* --- Tablas mejoradas --- */ .table-container { overflow-x: auto; - margin-top: 1.5rem; - border-radius: 8px; - border: 1px solid var(--border); + margin-top: 20px; } .courses-table { width: 100%; border-collapse: collapse; + margin-bottom: 20px; } -.courses-table th, .courses-table td { - padding: 1rem 1.25rem; +.courses-table th, +.courses-table td { + padding: 12px 15px; text-align: left; - border-bottom: 1px solid var(--border); + border-bottom: 1px solid #e2e8f0; } .courses-table th { - background-color: var(--primary-lighter); + background-color: #f8fafc; font-weight: 600; - color: var(--primary-dark); - text-transform: uppercase; - font-size: 0.8rem; - letter-spacing: 0.5px; -} - -.courses-table tr:last-child td { - border-bottom: none; + color: #003f7d; } .courses-table tr:hover { - background-color: var(--primary-lighter); -} - -/* --- Botones mejorados --- */ -.btn { - background-color: var(--primary-main); - color: white; - padding: 0.75rem 1.5rem; - border: none; - border-radius: 8px; - cursor: pointer; - font-weight: 500; - font-family: 'Inter', sans-serif; - transition: all 0.2s ease; - font-size: 0.95rem; - display: inline-flex; - align-items: center; - gap: 0.5rem; -} - -.btn:hover { - background-color: var(--primary-dark); - transform: translateY(-1px); - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); -} - -.btn svg { - width: 16px; - height: 16px; + background-color: #f5f7fa; } .download-btn { - background-color: var(--success); + padding: 0.4rem 0.8rem; + font-size: 0.9rem; +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 15px; + margin-top: 20px; +} + +.pagination-btn { + padding: 0.5rem 1rem; +} +/* Mantén todos tus estilos anteriores y añade estos */ + +/* Estilos para el dashboard */ +.admin .sidebar-menu li[data-section="courses"], +.admin .sidebar-menu li[data-section="students"], +.user .sidebar-menu li[data-section="my-courses"], +.user .sidebar-menu li[data-section="diplomas"] { + display: none; +} + +.admin .sidebar-menu li[data-section="courses"], +.admin .sidebar-menu li[data-section="students"] { + display: flex; +} + +.user .sidebar-menu li[data-section="my-courses"], +.user .sidebar-menu li[data-section="diplomas"] { + display: flex; +} + +/* Mejoras de responsive */ +@media (max-width: 768px) { + .main-container { + flex-direction: column; + } + + .sidebar { + width: 100%; + height: auto; + } + + .login-card { + width: 90%; + padding: 1.5rem; + } +} +/* Estilos adicionales */ +.loader { + text-align: center; + padding: 2rem; + font-style: italic; + color: #64748b; +} + +.course-card { + background: white; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + padding: 1.25rem; + margin-bottom: 1rem; + transition: transform 0.2s ease; +} + +.course-card:hover { + transform: translateY(-2px); +} + +.course-card h3 { + margin-top: 0; + color: #003f7d; +} + +.diploma-preview { + background: white; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + padding: 1.5rem; + margin-bottom: 1.5rem; + text-align: center; +} + +.diploma-preview h3 { + color: #003f7d; +} + +.search-container { + display: flex; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.search-input { + flex: 1; + padding: 0.75rem; + border: 1px solid #e2e8f0; + border-radius: 6px; +} + +/* Añadir al final del archivo */ + +/* Estilos para el header */ +header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; +} + +header h1 { + position: static; + transform: none; + margin: 0; +} + +#user-info { + order: 1; + display: flex; + align-items: center; + gap: 1rem; +} + +/* Estilos para el enlace de cerrar sesión */ +.logout-link { + color: #333; + text-decoration: none; + padding: 0.75rem 1rem; + display: block; + transition: all 0.2s ease; +} + +.logout-link:hover { + color: #e53e3e; + background-color: #fee2e2; +} + +/* Estilos para la columna de acciones */ +.courses-table td:last-child { + text-align: center; +} + +.download-btn { + padding: 0.4rem 0.8rem; + font-size: 0.9rem; + background-color: #38a169; } .download-btn:hover { background-color: #2f855a; } -/* --- Formularios mejorados --- */ -input, select, textarea { - width: 100%; - padding: 0.85rem; - margin-top: 0.5rem; - margin-bottom: 1.25rem; - border: 1px solid var(--border); - border-radius: 8px; - font-family: 'Inter', sans-serif; - font-size: 1rem; - transition: all 0.2s ease; - background-color: var(--surface); +.download-btn:disabled { + background-color: #a0aec0; + cursor: not-allowed; } - -input:focus, select:focus, textarea:focus { - outline: none; - border-color: var(--primary-light); - box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1); -} - -label { - font-weight: 500; - color: var(--text-secondary); - font-size: 0.95rem; -} - -/* --- Efectos de transición --- */ -.content-section { - transition: opacity 0.3s ease; -} - -/* --- Responsive --- */ -@media (max-width: 1024px) { - .sidebar { - width: 240px; - } - - .content { - padding: 1.5rem; - } -} - -@media (max-width: 768px) { - .main-container { - flex-direction: column; - } - - .sidebar { - width: 100%; - height: auto; - padding: 1rem 0; - } - - .sidebar-menu { - display: flex; - overflow-x: auto; - padding: 0 1rem; - } - - .sidebar-menu li { - white-space: nowrap; - } - - header h1 { - position: static; - transform: none; - margin-right: auto; - } - - #user-info { - margin-left: 0; - } -} - -@media (max-width: 480px) { - .login-card { - padding: 1.75rem; - } - - .card { - padding: 1.5rem; - } - - .content { - padding: 1.25rem; - } -} - -.error-card { - border-left: 4px solid var(--error); -} - -.error-card h2 { - color: var(--error); -} - -.hidden { - display: none; -} - -.loading-spinner { - border: 2px solid rgba(255, 255, 255, 0.3); - border-radius: 50%; - border-top: 2px solid white; - width: 16px; - height: 16px; - animation: spin 1s linear infinite; - display: inline-block; - vertical-align: middle; -} - -.loading-spinner.small { - width: 12px; - height: 12px; - border-width: 1.5px; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - +/* Badges para tipos de curso */ .badge { - display: inline-block; - padding: 0.25rem 0.5rem; + padding: 4px 8px; border-radius: 12px; - font-size: 0.75rem; + font-size: 0.8em; font-weight: 600; text-transform: capitalize; } -.badge.pildora { - background-color: #DBEAFE; - color: #1E40AF; +.badge.active { + background-color: #4caf50; + color: white; } -.badge.inyeccion { - background-color: #D1FAE5; - color: #065F46; +.badge.inactive { + background-color: #f44336; + color: white; } -.badge.tratamiento { - background-color: #E0E7FF; - color: #3730A3; -} \ No newline at end of file +.badge.type-inyeccion { + background-color: #2196f3; + color: white; +} + +.badge.type-pildora { + background-color: #ff9800; + color: white; +} + +.badge.type-tratamiento { + background-color: #9c27b0; + color: white; +} +/* Estilos para celdas de descripción */ +.description-cell { + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.description-cell:hover { + white-space: normal; + overflow: visible; + position: relative; + z-index: 1; + background: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +/* Estilos para mensaje de no datos */ +.no-data { + text-align: center; + padding: 20px; + color: #64748b; + font-style: italic; +} + +/* Mejoras para textarea */ +textarea { + min-height: 100px; + resize: vertical; + font-family: "Inter", sans-serif; +} + +/* Estilos para tarjetas de error */ +.error-card { + border-left-color: #e53e3e !important; +} + +.error-card h2 { + color: #e53e3e; +} +/* ------------------------- */ +/* ESTILOS GESTIÓN ALUMNOS */ +/* ------------------------- */ + +.students-management { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +/* Formulario */ +#studentForm { + margin-top: 1rem; +} + +.form-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1rem; + margin-bottom: 1rem; +} + +.form-group { + margin-bottom: 0; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #334155; +} + +.form-group input { + width: 100%; + padding: 0.75rem; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-family: "Inter", sans-serif; + transition: all 0.2s ease; +} + +.form-group input:focus { + border-color: #2563eb; + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); + outline: none; +} + +.form-actions { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + margin-top: 1rem; +} + +/* Tabla */ +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + flex-wrap: wrap; + gap: 1rem; +} + +.search-box { + position: relative; + min-width: 250px; +} + +.search-box input { + padding: 0.5rem 1rem 0.5rem 2.5rem; + border: 1px solid #e2e8f0; + border-radius: 20px; + width: 100%; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 1rem center; + background-size: 1rem; +} + +.table-responsive { + overflow-x: auto; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.students-table { + width: 100%; + border-collapse: collapse; + font-size: 0.95rem; +} + +.students-table th { + background-color: #f8fafc; + padding: 0.75rem 1rem; + text-align: left; + font-weight: 600; + color: #334155; + border-bottom: 1px solid #e2e8f0; +} + +.students-table td { + padding: 0.75rem 1rem; + border-bottom: 1px solid #e2e8f0; + vertical-align: middle; +} + +.students-table tr:last-child td { + border-bottom: none; +} + +.students-table tr:hover td { + background-color: #f8fafc; +} + +/* Botones de acción */ +.action-buttons { + display: flex; + gap: 0.5rem; +} + +.btn-sm { + padding: 0.35rem 0.75rem; + font-size: 0.85rem; +} + +.btn-edit { + background-color: #f59e0b; + color: white; +} + +.btn-edit:hover { + background-color: #d97706; +} + +/* Mensaje sin datos */ +.no-data { + text-align: center; + padding: 2rem; + color: #64748b; +} + +.no-data svg { + margin-bottom: 1rem; + stroke-width: 1.5; +} + +.no-data h3 { + color: #334155; + margin-bottom: 0.5rem; +} + +.no-data p { + margin-bottom: 1.5rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .form-grid { + grid-template-columns: 1fr; + } + + .card-header { + flex-direction: column; + align-items: flex-start; + } + + .search-box { + width: 100%; + } + + .action-buttons { + flex-direction: column; + gap: 0.5rem; + } + + .action-buttons button { + width: 100%; + } +} +/* Modal styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.modal { + background: white; + border-radius: 8px; + width: 90%; + max-width: 500px; + padding: 20px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.modal-header h3 { + margin: 0; +} + +.close-btn { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding-top: 1rem; + border-top: 1px solid #e2e8f0; + background-color: #f9fafb; +} + +/* Toast styles */ +.toast { + position: fixed; + bottom: 20px; + right: 20px; + padding: 12px 20px; + border-radius: 4px; + color: white; + z-index: 1001; + animation: slideIn 0.3s ease-out; +} + +.toast-success { + background-color: #4caf50; +} + +.toast-error { + background-color: #f44336; +} + +.toast-warning { + background-color: #ff9800; +} + +.fade-out { + animation: fadeOut 0.3s ease-in; +} + +@keyframes slideIn { + from { + transform: translateY(100px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.checkbox-group { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 0.5rem; + padding: 0.5rem; + border: 1px solid #e2e8f0; + border-radius: 6px; + background-color: #f9fafb; +} + +.checkbox-group label { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.3rem; + background-color: #ffffff; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; +} + +.checkbox-group label:hover { + background-color: #e2e8f0; +} + +.checkbox-group input[type="checkbox"] { + accent-color: #2563eb; +} + +.course-badges { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.course-badge { + background-color: #e0e7ff; + color: #2563eb; + padding: 0.25rem 0.5rem; + border-radius: 12px; + font-size: 0.85rem; + font-weight: 500; +} diff --git a/assets/js/main.js b/assets/js/main.js index 9e4416d..94fff68 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,587 +1,1188 @@ -document.addEventListener('DOMContentLoaded', function() { - const loginForm = document.getElementById('loginForm'); +document.addEventListener("DOMContentLoaded", function () { + // Manejo del formulario de login + const loginForm = document.getElementById("loginForm"); if (loginForm) { - loginForm.addEventListener('submit', handleLogin); - } else { - initDashboard(); // Nombre de función consistente + loginForm.addEventListener("submit", handleLogin); + } + + // Configuración inicial del dashboard + if (document.body.classList.contains("admin")) { + initializeDashboard(); } }); -function initDashboard() { - // Configuración inicial - setupSidebarNavigation(); - loadInitialData(); - - // Mostrar sección activa - const activeSection = document.querySelector('.sidebar-menu li.active'); - if (activeSection) { - const sectionId = activeSection.dataset.section; - showSection(sectionId, true); - } - - // Configurar tooltips para botones - initTooltips(); - - // Configurar eventos globales - document.addEventListener('click', handleGlobalEvents); -} - +// Función para manejar el login function handleLogin(e) { e.preventDefault(); const formData = new FormData(this); - + fetch(this.action, { - method: 'POST', - body: formData + method: "POST", + body: formData, }) - .then(response => response.json()) - .then(data => { - if (data.success) { - window.location.href = data.redirect; - } else { - alert(data.message); - } - }) - .catch(error => console.error('Error:', error)); + .then((response) => response.json()) + .then((data) => { + if (data.success) { + window.location.href = data.redirect; + } else { + alert(data.message); + } + }) + .catch((error) => console.error("Error:", error)); } +// Inicialización del dashboard +function initializeDashboard() { + setupSidebarNavigation(); + loadInitialData(); -function setupSidebarNavigation() { - const menuItems = document.querySelectorAll('.sidebar-menu li:not(.logout-link)'); - - menuItems.forEach(item => { - item.addEventListener('click', function() { - // Agregar efecto visual al hacer clic - this.classList.add('click-feedback'); - setTimeout(() => this.classList.remove('click-feedback'), 200); - - const section = this.dataset.section; - showSection(section); - }); - }); -} - -async function loadInitialData() { - try { - // Mostrar skeleton loading - document.querySelectorAll('.stats p span').forEach(span => { - span.innerHTML = ''; - }); - - const [courses, users] = await Promise.all([ - fetchData('api/cursos.php'), - fetchData('api/usuarios.php') - ]); - - updateStats(courses, users); - } catch (error) { - console.error('Error loading initial data:', error); - showToast('Error al cargar datos iniciales', 'error'); + const activeSection = document.querySelector(".sidebar-menu li.active"); + if (activeSection) { + const sectionId = activeSection.getAttribute("data-section"); + showSection(sectionId, true); } } -function updateStats(courses = [], users = []) { - // Efecto de conteo animado - animateValue('active-courses-count', 0, courses.length, 1000); - animateValue('students-count', 0, users.length, 1000); - - const approvedCourses = courses.filter(c => c.estado === 'Aprobado'); - animateValue('diplomas-count', 0, approvedCourses.length, 1000); +// Configuración del menú lateral +function setupSidebarNavigation() { + document.querySelectorAll(".sidebar-menu li").forEach((item) => { + if (item.getAttribute("data-section")) { + item.addEventListener("click", function () { + const section = this.getAttribute("data-section"); + showSection(section); + }); + } + }); } -async function showSection(sectionId, isInitialLoad = false) { - updateActiveMenu(sectionId); - const sectionElement = getSectionElement(sectionId); - - if (sectionElement) { - toggleSections(sectionElement); - - if (!(isInitialLoad && sectionId === 'dashboard')) { - await loadDynamicContent(sectionId, sectionElement); +// Carga de datos iniciales +function loadInitialData() { + const profesorId = getProfesorId(); + + if (!profesorId) { + console.error("No se pudo obtener el ID del profesor"); + return; + } + + fetch(`api/cursos.php?profesor_id=${profesorId}`) + .then((response) => response.json()) + .then((courses) => { + // Cargar estudiantes y diplomas + Promise.all([ + fetch(`api/alumnos.php?profesor_id=${profesorId}`).then((res) => + res.json() + ), + fetch(`api/diplomas.php?profesor_id=${profesorId}`).then((res) => + res.json() + ), + ]).then(([students, diplomas]) => { + updateProfessorStats(courses, students, diplomas); + }); + }) + .catch((error) => console.error("Error:", error)); +} + +// Actualización de estadísticas +function updateProfessorStats(courses, students, diplomas) { + document.getElementById("active-courses-count").textContent = courses.length; + document.getElementById("students-count").textContent = students.length; + document.getElementById("diplomas-count").textContent = diplomas.length || 0; +} + +// Obtención del ID del profesor +function getProfesorId() { + try { + // 1. Verificar en el elemento del DOM (primera opción) + const profesorElement = document.getElementById("current-profesor"); + if (profesorElement && profesorElement.dataset.id) { + return profesorElement.dataset.id; } + + // 2. Verificar en sessionStorage/localStorage + const storedUser = + sessionStorage.getItem("currentUser") || + localStorage.getItem("currentUser"); + if (storedUser) { + const user = JSON.parse(storedUser); + if (user && user.profesor_id) { + return user.profesor_id; + } + } + + console.log( + "No se encontró el ID del profesor en el DOM o en el almacenamiento" + ); + return null; + } catch (error) { + console.error("Error en getProfesorId:", error); + return null; + } +} + +function showModal(title, content, buttons = []) { + const modalHtml = ` + + `; + + const modalContainer = document.createElement("div"); + modalContainer.innerHTML = modalHtml; + document.body.appendChild(modalContainer); + document.body.style.overflow = "hidden"; +} + +function closeModal() { + const modal = document.getElementById("modal-overlay"); + if (modal) { + modal.remove(); + document.body.style.overflow = ""; + } +} + +function showToast(type, message) { + const toast = document.createElement("div"); + toast.className = `toast toast-${type}`; + toast.textContent = message; + document.body.appendChild(toast); + + setTimeout(() => { + toast.classList.add("fade-out"); + setTimeout(() => toast.remove(), 300); + }, 3000); +} + +// Manejo de secciones +function showSection(sectionId, isInitialLoad = false) { + updateActiveMenu(sectionId); + + const sectionElement = document.getElementById(`${sectionId}-content`); + if (!sectionElement) return; + + document.querySelectorAll(".content-section").forEach((s) => { + s.classList.remove("active"); + }); + sectionElement.classList.add("active"); + + // Cargar siempre los datos del dashboard al acceder + if (sectionId === "dashboard") { + loadInitialData(); + } else { + loadDynamicContent(sectionId, sectionElement); } } function updateActiveMenu(sectionId) { - document.querySelectorAll('.sidebar-menu li').forEach(li => { - li.classList.remove('active'); + document.querySelectorAll(".sidebar-menu li").forEach((li) => { + li.classList.remove("active"); }); - const activeItem = document.querySelector(`.sidebar-menu li[data-section="${sectionId}"]`); - if (activeItem) { - activeItem.classList.add('active'); + + const activeItem = document.querySelector( + `.sidebar-menu li[data-section="${sectionId}"]` + ); + if (activeItem) activeItem.classList.add("active"); +} + +// Carga de contenido dinámico +function loadDynamicContent(sectionId, container) { + container.innerHTML = '
Cargando...
'; + + switch (sectionId) { + case "dashboard": + loadDashboardContent(container); + break; + case "courses": + loadProfessorCourses(container); + break; + case "students": + loadStudentsManagement(container); + break; + case "diplomas": + loadDiplomasSection(container); + break; + default: + container.innerHTML = + '

Sección no implementada

'; } } -function getSectionElement(sectionId) { - return document.getElementById(`${sectionId}-content`); -} +// Carga del dashboard +async function loadDashboardContent(container, forceReload = false) { + try { + // Mostrar loader + container.innerHTML = '
Cargando datos...
'; -function toggleSections(activeSection) { - document.querySelectorAll('.content-section').forEach(section => { - section.classList.remove('active'); - }); - activeSection.classList.add('active'); -} + // Obtener siempre los datos frescos del servidor + const profesorId = getProfesorId(); + if (!profesorId) { + throw new Error("Debes iniciar sesión nuevamente"); + } + + // Usar caché solo si no es un forceReload + const cacheKey = `dashboardData-${profesorId}`; + if (!forceReload && sessionStorage.getItem(cacheKey)) { + const cachedData = JSON.parse(sessionStorage.getItem(cacheKey)); + renderDashboard(container, cachedData); + return; + } + + // Obtener datos frescos + const [coursesRes, studentsRes, diplomasRes] = await Promise.all([ + fetch(`api/cursos.php?profesor_id=${profesorId}&t=${Date.now()}`), + fetch(`api/alumnos.php?t=${Date.now()}`), + fetch(`api/diplomas.php?profesor_id=${profesorId}&t=${Date.now()}`), + ]); + + const [coursesData, studentsData, diplomasData] = await Promise.all([ + coursesRes.json(), + studentsRes.json(), + diplomasRes.json(), + ]); + + if ( + !coursesData.success || + !studentsData.success || + !diplomasData.success + ) { + throw new Error("Error en los datos recibidos"); + } + + const dashboardData = { + cursos: coursesData.data, + alumnos: studentsData.data, + diplomas: diplomasData.data, + lastUpdated: new Date().toLocaleTimeString(), + }; + + // Guardar en caché y renderizar + sessionStorage.setItem(cacheKey, JSON.stringify(dashboardData)); + renderDashboard(container, dashboardData); + } catch (error) { + console.error("Error:", error); + container.innerHTML = ` +
+

Error al cargar datos

+

${error.message}

+ +
`; + } +} +function renderDashboard(container, data) { + const activeCourses = data.cursos.filter((c) => c.estado === "activo"); + const totalStudents = data.alumnos.length; + const totalDiplomas = data.diplomas.length; -async function loadDynamicContent(sectionId, container) { - // Mostrar skeleton loading container.innerHTML = ` -
-
-
-
-
- `; - - try { - switch(sectionId) { - case 'courses': - await loadCoursesSection(container); - break; - case 'students': - await loadStudentsSection(container); - break; - case 'diplomas': - await loadDiplomasSection(container); - break; - default: - container.innerHTML = '

Sección no implementada

'; - } - } catch (error) { - console.error(`Error loading ${sectionId} section:`, error); - container.innerHTML = ` -
-

Error al cargar contenido

-

No se pudo cargar la información solicitada.

- -
- `; - } -} - -// Funciones para cargar contenido específico -async function loadCoursesSection(container) { - try { - const courses = await fetchData('api/cursos.php'); - - // Verificar si courses es un array - if (!Array.isArray(courses)) { - throw new Error('Formato de datos inválido'); - } - - container.innerHTML = ` -
-

Gestión de Cursos

-
-
- - -
- -
- - -
- - - -
- -
-
-
-
-

Lista de Cursos

-
-
- - -
+
+
+

Resumen General

+ Actualizado: ${ + data.lastUpdated + } + +
+
+

Resumen:

+

${ + activeCourses.length + } cursos activos

+

${totalStudents} alumnos registrados

+

${totalDiplomas} diplomas emitidos

+
-
- - - - - - - - - - - ${generateCoursesTableRows(courses)} - -
NombreTipoCompetenciasAcciones
-
-
- `; - - setupCourseForm(); - } catch (error) { - console.error('Error loading courses:', error); - container.innerHTML = ` -
-

Error al cargar los cursos

-

${error.message || 'No se pudieron cargar los cursos'}

- -
- `; - } -} -function formatCourseType(type) { - const types = { - 'pildora': 'Píldora', - 'inyeccion': 'Inyección', - 'tratamiento': 'Tratamiento' - }; - return types[type] || type; +
+

Mis Cursos Activos

+ ${renderCoursesPreview(activeCourses)} +
`; } -function loadStudentsSection(container) { - fetch('api/usuarios.php') - .then(response => response.json()) - .then(users => { +// Gestión de cursos +function loadProfessorCourses(container) { + fetch(`api/cursos.php?profesor_id=${getProfesorId()}`) + .then((response) => response.json()) + .then((courses) => { container.innerHTML = `
-

Gestión de Estudiantes

-
- - -
+

Mis Cursos

+
+ + + + + + + + + + + + +
+ +
+
+
+
+

Lista de Cursos

- - - - - - - ${generateStudentsTableRows(users)} - -
NombreUsuarioEmailCursos Inscritos
-
-
`; - - setupStudentSearch(); - }) - .catch(error => { - container.innerHTML = '

Error al cargar los estudiantes

'; - console.error('Error:', error); - }); -} - -function loadDiplomasSection(container) { - fetch('api/cursos.php') - .then(response => response.json()) - .then(courses => { - const approvedCourses = courses.filter(c => c.estado === 'Aprobado'); - - container.innerHTML = ` -
-

Diplomas Emitidos

-
- - -
-
- - - - - - + + + - - ${generateDiplomasTableRows(approvedCourses)} + + ${courses + .map( + (course) => ` + + + + + + + + ` + ) + .join("")}
CursoEstudianteFecha de AprobaciónDescripciónTipoEstado Acciones
${course.nombre}${ + course.descripcion || "-" + }${course.tipo}${course.estado} +
+ + +
+
`; - - setupDiplomaSearch(); - }) - .catch(error => { - container.innerHTML = '

Error al cargar los diplomas

'; - console.error('Error:', error); + + setupCourseForm(); }); } -// Funciones auxiliares para generar contenido -function generateCoursesTableRows(courses = []) { - return courses.map(course => ` - - ${course.nombre} - ${formatCourseType(course.tipo)} - ${course.competencias || '-'} - - - - - - `).join(''); -} - -/** - * Muestra un toast notification - */ -function showToast(message, type = 'info') { - const toast = document.createElement('div'); - toast.className = `toast-notification ${type}`; - toast.textContent = message; - document.body.appendChild(toast); - - setTimeout(() => { - toast.classList.add('fade-out'); - setTimeout(() => toast.remove(), 300); - }, 3000); -} - -function generateStudentsTableRows(users) { - return users.map(user => ` - - ${user.nombre} - ${user.username} - ${user.email} - ${user.cursos ? user.cursos.length : 0} - - `).join(''); -} - -function generateDiplomasTableRows(courses) { - return courses.map(course => ` - - ${course.nombre} - ${course.estudiante || 'N/A'} - ${course.fecha_fin || 'N/A'} - - - - - `).join(''); -} -function animateValue(id, start, end, duration) { - const element = document.getElementById(id); - if (!element) return; - - const range = end - start; - const startTime = Date.now(); - const endTime = startTime + duration; - - const update = () => { - const now = Date.now(); - const progress = Math.min((now - startTime) / duration, 1); - const value = Math.floor(start + progress * range); - element.textContent = value.toLocaleString(); - - if (now < endTime) { - requestAnimationFrame(update); - } - }; - - update(); -} - - -// Configuración de eventos dinámicos function setupCourseForm() { - const courseTypeSelect = document.getElementById('courseType'); - if (courseTypeSelect) { - courseTypeSelect.addEventListener('change', function() { - const competencesField = document.getElementById('competencesField'); - competencesField.classList.toggle('hidden', this.value !== 'tratamiento'); - }); - } - - const courseForm = document.getElementById('courseForm'); - if (courseForm) { - courseForm.addEventListener('submit', async function(e) { - e.preventDefault(); - - const submitBtn = this.querySelector('button[type="submit"]'); - const originalText = submitBtn.innerHTML; - submitBtn.innerHTML = ''; - submitBtn.disabled = true; - - try { - const formData = new FormData(this); - const jsonData = Object.fromEntries(formData.entries()); - - const response = await fetch('api/cursos.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(jsonData) - }); - - const data = await response.json(); - - if (data.success) { - showToast('Curso creado exitosamente', 'success'); - await loadCoursesSection(document.getElementById('courses-content')); - } else { - showToast('Error al crear el curso', 'error'); - } - } catch (error) { - console.error('Error:', error); - showToast('Error en la conexión', 'error'); - } finally { - submitBtn.innerHTML = originalText; - submitBtn.disabled = false; - } - }); - } -} + const form = document.getElementById("courseForm"); + if (!form) return; -function setupStudentSearch() { - const searchButton = document.getElementById('searchStudentButton'); - if (searchButton) { - searchButton.addEventListener('click', function() { - const searchTerm = document.getElementById('studentSearch').value.toLowerCase(); - const tableBody = document.getElementById('students-list-body'); - - fetch('api/usuarios.php') - .then(response => response.json()) - .then(users => { - const filteredUsers = users.filter(user => - user.nombre.toLowerCase().includes(searchTerm) || - user.username.toLowerCase().includes(searchTerm) || - user.email.toLowerCase().includes(searchTerm) - ); - - tableBody.innerHTML = generateStudentsTableRows(filteredUsers); - }); - }); - } -} + form.addEventListener("submit", function (e) { + e.preventDefault(); + const formData = new FormData(this); + const jsonData = { + nombre: formData.get("nombre"), + descripcion: formData.get("descripcion"), + tipo: formData.get("tipo"), + profesor_id: getProfesorId(), + }; -function setupDiplomaSearch() { - const searchButton = document.getElementById('searchDiplomaButton'); - if (searchButton) { - searchButton.addEventListener('click', function() { - const searchTerm = document.getElementById('diplomaSearch').value.toLowerCase(); - const tableBody = document.getElementById('diplomas-list-body'); - - fetch('api/cursos.php') - .then(response => response.json()) - .then(courses => { - const approvedCourses = courses.filter(c => c.estado === 'Aprobado'); - const filteredCourses = approvedCourses.filter(course => - course.nombre.toLowerCase().includes(searchTerm) || - (course.estudiante && course.estudiante.toLowerCase().includes(searchTerm)) - ); - - tableBody.innerHTML = generateDiplomasTableRows(filteredCourses); - }); - }); - } - - // Configurar eventos de descarga de diplomas - document.querySelectorAll('.download-btn').forEach(btn => { - btn.addEventListener('click', function() { - const courseId = this.getAttribute('data-course-id'); - - // Mostrar mensaje de carga - const originalText = this.innerHTML; - this.innerHTML = 'Generando diploma...'; - this.disabled = true; - - // Abrir en nueva pestaña - window.open(`certificado.php?course_id=${courseId}`, '_blank'); - - // Restaurar botón después de un breve retraso - setTimeout(() => { - this.innerHTML = originalText; - this.disabled = false; - }, 2000); - }); + const isEdit = form.dataset.editing === "true"; + const courseId = form.dataset.courseId; + + if (isEdit) { + updateCourse(courseId, jsonData); + } else { + createCourse(jsonData); + } }); } -function handleGlobalEvents(e) { - // Delegación de eventos para mejor performance - if (e.target.closest('.edit-btn')) { - const courseId = e.target.closest('.edit-btn').dataset.id; - handleEditCourse(courseId); - } - - if (e.target.closest('.delete-btn')) { - const courseId = e.target.closest('.delete-btn').dataset.id; - handleDeleteCourse(courseId); - } - - if (e.target.closest('.download-btn')) { - const courseId = e.target.closest('.download-btn').dataset.id; - handleDownloadDiploma(courseId); +window.createCourse = function (data) { + fetch("api/cursos.php", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + showSection("courses"); + } + }) + .catch((error) => console.error("Error:", error)); +}; + +window.updateCourse = function (id, data) { + data.id = id; + + fetch("api/cursos.php", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + showSection("courses"); + } + }) + .catch((error) => console.error("Error:", error)); +}; + +window.editCourse = function (id) { + fetch(`api/cursos.php?profesor_id=${getProfesorId()}`) + .then((response) => response.json()) + .then((courses) => { + const course = courses.find((c) => c.id == id); + if (!course) return; + + const form = document.getElementById("courseForm"); + form.nombre.value = course.nombre; + form.descripcion.value = course.descripcion || ""; + form.tipo.value = course.tipo; + + form.dataset.editing = "true"; + form.dataset.courseId = id; + form.querySelector("button").textContent = "Actualizar Curso"; + form.scrollIntoView({ behavior: "smooth" }); + }); +}; + +window.deleteCourse = function (id) { + if (!confirm("¿Estás seguro de eliminar este curso?")) return; + + fetch(`api/cursos.php?id=${id}`, { + method: "DELETE", + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + showSection("courses"); + } + }) + .catch((error) => console.error("Error:", error)); +}; + +// Gestión de alumnos +function loadStudentsManagement(container) { + container.innerHTML = '
Cargando alumnos...
'; + + fetch("api/alumnos.php") + .then((response) => { + if (!response.ok) throw new Error("Error en la respuesta del servidor"); + return response.json(); + }) + .then((data) => { + if (!data.success) + throw new Error(data.error || "Error al obtener alumnos"); + + container.innerHTML = ` +
+ ${renderStudentForm()} + ${renderStudentsTable(data.data || [])} +
+ `; + + setupStudentForm(); + (data.data || []).forEach((alumno) => loadStudentCourses(alumno.id)); + }) + .catch((error) => { + console.error("Error:", error); + container.innerHTML = ` +
+

Error al cargar alumnos

+

${error.message}

+ +
`; + }); +} + +function renderStudentForm() { + return ` +
+

Gestión de Alumnos

+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
`; +} + +function setupStudentForm() { + const form = document.getElementById("studentForm"); + if (!form) return; + + form.addEventListener("submit", function (e) { + e.preventDefault(); + submitStudentForm(); + }); + + // Configurar búsqueda + const searchInput = document.getElementById("studentSearch"); + if (searchInput) { + searchInput.addEventListener("input", function () { + filterStudents(this.value.toLowerCase()); + }); } } -function handleDownloadDiploma(courseId) { - const btn = document.querySelector(`.download-btn[data-course-id="${courseId}"]`); - const originalText = btn.innerHTML; - - btn.innerHTML = ''; - btn.disabled = true; - - // Simular generación de diploma - setTimeout(() => { - window.open(`certificado.php?course_id=${courseId}`, '_blank'); - btn.innerHTML = originalText; - btn.disabled = false; - showToast('Diploma generado con éxito', 'success'); - }, 1500); +function submitStudentForm() { + const form = document.getElementById("studentForm"); + const submitBtn = form.querySelector('button[type="submit"]'); + const submitText = document.getElementById("submitText"); + const spinner = document.getElementById("submitSpinner"); + + // Mostrar spinner + submitText.textContent = "Procesando..."; + spinner.style.display = "inline-block"; + submitBtn.disabled = true; + + const formData = new FormData(form); + const jsonData = { + nombre: formData.get("nombre"), + email: formData.get("email"), + telefono: formData.get("telefono"), + }; + + const isEdit = form.dataset.editing === "true"; + const studentId = form.dataset.studentId; + + const url = "api/alumnos.php" + (isEdit ? "" : ""); + const method = isEdit ? "PUT" : "POST"; + + if (isEdit) { + jsonData.id = studentId; + } + + fetch(url, { + method: method, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonData), + }) + .then((response) => response.json()) + .then((data) => { + if (!data.success) + throw new Error(data.error || "Error al guardar alumno"); + + showToast( + "success", + data.message || (isEdit ? "Alumno actualizado" : "Alumno creado") + ); + resetStudentForm(); + loadStudentsManagement(document.querySelector("#students-content")); + }) + .catch((error) => { + showToast("error", error.message || "Error en el servidor"); + }) + .finally(() => { + submitText.textContent = "Guardar"; + spinner.style.display = "none"; + submitBtn.disabled = false; + }); } -async function fetchData(url, options = {}) { + +window.createStudent = function (data) { + fetch("api/alumnos.php", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + showSection("students"); + } + }) + .catch((error) => console.error("Error:", error)); +}; + +window.updateStudent = function (id, data) { + data.id = id; + + fetch("api/alumnos.php", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + showSection("students"); + } + }) + .catch((error) => console.error("Error:", error)); +}; + +window.editStudent = function (id) { + fetch(`api/alumnos.php`) + .then((response) => response.json()) + .then((data) => { + if (!data.success) + throw new Error(data.error || "Error al obtener alumnos"); + + const alumno = data.data.find((a) => a.id == id); + if (!alumno) throw new Error("Alumno no encontrado"); + + const form = document.getElementById("studentForm"); + form.nombre.value = alumno.nombre; + form.email.value = alumno.email; + form.telefono.value = alumno.telefono || ""; + + form.dataset.editing = "true"; + form.dataset.studentId = id; + + document.getElementById("submitText").textContent = "Actualizar"; + document.getElementById("cancelBtn").style.display = "inline-block"; + + form.scrollIntoView({ behavior: "smooth" }); + }) + .catch((error) => { + showToast("error", error.message || "Error al cargar alumno"); + }); +}; + +window.deleteStudent = function (id) { + fetch(`api/alumnos.php?id=${id}`, { + method: "DELETE", + }) + .then((response) => response.json()) + .then((data) => { + if (!data.success) + throw new Error(data.error || "Error al eliminar alumno"); + + showToast("success", data.message || "Alumno eliminado"); + closeModal(); + loadStudentsManagement(document.querySelector("#students-content")); + }) + .catch((error) => { + showToast("error", error.message || "Error al eliminar alumno"); + }); +}; +window.confirmDeleteStudent = function (id) { + showModal( + "Confirmar eliminación", + "¿Estás seguro de eliminar este alumno? Esta acción no se puede deshacer.", + [ + { + text: "Cancelar", + class: "btn-outline", + handler: "closeModal", + }, + { + text: "Eliminar", + class: "btn-danger", + handler: () => deleteStudent(id), + }, + ] + ); +}; +window.resetStudentForm = function () { + const form = document.getElementById("studentForm"); + form.reset(); + form.dataset.editing = "false"; + delete form.dataset.studentId; + + document.getElementById("submitText").textContent = "Guardar"; + document.getElementById("cancelBtn").style.display = "none"; +}; +function filterStudents(searchTerm) { + const rows = document.querySelectorAll(".students-table tbody tr"); + + rows.forEach((row) => { + const nombre = row.cells[0].textContent.toLowerCase(); + const email = row.cells[1].textContent.toLowerCase(); + const telefono = row.cells[2].textContent.toLowerCase(); + + if ( + nombre.includes(searchTerm) || + email.includes(searchTerm) || + telefono.includes(searchTerm) + ) { + row.style.display = ""; + } else { + row.style.display = "none"; + } + }); +} + +//alumnos-cursos + +function renderStudentsTable(alumnos) { + if (alumnos.length === 0) { + return ` +
+
+ + + + + + +

No hay alumnos registrados

+

Comienza agregando tu primer alumno

+
+
`; + } + + return ` +
+
+

Lista de Alumnos

+
+ + +
+
+
+ + + + + + + + + + + + ${alumnos + .map( + (alumno) => ` + + + + + + + + ` + ) + .join("")} + +
NombreEmailTeléfonoCursosAcciones
${alumno.nombre}${alumno.email}${alumno.telefono || "-"} +
+ ${renderCourseBadges(alumno.id)} +
+
+
+ + +
+
+
+
`; +} + +function renderCourseBadges(alumnoId) { + return '
'; // Se cargará dinámicamente +} + +async function loadStudentCourses(alumnoId) { try { - const response = await fetch(url, options); - - if (!response.ok) { - throw new Error(`Error HTTP: ${response.status}`); - } - + const response = await fetch( + `api/alumnos-cursos.php?alumno_id=${alumnoId}` + ); const data = await response.json(); - - // Verificar si la respuesta es válida - if (data === null || data === undefined) { - throw new Error('Respuesta vacía del servidor'); + + if (data.success) { + const container = document.getElementById(`courses-${alumnoId}`); + if (container) { + container.innerHTML = + data.data + .map( + (curso) => ` + + ${curso.nombre} + + + ` + ) + .join("") || 'Sin cursos'; + } } - - return data; } catch (error) { - console.error(`Error fetching data from ${url}:`, error); - throw error; // Re-lanzamos el error para manejarlo en el nivel superior + console.error("Error loading student courses:", error); } -} \ No newline at end of file +} + +window.showAssignStudentModal = async function () { + try { + console.log("Mostrando modal de vinculación"); + + const profesorId = getProfesorId(); + if (!profesorId) { + console.error("No se pudo obtener el ID del profesor"); + showToast("error", "No se pudo identificar al profesor"); + return; + } + + // Mostrar loader mientras se cargan los datos + const tempModalContent = `
Cargando datos...
`; + showModal("Vincular Alumnos a Curso", tempModalContent); + + // Cargar cursos y alumnos en paralelo + const [cursosRes, alumnosRes] = await Promise.all([ + fetch(`api/cursos.php?profesor_id=${profesorId}`), + fetch("api/alumnos.php"), + ]); + + const [cursosData, alumnosData] = await Promise.all([ + cursosRes.json(), + alumnosRes.json(), + ]); + + if (!cursosData || !alumnosData || !alumnosData.success) { + closeModal(); + throw new Error("Error al cargar datos necesarios"); + } + + // Crear contenido del modal + const modalHtml = ` +
+ + +
+ +
+ +
+ ${ + alumnosData.data.length > 0 + ? alumnosData.data + .map( + (alumno) => ` + + ` + ) + .join("") + : "

No hay alumnos disponibles

" + } +
+
+ `; + + // Actualizar el contenido del modal + const modalBody = document.querySelector(".modal-body"); + if (modalBody) { + modalBody.innerHTML = modalHtml; + } + + // Crear el footer si no existe + let modalFooter = document.querySelector(".modal-footer"); + if (!modalFooter) { + modalFooter = document.createElement("div"); + modalFooter.className = "modal-footer"; + document.querySelector(".modal").appendChild(modalFooter); + } + + // Actualizar el contenido del footer + modalFooter.innerHTML = ` + + + `; + + // Función para vincular alumnos seleccionados + window.assignStudentsToCourse = async function () { + const cursoId = document.getElementById("selectCurso").value; + const selectedAlumnos = Array.from( + document.querySelectorAll('input[name="alumnos"]:checked') + ).map((input) => input.value); + + if (selectedAlumnos.length === 0) { + showToast("warning", "Selecciona al menos un alumno"); + return; + } + + try { + const response = await fetch("api/alumnos-cursos.php", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + curso_id: cursoId, + alumnos: selectedAlumnos, + }), + }); + + const result = await response.json(); + if (result.success) { + showToast("success", "Alumnos vinculados exitosamente"); + closeModal(); + loadStudentsManagement(document.querySelector("#students-content")); + } else { + showToast("error", result.error || "Error al vincular"); + } + } catch (error) { + console.error("Error al vincular:", error); + showToast("error", "Error al realizar la vinculación"); + } + }; + } catch (error) { + console.error("Error en showAssignStudentModal:", error); + closeModal(); + showToast("error", "Error al cargar datos para vinculación"); + } +}; + +window.unassignStudent = async function (alumnoId, cursoId) { + if (confirm("¿Desvincular este alumno del curso?")) { + try { + const response = await fetch( + `api/alumnos-cursos.php?alumno_id=${alumnoId}&curso_id=${cursoId}`, + { + method: "DELETE", + } + ); + + const data = await response.json(); + + if (data.success) { + showToast("success", data.message); + loadStudentCourses(alumnoId); + } else { + showToast("error", data.error || "Error al desvincular"); + } + } catch (error) { + showToast("error", "Error en la conexión"); + } + } +}; + +// Gestión de diplomas +function loadDiplomasSection(container) { + container.innerHTML = '
Cargando diplomas...
'; + + fetch(`api/diplomas.php?profesor_id=${getProfesorId()}`) + .then((response) => { + if (!response.ok) throw new Error("Error en la respuesta del servidor"); + return response.json(); + }) + .then((data) => { + if (!data.success) + throw new Error(data.error || "Error al obtener diplomas"); + + if (data.data.length === 0) { + container.innerHTML = ` +
+

Diplomas Emitidos

+

No hay diplomas registrados aún

+
`; + return; + } + + container.innerHTML = ` +
+

Diplomas Emitidos

+
+ + + + + + + + + + + + + + ${data.data + .map( + (diploma) => ` + + + + + + + + + + ` + ) + .join("")} + +
AlumnoEmailCursoTipoFechaCódigoAcciones
${diploma.alumno_nombre}${diploma.alumno_email}${diploma.curso_nombre}${diploma.curso_tipo}${diploma.fecha_formateada}${diploma.codigo_unico} + + +
+
+
`; + }) + .catch((error) => { + console.error("Error:", error); + container.innerHTML = ` +
+

Error al cargar diplomas

+

${error.message}

+ +
`; + }); +} + +// Funciones auxiliares +function generateCoursesPreview(courses) { + if (!courses.length) return "

No tienes cursos activos

"; + + return ` +
+ + + + + + + + + + ${courses + .map( + (course) => ` + + + + + + ` + ) + .join("")} + +
NombreTipoEstado
${course.nombre || "Sin nombre"}${ + course.tipo || "N/A" + }${course.estado || "N/A"}
+
`; +} + +function getCourseTypeClass(type) { + const types = { + inyeccion: "type-inyeccion", + pildora: "type-pildora", + tratamiento: "type-tratamiento", + }; + return types[type] || ""; +} + +// Funciones globales para diplomas +window.downloadDiploma = function (codigo) { + window.open(`certificado.php?codigo=${codigo}`, "_blank"); +}; + +window.resendDiploma = function (codigo) { + fetch(`api/diplomas.php?action=resend&codigo=${codigo}`) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + alert("Diploma reenviado exitosamente"); + } else { + alert("Error: " + (data.error || "No se pudo reenviar el diploma")); + } + }) + .catch((error) => { + console.error("Error:", error); + alert("Error al reenviar el diploma"); + }); +}; diff --git a/dashboard.php b/dashboard.php index 06ff4ee..b8f00c8 100644 --- a/dashboard.php +++ b/dashboard.php @@ -2,7 +2,36 @@ include 'includes/config.php'; redirect_if_not_logged_in(); -$user = $_SESSION['user']; + +$profesor = $_SESSION['profesor']; + +// Obtener estadísticas directamente desde PHP +$stmt = $pdo->prepare(" + SELECT COUNT(*) as total FROM cursos + WHERE profesor_id = ? AND estado = 'activo' +"); +$stmt->execute([$profesor['id']]); +$cursos_activos = $stmt->fetch()['total']; + +$stmt = $pdo->prepare(" + SELECT COUNT(DISTINCT a.id) as total + FROM alumnos a + JOIN alumnos_cursos ac ON a.id = ac.alumno_id + JOIN cursos c ON ac.curso_id = c.id + WHERE c.profesor_id = ? +"); +$stmt->execute([$profesor['id']]); +$alumnos_registrados = $stmt->fetch()['total']; + +$stmt = $pdo->prepare(" + SELECT COUNT(*) as total + FROM diplomas d + JOIN alumnos_cursos ac ON d.alumno_curso_id = ac.id + JOIN cursos c ON ac.curso_id = c.id + WHERE c.profesor_id = ? +"); +$stmt->execute([$profesor['id']]); +$diplomas_emitidos = $stmt->fetch()['total']; ?> @@ -11,24 +40,25 @@ $user = $_SESSION['user']; DiploMaster - Panel - - +

DiploMaster

-
- -
+
+
+ +
+
@@ -36,13 +66,13 @@ $user = $_SESSION['user'];
-

Panel de Administración

-

Bienvenido al sistema de gestión de DiploMaster

+

Bienvenido

+

Este es tu panel de gestión de DiploMaster

-

Estadísticas:

-

0 cursos activos

-

0 estudiantes registrados

-

0 diplomas emitidos

+

Resumen:

+

cursos activos

+

alumnos registrados

+

diplomas emitidos

diff --git a/hash.php b/hash.php new file mode 100644 index 0000000..9695f31 --- /dev/null +++ b/hash.php @@ -0,0 +1,14 @@ +"; +echo "Hash generado: " . $hash . "
"; + +// Verificación +if (password_verify($contraseña, $hash)) { + echo "✅ La contraseña coincide con el hash!"; +} else { + echo "❌ La contraseña NO coincide"; +} +?> \ No newline at end of file diff --git a/includes/config.php b/includes/config.php index 930bb62..acd4194 100644 --- a/includes/config.php +++ b/includes/config.php @@ -21,7 +21,7 @@ try { } function is_logged_in() { - return isset($_SESSION['user']); + return isset($_SESSION['profesor']); } function redirect_if_not_logged_in() { @@ -29,5 +29,4 @@ function redirect_if_not_logged_in() { header('Location: index.php'); exit; } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/index.php b/index.php index 73ec632..cda4885 100644 --- a/index.php +++ b/index.php @@ -17,14 +17,14 @@

DiploMaster

-

Bienvenido al sistema de diplomas

+

Sistema de gestión de diplomas para profesores

- +
@@ -40,11 +40,6 @@ - -
diff --git a/sql/diplomaster (1).sql b/sql/diplomaster (1).sql index fb22b48..daac7be 100644 --- a/sql/diplomaster (1).sql +++ b/sql/diplomaster (1).sql @@ -1,94 +1,75 @@ +-- Crear la base de datos +CREATE DATABASE IF NOT EXISTS diplomaster; +USE diplomaster; - -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -START TRANSACTION; -SET time_zone = "+00:00"; - -CREATE database diplomaster; -use diplomaster; - - -CREATE TABLE `cursos` ( - `id` int(11) NOT NULL, - `nombre` varchar(100) NOT NULL, - `tipo` enum('pildora','inyeccion','tratamiento') NOT NULL, - `competencias` text DEFAULT NULL, - `fecha_creacion` timestamp NOT NULL DEFAULT current_timestamp() -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - - - -INSERT INTO `cursos` (`id`, `nombre`, `tipo`, `competencias`, `fecha_creacion`) VALUES -(1, 'Seguridad Informática', 'tratamiento', 'Análisis de datos, Comunicación efectiva', '2025-05-05 01:27:58'), -(2, 'Introducción a Python', 'pildora', NULL, '2025-05-05 01:27:58'), -(3, 'Machine Learning Avanzado', 'tratamiento', 'Modelado predictivo, Python', '2025-05-05 01:27:58'); - - - +-- Tabla de usuarios (profesores) CREATE TABLE `usuarios` ( - `id` int(11) NOT NULL, - `username` varchar(50) NOT NULL, - `password` varchar(255) NOT NULL, - `nombre` varchar(100) NOT NULL, - `email` varchar(100) NOT NULL, - `rol` enum('admin','user') NOT NULL DEFAULT 'user', - `fecha_registro` timestamp NOT NULL DEFAULT current_timestamp() -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + `id` INT(11) NOT NULL AUTO_INCREMENT, + `nombre` VARCHAR(100) NOT NULL, + `email` VARCHAR(100) UNIQUE NOT NULL, + `password` VARCHAR(255) NOT NULL, + `aprobado` BOOLEAN NOT NULL DEFAULT 1, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- Tabla de cursos +CREATE TABLE `cursos` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `nombre` VARCHAR(100) NOT NULL, + `descripcion` VARCHAR(250) DEFAULT NULL, + `estado` ENUM('activo', 'completado', 'archivado') NOT NULL DEFAULT 'activo', + `tipo` ENUM('inyeccion', 'pildora', 'tratamiento') NOT NULL, + `profesor_id` INT(11) NOT NULL, + PRIMARY KEY (`id`), + FOREIGN KEY (`profesor_id`) REFERENCES `usuarios`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- Tabla de alumnos +CREATE TABLE `alumnos` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `nombre` VARCHAR(100) NOT NULL, + `email` VARCHAR(100) UNIQUE NOT NULL, + `telefono` VARCHAR(15) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -INSERT INTO `usuarios` (`id`, `username`, `password`, `nombre`, `email`, `rol`, `fecha_registro`) VALUES -(1, 'admin', '$2y$10$H3tCRUt444g0jo996uiKXenINy2d84FfuwQhoDBfa3tNblZLtNZpK', 'Administrador', 'admin@diplomaster.com', 'admin', '2025-05-05 01:27:58'), -(2, 'usuario1', '$2y$10$SjyU29E200ax73/m0NTjqe0sMLmpsPBThlIUucGKyxvqh/znHiwh.', 'Juan Pérez', 'juan@example.com', 'user', '2025-05-05 01:27:58'); +-- Tabla para vincular alumnos con cursos +CREATE TABLE `alumnos_cursos` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `alumno_id` INT(11) NOT NULL, + `curso_id` INT(11) NOT NULL, + `estado` ENUM('cursando', 'aprobado', 'reprobado') NOT NULL DEFAULT 'cursando', + PRIMARY KEY (`id`), + FOREIGN KEY (`alumno_id`) REFERENCES `alumnos`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`curso_id`) REFERENCES `cursos`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- Tabla de diplomas +CREATE TABLE `diplomas` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `alumno_curso_id` INT(11) NOT NULL, + `codigo_unico` VARCHAR(20) NOT NULL UNIQUE, + `fecha_emision` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + FOREIGN KEY (`alumno_curso_id`) REFERENCES `alumnos_cursos`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- Datos iniciales +INSERT INTO `usuarios` (`nombre`, `email`, `password`, `aprobado`) VALUES +('Profesor Demo', 'profesor@demo.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 1); -CREATE TABLE `usuario_cursos` ( - `id` int(11) NOT NULL, - `usuario_id` int(11) NOT NULL, - `curso_id` int(11) NOT NULL, - `estado` enum('En progreso','Aprobado','Completado') NOT NULL DEFAULT 'En progreso', - `fecha_inicio` date DEFAULT NULL, - `fecha_fin` date DEFAULT NULL, - `profesor` varchar(100) DEFAULT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +INSERT INTO `cursos` (`nombre`, `descripcion`, `estado`, `tipo`, `profesor_id`) VALUES +('Curso Básico', 'Curso introductorio', 'activo', 'inyeccion', 1), +('Curso Avanzado', 'Para alumnos avanzados', 'activo', 'tratamiento', 1); +INSERT INTO `alumnos` (`nombre`, `email`, `telefono`) VALUES +('Alumno Uno', 'alumno1@demo.com', '123456789'), +('Alumno Dos', 'alumno2@demo.com', '987654321'); +INSERT INTO `alumnos_cursos` (`alumno_id`, `curso_id`, `estado`) VALUES +(1, 1, 'aprobado'), +(2, 1, 'cursando'), +(1, 2, 'cursando'); -INSERT INTO `usuario_cursos` (`id`, `usuario_id`, `curso_id`, `estado`, `fecha_inicio`, `fecha_fin`, `profesor`) VALUES -(1, 2, 1, 'Aprobado', '2023-01-15', '2023-04-20', 'Dra. Ana López'), -(2, 2, 2, 'En progreso', '2023-05-10', NULL, 'Prof. Carlos Ruiz'); - - - -ALTER TABLE `cursos` - ADD PRIMARY KEY (`id`); - - -ALTER TABLE `usuarios` - ADD PRIMARY KEY (`id`), - ADD UNIQUE KEY `username` (`username`), - ADD UNIQUE KEY `email` (`email`); - -ALTER TABLE `usuario_cursos` - ADD PRIMARY KEY (`id`), - ADD KEY `usuario_id` (`usuario_id`), - ADD KEY `curso_id` (`curso_id`); - - -ALTER TABLE `cursos` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; - - -ALTER TABLE `usuarios` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; - - -ALTER TABLE `usuario_cursos` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; - - -ALTER TABLE `usuario_cursos` - ADD CONSTRAINT `usuario_cursos_ibfk_1` FOREIGN KEY (`usuario_id`) REFERENCES `usuarios` (`id`), - ADD CONSTRAINT `usuario_cursos_ibfk_2` FOREIGN KEY (`curso_id`) REFERENCES `cursos` (`id`); -COMMIT; +INSERT INTO `diplomas` (`alumno_curso_id`, `codigo_unico`) VALUES +(1, 'DIPL-000001'); \ No newline at end of file