diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..57b26f9 Binary files /dev/null and b/.DS_Store differ diff --git a/api/alumnos-cursos.php b/api/alumnos-cursos.php index 32165b3..6b43be8 100644 --- a/api/alumnos-cursos.php +++ b/api/alumnos-cursos.php @@ -14,96 +14,123 @@ $profesorId = $_SESSION['profesor']['id'] ?? null; try { switch ($method) { case 'GET': - $alumnoId = $_GET['alumno_id'] ?? null; - + $alumnoId = $_GET['alumno_id'] ?? null; + $tipoCurso = $_GET['tipo'] ?? null; + $estadoCurso = $_GET['estado'] ?? 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 = ?"; + // Obtener cursos de un alumno específico (incluyendo competencias si aplica) + $query = "SELECT + c.id, + c.nombre, + c.tipo, + c.estado, + c.competencias + 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; - + $query = " + SELECT + ac.id, + ac.alumno_id, + ac.curso_id, + c.nombre AS curso_nombre, + c.tipo, + c.estado, + c.competencias + FROM alumnos_cursos ac + JOIN cursos c ON ac.curso_id = c.id + WHERE c.profesor_id = ? + "; + $params = [$profesorId]; + + if ($tipoCurso) { + $query .= " AND c.tipo = ?"; + $params[] = $tipoCurso; + } + + if ($estadoCurso) { + $query .= " AND c.estado = ?"; + $params[] = $estadoCurso; + } + + $stmt = $pdo->prepare($query); + $stmt->execute($params);? + } + + 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'] ?? []; + $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) { + if (empty($cursoId) || empty($alumnos)) { http_response_code(400); - echo json_encode(['success' => false, 'error' => 'IDs requeridos']); + 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; } - - // Eliminar asignación + + 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; + } + + $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; + } + $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']); @@ -112,4 +139,3 @@ try { 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 index 27adf55..585a2c6 100644 --- a/api/alumnos.php +++ b/api/alumnos.php @@ -27,88 +27,178 @@ try { ]); break; - case 'POST': - $data = json_decode(file_get_contents('php://input'), true); + 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; - } + 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 - ]); + // Verificar email duplicado + $stmt = $pdo->prepare("SELECT id FROM alumnos WHERE email = ?"); + $stmt->execute([$data['email']]); + if ($stmt->fetch()) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'El correo ya está registrado']); + exit; + } - echo json_encode([ - 'success' => true, - 'id' => $pdo->lastInsertId(), - 'message' => 'Alumno creado exitosamente' - ]); - break; + // Verificar teléfono duplicado + if (!empty($data['telefono'])) { + $stmt = $pdo->prepare("SELECT id FROM alumnos WHERE telefono = ?"); + $stmt->execute([$data['telefono']]); + if ($stmt->fetch()) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'El teléfono ya está registrado']); + exit; + } + } - case 'PUT': - $data = json_decode(file_get_contents('php://input'), true); + $pdo->beginTransaction(); + try { + // Insertar alumno + $stmt = $pdo->prepare(" + INSERT INTO alumnos (nombre, email, telefono) + VALUES (?, ?, ?) + "); + $stmt->execute([ + $data['nombre'], + $data['email'], + $data['telefono'] ?? null + ]); - if (empty($data['id']) || empty($data['nombre']) || empty($data['email'])) { - http_response_code(400); - echo json_encode(['success' => false, 'error' => 'Datos incompletos']); - exit; - } + $alumnoId = $pdo->lastInsertId(); - $stmt = $pdo->prepare(" - UPDATE alumnos SET - nombre = ?, - email = ?, - telefono = ? - WHERE id = ? - "); - $stmt->execute([ - $data['nombre'], - $data['email'], - $data['telefono'] ?? null, - $data['id'] - ]); + // Si viene curso_id, asignar también a curso + if (!empty($data['curso_id'])) { + $stmt = $pdo->prepare(" + INSERT INTO alumnos_cursos (alumno_id, curso_id, estado) + VALUES (?, ?, 'cursando') + "); + $stmt->execute([$alumnoId, $data['curso_id']]); + } - echo json_encode([ - 'success' => true, - 'message' => 'Alumno actualizado exitosamente' - ]); - break; + $pdo->commit(); + echo json_encode([ + 'success' => true, + 'id' => $alumnoId, + 'message' => 'Alumno creado y asignado exitosamente' + ]); + } catch (PDOException $e) { + $pdo->rollBack(); + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Error al registrar: ' . $e->getMessage()]); + } + 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; - } + 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; + } + + // Verificar email duplicado (excluyendo el mismo alumno) + $stmt = $pdo->prepare("SELECT id FROM alumnos WHERE email = ? AND id != ?"); + $stmt->execute([$data['email'], $data['id']]); + if ($stmt->fetch()) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'El correo ya está registrado por otro alumno']); + exit; + } + + // Verificar teléfono duplicado (excluyendo el mismo alumno) + if (!empty($data['telefono'])) { + $stmt = $pdo->prepare("SELECT id FROM alumnos WHERE telefono = ? AND id != ?"); + $stmt->execute([$data['telefono'], $data['id']]); + if ($stmt->fetch()) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'El teléfono ya está registrado por otro alumno']); + exit; + } + } + + $stmt = $pdo->prepare(" + UPDATE alumnos SET + nombre = ?, + email = ?, + telefono = ? + WHERE id = ? + "); + $stmt->execute([ + $data['nombre'], + $data['email'], + $data['telefono'] ?? null, + $data['id'] + ]); + + // Si curso_id viene en la actualización, actualizar también el curso + if (!empty($data['curso_id'])) { + // Verifica si ya tiene una relación previa + $check = $pdo->prepare("SELECT id FROM alumnos_cursos WHERE alumno_id = ?"); + $check->execute([$data['id']]); + + if ($check->fetch()) { + // Si ya tiene relación, actualizamos el curso asignado + $updateCurso = $pdo->prepare(" + UPDATE alumnos_cursos SET curso_id = ?, estado = 'cursando' + WHERE alumno_id = ? + "); + $updateCurso->execute([$data['curso_id'], $data['id']]); + } else { + // Si no tiene relación previa, la insertamos + $insertCurso = $pdo->prepare(" + INSERT INTO alumnos_cursos (alumno_id, curso_id, estado) + VALUES (?, ?, 'cursando') + "); + $insertCurso->execute([$data['id'], $data['curso_id']]); + } + } + + echo json_encode([ + 'success' => true, + 'message' => 'Alumno actualizado exitosamente' + ]); + break; + - // 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; + case 'DELETE': + $id = $_GET['id'] ?? null; + if (!$id) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'ID de alumno no proporcionado']); + exit; + } + + try { + $pdo->beginTransaction(); + + // Eliminar vinculaciones alumno_curso + $stmt = $pdo->prepare("DELETE FROM alumnos_cursos WHERE alumno_id = ?"); + $stmt->execute([$id]); + + // Eliminar alumno + $stmt = $pdo->prepare("DELETE FROM alumnos WHERE id = ?"); + $stmt->execute([$id]); + + $pdo->commit(); + + echo json_encode([ + 'success' => true, + 'message' => 'Alumno eliminado exitosamente' + ]); + } catch (PDOException $e) { + $pdo->rollBack(); + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Error al eliminar alumno: ' . $e->getMessage()]); + } + break; + default: http_response_code(405); diff --git a/api/cursos.php b/api/cursos.php index ba0a6da..c993cc6 100644 --- a/api/cursos.php +++ b/api/cursos.php @@ -14,73 +14,107 @@ $profesorId = $_SESSION['profesor']['id']; switch ($method) { case 'GET': try { - $query = "SELECT * FROM cursos WHERE profesor_id = ?"; - $stmt = $pdo->prepare($query); - $stmt->execute([$profesorId]); - echo json_encode($stmt->fetchAll()); + $tipo = $_GET['tipo'] ?? null; + if ($tipo) { + $query = "SELECT * FROM cursos WHERE profesor_id = ? AND tipo = ?"; + $stmt = $pdo->prepare($query); + $stmt->execute([$profesorId, $tipo]); + } else { + $query = "SELECT * FROM cursos WHERE profesor_id = ?"; + $stmt = $pdo->prepare($query); + $stmt->execute([$profesorId]); + } + echo json_encode(['success' => true, 'data' => $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); - + if (empty($data['nombre']) || empty($data['tipo'])) { http_response_code(400); echo json_encode(['error' => 'Nombre y tipo son requeridos']); exit; } - + try { + // Validar que no exista otro curso con el mismo nombre y tipo para ese profesor + $stmt = $pdo->prepare("SELECT id FROM cursos WHERE nombre = ? AND tipo = ? AND profesor_id = ?"); + $stmt->execute([$data['nombre'], $data['tipo'], $profesorId]); + if ($stmt->fetch()) { + echo json_encode([ + 'success' => false, + 'error' => "Ya existe un curso llamado '{$data['nombre']}' de tipo '{$data['tipo']}'." + ]); + exit; + } + $stmt = $pdo->prepare(" - INSERT INTO cursos (nombre, descripcion, tipo, competencias, estado, profesor_id) - VALUES (?, ?, ?, ?, ?, ?) + INSERT INTO cursos (nombre, descripcion, tipo, competencias, docente, horas_trabajadas, estado, profesor_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ $data['nombre'], $data['descripcion'] ?? null, $data['tipo'], $data['competencias'] ?? null, + $data['docente'] ?? null, + $data['horas_trabajadas'] ?? null, $data['estado'], $profesorId ]); - + echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]); } catch (PDOException $e) { http_response_code(500); - echo json_encode(['error' => 'Error al crear curso']); + echo json_encode(['error' => $e->getMessage()]); } + 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; + + 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; } - + + // Validar que no haya otro curso con el mismo nombre y tipo (excluyendo el actual) + $stmt = $pdo->prepare("SELECT id FROM cursos WHERE nombre = ? AND tipo = ? AND profesor_id = ? AND id != ?"); + $stmt->execute([$data['nombre'], $data['tipo'], $profesorId, $data['id']]); + if ($stmt->fetch()) { + echo json_encode([ + 'success' => false, + 'error' => "Ya existe otro curso llamado '{$data['nombre']}' de tipo '{$data['tipo']}'." + ]); + exit; + } + $stmt = $pdo->prepare(" UPDATE cursos SET - nombre = ?, - descripcion = ?, - tipo = ?, - competencias = ?, - estado = ? + nombre = ?, + descripcion = ?, + tipo = ?, + competencias = ?, + docente = ?, + horas_trabajadas = ?, + estado = ? WHERE id = ? "); $stmt->execute([ @@ -88,17 +122,19 @@ switch ($method) { $data['descripcion'] ?? null, $data['tipo'], $data['competencias'] ?? null, + $data['docente'] ?? null, + $data['horas_trabajadas'] ?? null, $data['estado'], $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) { @@ -106,30 +142,30 @@ switch ($method) { 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 +?> diff --git a/api/dashboard_data.php b/api/dashboard_data.php new file mode 100644 index 0000000..436d50a --- /dev/null +++ b/api/dashboard_data.php @@ -0,0 +1,40 @@ + "No autenticado"]); + exit; +} + +function getAll($pdo, $sql, $params = []) { + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +$cursos = getAll($pdo, "SELECT * FROM cursos WHERE profesor_id = ?", [$profesor_id]); + +$alumnos = getAll($pdo, " + SELECT DISTINCT a.* 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 = ? +", [$profesor_id]); + +$diplomas = getAll($pdo, " + SELECT d.* 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 = ? +", [$profesor_id]); + +echo json_encode([ + "cursos" => $cursos, + "alumnos" => $alumnos, + "diplomas" => $diplomas, + "lastUpdated" => date("Y-m-d H:i:s") +]); diff --git a/api/diploma.php b/api/diploma.php deleted file mode 100644 index c2a0cc2..0000000 --- a/api/diploma.php +++ /dev/null @@ -1,44 +0,0 @@ - '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/diplomas.php b/api/diplomas.php new file mode 100644 index 0000000..20cbe9b --- /dev/null +++ b/api/diplomas.php @@ -0,0 +1,96 @@ + false, 'error' => 'No autenticado']); + exit; +} + +$method = $_SERVER['REQUEST_METHOD']; +$profesorId = $_SESSION['profesor']['id'] ?? null; + +switch ($method) { + case 'POST': + $input = json_decode(file_get_contents('php://input'), true); + $alumnoCursoId = $input['alumno_curso_id'] ?? null; + + if (!$alumnoCursoId) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Falta ID de alumno_curso']); + exit; + } + + $stmt = $pdo->prepare(" + SELECT ac.id + FROM alumnos_cursos ac + JOIN cursos c ON ac.curso_id = c.id + WHERE ac.id = ? AND c.profesor_id = ? + "); + $stmt->execute([$alumnoCursoId, $profesorId]); + + if (!$stmt->fetch()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'No autorizado']); + exit; + } + + $stmt = $pdo->prepare("SELECT codigo_unico FROM diplomas WHERE alumno_curso_id = ?"); + $stmt->execute([$alumnoCursoId]); + $existing = $stmt->fetchColumn(); + + if ($existing) { + http_response_code(409); + echo json_encode(['success' => false, 'error' => 'Este alumno ya tiene un diploma', 'codigo_unico' => $existing]); + exit; + } + + $codigo = strtoupper(substr(uniqid(), -6)); + $fecha = date('Y-m-d'); + + // Insertar nuevo diploma + $stmt = $pdo->prepare(" + INSERT INTO diplomas (alumno_curso_id, codigo_unico, fecha_emision) + VALUES (?, ?, ?) + "); + $stmt->execute([$alumnoCursoId, $codigo, $fecha]); + + echo json_encode([ + 'success' => true, + 'codigo_unico' => $codigo + ]); + break; + + case 'GET': + if (!$profesorId) { + echo json_encode(['success' => false, 'error' => 'Profesor no identificado']); + exit; + } + + $stmt = $pdo->prepare(" + SELECT d.codigo_unico, d.fecha_emision, + ac.id AS alumno_curso_id, + a.nombre AS alumno_nombre, a.email AS alumno_email, + c.nombre AS curso_nombre, c.tipo AS curso_tipo + 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 + WHERE c.profesor_id = ? + ORDER BY d.fecha_emision DESC + "); + $stmt->execute([$profesorId]); + + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + + echo json_encode([ + 'success' => true, + 'data' => $result + ]); + break; + + default: + http_response_code(405); + echo json_encode(['success' => false, 'error' => 'Método no permitido']); +} diff --git a/api/enviar_diploma.php b/api/enviar_diploma.php new file mode 100644 index 0000000..7b5128b --- /dev/null +++ b/api/enviar_diploma.php @@ -0,0 +1,135 @@ + false, 'error' => 'Código no proporcionado']); + exit; +} + +$stmt = $pdo->prepare(" + SELECT + a.nombre AS alumno_nombre, + a.email AS alumno_email, + a.telefono AS alumno_telefono, + c.nombre AS curso_nombre, + c.tipo, + c.horas_trabajadas, + c.competencias, + d.fecha_emision + 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 + WHERE d.codigo_unico = ? +"); +$stmt->execute([$codigo]); +$diploma = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$diploma) { + http_response_code(404); + echo json_encode(['success' => false, 'error' => 'Diploma no encontrado']); + exit; +} + +function t($txt) { + return iconv('UTF-8', 'windows-1252//TRANSLIT', $txt); +} + +$alumno = $diploma['alumno_nombre']; +$email = $diploma['alumno_email']; +$telefono = $diploma['alumno_telefono']; +$telefonoFormateado = '52' . preg_replace('/\D/', '', $telefono); +$curso = $diploma['curso_nombre']; +$tipo = $diploma['tipo']; +$horas = $diploma['horas_trabajadas']; +$competencias = $diploma['competencias'] ?? ''; +$fecha = new DateTime($diploma['fecha_emision']); +$dia = str_pad($fecha->format('d'), 2, '0', STR_PAD_LEFT); +$mes = $fecha->format('m'); +$anio = $fecha->format('Y'); + +$host = $_SERVER['HTTP_HOST']; +$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http"; +$baseDir = explode('/api', $_SERVER['SCRIPT_NAME'])[0]; +$validationUrl = "$protocol://$host$baseDir/certificado.php?codigo=$codigo"; + +$qrPath = __DIR__ . "/../temp/qr_$codigo.png"; +Builder::create() + ->writer(new PngWriter()) + ->data($validationUrl) + ->size(150) + ->margin(5) + ->build() + ->saveToFile($qrPath); + +$pdf = new Fpdi(); +$pdf->AddPage(); +$pdf->setSourceFile(__DIR__ . '/../fpdf/formatodiploma.pdf'); +$template = $pdf->importPage(1); +$pdf->useTemplate($template); +$pdf->SetTextColor(33, 37, 41); + +$pdf->SetFont('Helvetica', 'B', 12); +$pdf->SetXY(0, 245); +$pdf->Cell(210, 10, t("Dr. Juan Manuel Gutiérrez Méndez"), 0, 1, 'C'); +$pdf->SetFont('Helvetica', '', 11); +$pdf->Cell(210, 6, t("Director de Proyectos"), 0, 1, 'C'); + +$pdf->SetFont('Helvetica', 'I', 10); +$pdf->SetXY(0, 268); +$pdf->Cell(210, 6, t("Se expide en la ciudad de Xalapa, Ver., a los {$dia} días de {$mes} de {$anio}"), 0, 1, 'C'); + +$pdf->Image($qrPath, 10, 240, 25, 25); +unlink($qrPath); + +$tempPath = "/tmp/diploma_$codigo.pdf"; +$pdf->Output($tempPath, 'F'); + +$mail = getMailer(); +$mail->addAddress($email, $alumno); +$mail->Subject = "Tu diploma del curso {$curso}"; +$mail->Body = "Hola {$alumno},\n\nAdjunto encontrarás tu diploma por haber concluido satisfactoriamente el curso tipo " . ucfirst($tipo) . ": {$curso}.\n\nGracias por tu participación.\n\nPuedes validar el diploma escaneando el código QR o visitando:\n$validationUrl"; +$mail->addAttachment($tempPath, "Diploma-$codigo.pdf"); + +try { + $mail->send(); + unlink($tempPath); + $instanceId = 'instance126094'; + $token = 'd7n8da6p4iixalci'; + $mensajeWA = "Hola {$alumno}, hemos enviado el diploma de LANIA a tu correo personal. ¡Felicidades por concluir el curso!"; + + $waUrl = "https://api.ultramsg.com/$instanceId/messages/chat"; + $waData = [ + 'token' => $token, + 'to' => $telefonoFormateado, + 'body' => $mensajeWA + ]; + + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $waUrl, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($waData) + ]); + $waResponse = curl_exec($ch); + curl_close($ch); + + echo json_encode(['success' => true, 'message' => 'Diploma enviado por correo y WhatsApp']); + +} catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $mail->ErrorInfo]); +} diff --git a/api/importar_csv.php b/api/importar_csv.php new file mode 100644 index 0000000..e8f8999 --- /dev/null +++ b/api/importar_csv.php @@ -0,0 +1,109 @@ + false, 'error' => 'No autenticado']); + exit; +} + +$input = json_decode(file_get_contents('php://input'), true); +$alumnos = $input['alumnos'] ?? []; + +if (empty($alumnos)) { + echo json_encode(['success' => false, 'error' => 'No se recibieron alumnos']); + exit; +} + +function limpiar($s) { + return trim($s, "\"' "); +} + +$errores = []; +$importados = 0; + +foreach ($alumnos as $index => $alumno) { + $fila = $index + 2; + + $nombre = limpiar($alumno['nombre'] ?? ''); + $email = limpiar($alumno['email'] ?? ''); + $telefono = limpiar($alumno['telefono'] ?? ''); + $tipoCurso = strtolower(limpiar($alumno['tipoCurso'] ?? '')); + $nombreCurso = limpiar($alumno['nombreCurso'] ?? ''); + + if (!$nombre || !$email || !$telefono || !$tipoCurso || !$nombreCurso) { + $errores[] = "Fila $fila: faltan datos obligatorios."; + continue; + } + + // Verificar curso+tipo + $stmtCurso = $pdo->prepare(" + SELECT id + FROM cursos + WHERE nombre = ? AND tipo = ? + "); + $stmtCurso->execute([$nombreCurso, $tipoCurso]); + $curso = $stmtCurso->fetch(PDO::FETCH_ASSOC); + + if (!$curso) { + $errores[] = "Fila $fila: el curso '$nombreCurso' con tipo '$tipoCurso' no existe."; + continue; + } + + // Verificar duplicados por separado + $emailDuplicado = false; + $telefonoDuplicado = false; + + $stmtEmail = $pdo->prepare("SELECT id FROM alumnos WHERE email = ?"); + $stmtEmail->execute([$email]); + if ($stmtEmail->fetch()) { + $emailDuplicado = true; + } + + $stmtTel = $pdo->prepare("SELECT id FROM alumnos WHERE telefono = ?"); + $stmtTel->execute([$telefono]); + if ($stmtTel->fetch()) { + $telefonoDuplicado = true; + } + + if ($emailDuplicado || $telefonoDuplicado) { + if ($emailDuplicado && $telefonoDuplicado) { + $errores[] = "Fila $fila: el email '$email' y el teléfono '$telefono' ya están registrados."; + } elseif ($emailDuplicado) { + $errores[] = "Fila $fila: el email '$email' ya está registrado."; + } elseif ($telefonoDuplicado) { + $errores[] = "Fila $fila: el teléfono '$telefono' ya está registrado."; + } + continue; + } + + try { + $pdo->beginTransaction(); + + $stmtAlumno = $pdo->prepare("INSERT INTO alumnos (nombre, email, telefono) VALUES (?, ?, ?)"); + $stmtAlumno->execute([$nombre, $email, $telefono]); + $alumnoId = $pdo->lastInsertId(); + + $stmtAsignar = $pdo->prepare("INSERT INTO alumnos_cursos (alumno_id, curso_id, estado) VALUES (?, ?, 'cursando')"); + $stmtAsignar->execute([$alumnoId, $curso['id']]); + + $pdo->commit(); + $importados++; + } catch (PDOException $e) { + $pdo->rollBack(); + $errores[] = "Fila $fila: error al guardar en base de datos."; + } +} + +if (!empty($errores)) { + echo json_encode([ + 'success' => false, + 'conflicts' => $errores + ]); +} else { + echo json_encode([ + 'success' => true, + 'message' => "$importados alumnos importados exitosamente." + ]); +} diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 0000000..d2e4fb7 Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/css/styles.css b/assets/css/styles.css index e9e13db..f107bfa 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -7,11 +7,10 @@ body { font-family: "Inter", sans-serif; - height: 100vh; - overflow: hidden; + min-height: 100vh; + overflow-y: auto; } -/* Estilos para el contenedor del login */ .login-container { display: flex; justify-content: center; @@ -327,7 +326,6 @@ select:focus { display: none; } -/* Estilos para el contenido específico */ .course-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); @@ -362,7 +360,6 @@ select:focus { margin: 1rem 0; } -/* Estilos para el buscador y tabla */ .search-container { display: flex; gap: 10px; @@ -421,9 +418,7 @@ select:focus { .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"], @@ -441,7 +436,6 @@ select:focus { display: flex; } -/* Mejoras de responsive */ @media (max-width: 768px) { .main-container { flex-direction: column; @@ -457,7 +451,7 @@ select:focus { padding: 1.5rem; } } -/* Estilos adicionales */ + .loader { text-align: center; padding: 2rem; @@ -509,9 +503,6 @@ select:focus { border-radius: 6px; } -/* Añadir al final del archivo */ - -/* Estilos para el header */ header { display: flex; justify-content: space-between; @@ -532,7 +523,6 @@ header h1 { gap: 1rem; } -/* Estilos para el enlace de cerrar sesión */ .logout-link { color: #333; text-decoration: none; @@ -546,7 +536,6 @@ header h1 { background-color: #fee2e2; } -/* Estilos para la columna de acciones */ .courses-table td:last-child { text-align: center; } @@ -565,7 +554,7 @@ header h1 { background-color: #a0aec0; cursor: not-allowed; } -/* Badges para tipos de curso */ + .badge { padding: 4px 8px; border-radius: 12px; @@ -603,7 +592,7 @@ header h1 { background-color: #9c27b0; color: white; } -/* Estilos para celdas de descripción */ + .description-cell { max-width: 300px; white-space: nowrap; @@ -612,15 +601,11 @@ header h1 { } .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); + background: inherit; + box-shadow: none; + cursor: default; } -/* Estilos para mensaje de no datos */ .no-data { text-align: center; padding: 20px; @@ -628,14 +613,12 @@ header h1 { 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; } @@ -643,9 +626,6 @@ textarea { .error-card h2 { color: #e53e3e; } -/* ------------------------- */ -/* ESTILOS GESTIÓN ALUMNOS */ -/* ------------------------- */ .students-management { display: flex; @@ -653,7 +633,6 @@ textarea { gap: 1.5rem; } -/* Formulario */ #studentForm { margin-top: 1rem; } @@ -759,7 +738,6 @@ textarea { background-color: #f8fafc; } -/* Botones de acción */ .action-buttons { display: flex; gap: 0.5rem; @@ -779,7 +757,6 @@ textarea { background-color: #d97706; } -/* Mensaje sin datos */ .no-data { text-align: center; padding: 2rem; @@ -800,7 +777,6 @@ textarea { margin-bottom: 1.5rem; } -/* Responsive */ @media (max-width: 768px) { .form-grid { grid-template-columns: 1fr; @@ -824,7 +800,7 @@ textarea { width: 100%; } } -/* Modal styles */ + .modal-overlay { position: fixed; top: 0; @@ -874,7 +850,6 @@ textarea { background-color: #f9fafb; } -/* Toast styles */ .toast { position: fixed; bottom: 20px; @@ -965,3 +940,97 @@ textarea { font-size: 0.85rem; font-weight: 500; } + +.description-cell { + max-width: 250px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.truncated-text { + display: inline-block; + max-width: 200px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + vertical-align: middle; +} + +.modal-description { + max-height: 300px; + overflow-y: auto; + white-space: pre-wrap; + word-wrap: break-word; + line-height: 1.4; +} + +.read-more { + color: #2563eb; + font-size: 0.85rem; + text-decoration: underline; + margin-left: 6px; + cursor: pointer; +} + +#courses-content h2 + form, +#courses-content h2 + #courseForm { + margin-top: 1.5rem; +} + +#courses-content textarea[name="descripcion"] { + font-size: 0.95rem; +} + +textarea[name="descripcion"] { + resize: none; + overflow-y: hidden; +} + +.tooltip-csv { + visibility: hidden; + opacity: 0; + position: absolute; + top: 140%; + left: 50%; + transform: translateX(-50%); + background-color: #2563eb; + color: #fff; + padding: 6px 10px; + border-radius: 4px; + white-space: nowrap; + font-size: 0.75rem; + transition: opacity 0.2s ease; + pointer-events: none; + z-index: 10; +} + +.tooltip-csv::after { + content: ""; + position: absolute; + bottom: 100%; + left: 50%; + margin-left: -6px; + border-width: 6px; + border-style: solid; + border-color: transparent transparent #2563eb transparent; +} + +svg:hover + .tooltip-csv, +.tooltip-csv:hover { + visibility: visible; + opacity: 1; +} + +.table-container td, +.table-container th { + vertical-align: middle; +} + +.table-container td:nth-child(1), +.table-container td:nth-child(2) { + max-width: 280px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/assets/formato_alumnos.csv b/assets/formato_alumnos.csv new file mode 100644 index 0000000..996c4ce --- /dev/null +++ b/assets/formato_alumnos.csv @@ -0,0 +1,2 @@ +"nombre,email,telefono,tipoCurso,nombreCurso" +"Rafael,rafael1@mail.com,10987654321,pildora,Curso Avanzado" \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index e1fd946..657f468 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,17 +1,14 @@ document.addEventListener("DOMContentLoaded", function () { - // Manejo del formulario de login const loginForm = document.getElementById("loginForm"); if (loginForm) { loginForm.addEventListener("submit", handleLogin); } - // Configuración inicial del dashboard if (document.body.classList.contains("admin")) { initializeDashboard(); } }); -// Función para manejar el login function handleLogin(e) { e.preventDefault(); const formData = new FormData(this); @@ -31,19 +28,11 @@ function handleLogin(e) { .catch((error) => console.error("Error:", error)); } -// Inicialización del dashboard function initializeDashboard() { setupSidebarNavigation(); - loadInitialData(); - - const activeSection = document.querySelector(".sidebar-menu li.active"); - if (activeSection) { - const sectionId = activeSection.getAttribute("data-section"); - showSection(sectionId, true); - } + showSection("dashboard", true); } -// Configuración del menú lateral function setupSidebarNavigation() { document.querySelectorAll(".sidebar-menu li").forEach((item) => { if (item.getAttribute("data-section")) { @@ -55,7 +44,6 @@ function setupSidebarNavigation() { }); } -// Carga de datos iniciales function loadInitialData() { const profesorId = getProfesorId(); @@ -67,7 +55,6 @@ function loadInitialData() { 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() @@ -82,23 +69,19 @@ function loadInitialData() { .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"); @@ -119,48 +102,187 @@ function getProfesorId() { } } -function showModal(title, content, buttons = []) { - const modalHtml = ` -
- `; +async function renderCursosGenerablesPorTipo(tipo) { + const container = document.getElementById("cursos-generar-diplomas"); + container.innerHTML = `Alumno | +Acciones | +
---|
${err.message}
Resumen:
-• ${ - activeCourses.length - } cursos activos
-• ${totalStudents} alumnos registrados
-• ${totalDiplomas} diplomas emitidos
-• Total de cursos: ${cursos.length}
+• Cursos activos: ${activos.length}
+• Tipo Inyección: ${inyeccion.length}
+• Tipo Píldora: ${pildora.length}
+• Tipo Tratamiento: ${tratamiento.length}
+• Alumnos registrados: ${data.alumnos.length}
+• Diplomas emitidos: ${data.diplomas.length}
+