Resumen del panel corregido

This commit is contained in:
Christopher Alessandro Rodriguez Salazar 2025-06-19 17:48:40 -06:00
parent 5e206dc1ef
commit 2274f5cf95
12 changed files with 1129 additions and 30 deletions

144
api/alumnos-cursos.php Normal file
View File

@ -0,0 +1,144 @@
<?php
header('Content-Type: application/json');
require '../includes/config.php';
if (!is_logged_in()) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => 'No autenticado']);
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
$profesorId = $_SESSION['profesor']['id'] ?? null;
try {
switch ($method) {
case 'GET':
$alumnoId = $_GET['alumno_id'] ?? null;
$tipoCurso = $_GET['tipo'] ?? null;
$estadoCurso = $_GET['estado'] ?? null;
if ($alumnoId) {
// 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 vínculos con posibles filtros
$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'] ?? [];
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()]);
}

211
api/alumnos.php Normal file
View File

@ -0,0 +1,211 @@
<?php
header('Content-Type: application/json');
require '../includes/config.php';
if (!is_logged_in()) {
http_response_code(401);
echo json_encode(['success' => 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;
}
// 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;
}
// 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;
}
}
$pdo->beginTransaction();
try {
// Insertar alumno
$stmt = $pdo->prepare("
INSERT INTO alumnos (nombre, email, telefono)
VALUES (?, ?, ?)
");
$stmt->execute([
$data['nombre'],
$data['email'],
$data['telefono'] ?? null
]);
$alumnoId = $pdo->lastInsertId();
// 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']]);
}
$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 '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;
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);
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()]);
}
?>

171
api/cursos.php Normal file
View File

@ -0,0 +1,171 @@
<?php
header('Content-Type: application/json');
require '../includes/config.php';
if (!is_logged_in()) {
http_response_code(401);
echo json_encode(['error' => 'No autenticado']);
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
$profesorId = $_SESSION['profesor']['id'];
switch ($method) {
case 'GET':
try {
$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, 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' => $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;
}
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 = ?,
docente = ?,
horas_trabajadas = ?,
estado = ?
WHERE id = ?
");
$stmt->execute([
$data['nombre'],
$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) {
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']);
}
?>

40
api/dashboard_data.php Normal file
View File

@ -0,0 +1,40 @@
<?php
session_start();
require '../conexion.php';
$profesor_id = $_SESSION['profesor']['id'] ?? null;
if (!$profesor_id) {
http_response_code(401);
echo json_encode(["error" => "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")
]);

91
api/diploma_old.php Normal file
View File

@ -0,0 +1,91 @@
<?php
header('Content-Type: application/json');
require '../includes/config.php';
if (!is_logged_in()) {
http_response_code(401);
echo json_encode(['error' => 'No autenticado']);
exit;
}
try {
$profesorId = $_GET['profesor_id'] ?? null;
$query = "
SELECT
d.codigo_unico,
d.fecha_emision,
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
";
$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 = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$row['fecha_formateada'] = date("d/m/Y", strtotime($row['fecha_emision']));
$diplomas[] = $row;
}
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()]);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['alumno_curso_id']) || empty($input['alumno_curso_id'])) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'alumno_curso_id requerido']);
exit;
}
// Generar código único
function generarCodigoUnico($length = 10) {
return substr(str_shuffle("ABCDEFGHJKLMNPQRSTUVWXYZ23456789"), 0, $length);
}
$alumnoCursoId = $input['alumno_curso_id'];
$codigoUnico = generarCodigoUnico();
// Verificar que no exista diploma duplicado para el mismo alumno_curso_id
$stmt = $pdo->prepare("SELECT id FROM diplomas WHERE alumno_curso_id = ?");
$stmt->execute([$alumnoCursoId]);
if ($stmt->fetch()) {
http_response_code(409);
echo json_encode(['success' => false, 'error' => 'Este alumno ya tiene un diploma registrado']);
exit;
}
// Insertar diploma
$stmt = $pdo->prepare("INSERT INTO diplomas (alumno_curso_id, codigo_unico) VALUES (?, ?)");
if ($stmt->execute([$alumnoCursoId, $codigoUnico])) {
echo json_encode(['success' => true, 'codigo_unico' => $codigoUnico]);
} else {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'No se pudo guardar el diploma']);
}
exit;
}

99
api/diplomas.php Normal file
View File

@ -0,0 +1,99 @@
<?php
header('Content-Type: application/json');
require '../includes/config.php';
if (!is_logged_in()) {
http_response_code(401);
echo json_encode(['success' => 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;
}
// Validar que el alumno_curso pertenece al profesor
$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;
}
// Verificar si ya existe un diploma para este alumno_curso
$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;
}
// Generar código único y fecha
$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']);
}

135
api/enviar_diploma.php Normal file
View File

@ -0,0 +1,135 @@
<?php
require '../includes/config.php';
require '../fpdf/fpdf.php';
require '../fpdf/src/autoload.php';
require '../includes/mailer_config.php';
require '../vendor/autoload.php';
use setasign\Fpdi\Fpdi;
use PHPMailer\PHPMailer\Exception;
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Writer\PngWriter;
header('Content-Type: application/json');
$codigo = $_GET['codigo'] ?? null;
if (!$codigo) {
http_response_code(400);
echo json_encode(['success' => 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]);
}

109
api/importar_csv.php Normal file
View File

@ -0,0 +1,109 @@
<?php
require '../includes/config.php';
header('Content-Type: application/json');
if (!is_logged_in()) {
http_response_code(401);
echo json_encode(['success' => 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."
]);
}

59
api/login.php Normal file
View File

@ -0,0 +1,59 @@
<?php
header('Content-Type: application/json');
require '../includes/config.php';
error_reporting(E_ALL);
ini_set('display_errors', 1);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
exit;
}
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
if (empty($email) || empty($password)) {
echo json_encode(['success' => false, 'message' => 'Email y contraseña son requeridos']);
exit;
}
try {
// 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 (!$profesor) {
echo json_encode(['success' => false, 'message' => 'Profesor no encontrado o no aprobado']);
exit;
}
if (!password_verify($password, $profesor['password'])) {
echo json_encode(['success' => false, 'message' => 'Contraseña incorrecta']);
exit;
}
// Configurar sesión de profesor
$_SESSION['profesor'] = [
'id' => $profesor['id'],
'nombre' => $profesor['nombre'],
'email' => $profesor['email']
];
echo json_encode([
'success' => true,
'redirect' => 'dashboard.php',
'profesor_id' => $profesor['id']
]);
} catch (PDOException $e) {
error_log('Error en login.php: ' . $e->getMessage());
echo json_encode([
'success' => false,
'message' => 'Error en el servidor. Por favor, intente más tarde.'
]);
}
?>

6
api/logout.php Normal file
View File

@ -0,0 +1,6 @@
<?php
session_start();
session_destroy();
header('Location: ../index.php');
exit;
?>

42
api/usuarios.php Normal file
View File

@ -0,0 +1,42 @@
<?php
header('Content-Type: application/json');
require '../includes/config.php';
if (!is_logged_in() || $_SESSION['user']['rol'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Acceso no autorizado']);
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
$stmt = $pdo->query("SELECT id, username, nombre, email, rol FROM usuarios");
echo json_encode($stmt->fetchAll());
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
$hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT);
$stmt = $pdo->prepare("
INSERT INTO usuarios (username, password, nombre, email, rol)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([
$data['username'],
$hashedPassword,
$data['nombre'],
$data['email'],
$data['rol'] ?? 'user'
]);
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Método no permitido']);
}
?>

View File

@ -34,15 +34,11 @@ function handleLogin(e) {
// 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);
}
// loadInitialData();
showSection("dashboard", true);
}
// Configuración del menú lateral
function setupSidebarNavigation() {
document.querySelectorAll(".sidebar-menu li").forEach((item) => {
@ -331,7 +327,7 @@ function showSection(sectionId, isInitialLoad = false) {
// Cargar siempre los datos del dashboard al acceder
if (sectionId === "dashboard") {
loadInitialData();
// loadInitialData();
} else {
loadDynamicContent(sectionId, sectionElement);
}
@ -1096,7 +1092,7 @@ function setupStudentForm() {
// Guardar alumno
form.addEventListener("submit", function (e) {
e.preventDefault();
submitStudentForm(); // ✅ Usa función unificada que detecta si es edición o nuevo
submitStudentForm();
});
const searchInput = document.getElementById("studentSearch");
@ -1113,7 +1109,6 @@ function submitStudentForm() {
const submitText = document.getElementById("submitText");
const spinner = document.getElementById("submitSpinner");
// Mostrar spinner
submitText.textContent = "Procesando...";
spinner.style.display = "inline-block";
submitBtn.disabled = true;
@ -1123,7 +1118,7 @@ function submitStudentForm() {
nombre: formData.get("nombre"),
email: formData.get("email"),
telefono: formData.get("telefono"),
curso_id: document.getElementById("curso").value, // ✅ Cambio aquí
curso_id: document.getElementById("curso").value,
};
const isEdit = form.dataset.editing === "true";
@ -1332,13 +1327,13 @@ function filterStudents(searchTerm) {
const nombre = row.cells[0].textContent.toLowerCase();
const email = row.cells[1].textContent.toLowerCase();
const telefono = row.cells[2].textContent.toLowerCase();
const cursos = row.cells[3].textContent.toLowerCase(); // ✅ aquí
const cursos = row.cells[3].textContent.toLowerCase();
if (
nombre.includes(searchTerm) ||
email.includes(searchTerm) ||
telefono.includes(searchTerm) ||
cursos.includes(searchTerm) // ✅ permite buscar tipo o nombre de curso
cursos.includes(searchTerm)
) {
row.style.display = "";
} else {
@ -1416,7 +1411,7 @@ function renderStudentsTable(alumnos) {
}
function renderCourseBadges(alumnoId) {
return '<div class="loader-sm"></div>'; // Se cargará dinámicamente
return '<div class="loader-sm"></div>';
}
async function loadStudentCourses(alumnoId) {
@ -1443,7 +1438,6 @@ async function loadStudentCourses(alumnoId) {
: '<span class="text-muted">Sin cursos</span>';
}
// ✅ Aplicar filtro visual si está activo
const filtro = document.getElementById("studentCourseFilter");
if (filtro) {
const estadoSeleccionado = filtro.value;
@ -1474,11 +1468,9 @@ window.showAssignStudentModal = async function () {
return;
}
// Mostrar loader mientras se cargan los datos
const tempModalContent = `<div class="loader">Cargando datos...</div>`;
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"),
@ -1494,7 +1486,6 @@ window.showAssignStudentModal = async function () {
throw new Error("Error al cargar datos necesarios");
}
// Crear contenido del modal
const modalHtml = `
<div class="form-group">
<label>Seleccionar Curso</label>
@ -1528,13 +1519,11 @@ window.showAssignStudentModal = async function () {
</div>
`;
// 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");
@ -1542,13 +1531,11 @@ window.showAssignStudentModal = async function () {
document.querySelector(".modal").appendChild(modalFooter);
}
// Actualizar el contenido del footer
modalFooter.innerHTML = `
<button class="btn btn-secondary" onclick="closeModal()">Cancelar</button>
<button class="btn btn-primary" onclick="assignStudentsToCourse()">Confirmar Vinculación</button>
`;
// Función para vincular alumnos seleccionados
window.assignStudentsToCourse = async function () {
const cursoId = document.getElementById("selectCurso").value;
const selectedAlumnos = Array.from(
@ -1614,7 +1601,6 @@ window.unassignStudent = async function (alumnoId, cursoId) {
}
};
// Gestión de diplomas
function loadDiplomasSection(container) {
container.innerHTML = `
<div class="card">
@ -1684,7 +1670,6 @@ function loadDiplomasSection(container) {
}
});
// Cargar diplomas emitidos
fetch(`api/diplomas.php?profesor_id=${getProfesorId()}`)
.then((response) => response.json())
.then((data) => {
@ -1760,7 +1745,6 @@ function switchDiplomaTab(tabName) {
if (activeTab) activeTab.style.display = "block";
}
// Funciones auxiliares
function generateCoursesPreview(courses) {
if (!courses.length) return "<p>No tienes cursos activos</p>";
@ -1804,7 +1788,6 @@ function getCourseTypeClass(type) {
return types[type] || "";
}
// Funciones globales para diplomas
window.downloadDiploma = function (codigo) {
window.open(`certificado.php?codigo=${codigo}`, "_blank");
};
@ -1914,7 +1897,7 @@ function downloadCSVTemplate() {
const link = document.createElement("a");
link.href = "assets/formato_alumnos.csv";
link.download = "formato_alumnos.csv";
document.body.appendChild(link); // requerido en algunos navegadores
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
@ -1936,10 +1919,9 @@ window.generateDiploma = function (alumnoCursoId) {
showToast("success", "Diploma generado correctamente");
window.open(`certificado.php?codigo=${data.codigo_unico}`, "_blank");
// 🔧 Buscar el botón "Enviar" de forma más confiable
const sendButtons = document.querySelectorAll(".btn-send-diploma");
sendButtons.forEach((btn) => {
// Buscar el que esté deshabilitado y no tenga código aún
if (btn.disabled && !btn.dataset.codigo) {
btn.disabled = false;
btn.title = "Enviar diploma por correo";
@ -1948,7 +1930,6 @@ window.generateDiploma = function (alumnoCursoId) {
}
});
// Opcional: recargar diplomas emitidos
loadDiplomasSection(document.querySelector("#diplomas-content"));
} else {
showToast("error", data.error || "Error al generar diploma");
@ -1994,3 +1975,14 @@ document.addEventListener('click', function (e) {
});
}
});
function loadDashboardContent(container, force = false) {
fetch('api/dashboard_data.php')
.then(res => res.json())
.then(data => renderDashboard(container, data))
.catch(err => {
container.innerHTML = `<p>Error al cargar estadísticas.</p>`;
console.error(err);
});
}