gestion alumnos actualizado

This commit is contained in:
alexis.palestina 2025-06-02 23:30:18 -06:00
parent 45368a331f
commit 5cda47e50b
7 changed files with 1690 additions and 108 deletions

View File

@ -14,27 +14,27 @@ $profesorId = $_SESSION['profesor']['id'] ?? null;
try {
switch ($method) {
case 'GET':
$alumnoId = $_GET['alumno_id'] ?? null;
$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 = ?";
// Obtener cursos de un alumno específico (con tipo)
$query = "SELECT c.id, c.nombre, c.tipo 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;
// 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);

View File

@ -27,31 +27,51 @@ 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
]);
$pdo->beginTransaction();
try {
// Insertar alumno
$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;
$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);
@ -75,6 +95,30 @@ try {
$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,

View File

@ -14,15 +14,22 @@ $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;
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
@ -50,8 +57,9 @@ switch ($method) {
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':

67
api/importar_csv.php Normal file
View File

@ -0,0 +1,67 @@
<?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;
}
$data = json_decode(file_get_contents('php://input'), true);
$alumnos = $data['alumnos'] ?? [];
if (empty($alumnos)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'No se enviaron alumnos']);
exit;
}
// Paso 1: Validar todos los cursos
$errores = [];
$profesorId = $_SESSION['profesor']['id'] ?? null;
foreach ($alumnos as $index => $alumno) {
$nombreCurso = trim($alumno['nombreCurso']);
$tipoCurso = trim($alumno['tipoCurso']);
$stmt = $pdo->prepare("SELECT id FROM cursos WHERE nombre = ? AND tipo = ? AND profesor_id = ?");
$stmt->execute([$nombreCurso, $tipoCurso, $profesorId]);
$curso = $stmt->fetch();
if (!$curso) {
$errores[] = "El curso \"{$nombreCurso}\" de tipo \"{$tipoCurso}\" en {$alumno['nombre']} ({$alumno['email']}) es inválido.";
} else {
$alumnos[$index]['curso_id'] = $curso['id']; // Guardamos para usar después
}
}
if (!empty($errores)) {
echo json_encode([
'success' => false,
'error' => "Error en cursos: " . count($errores) . " conflictos encontrados",
'conflicts' => $errores
]);
exit;
}
// Paso 2: Insertar alumnos
try {
$pdo->beginTransaction();
foreach ($alumnos as $a) {
$stmt = $pdo->prepare("INSERT INTO alumnos (nombre, email, telefono) VALUES (?, ?, ?)");
$stmt->execute([$a['nombre'], $a['email'], $a['telefono'] ?? null]);
$alumnoId = $pdo->lastInsertId();
$stmt = $pdo->prepare("INSERT INTO alumnos_cursos (alumno_id, curso_id, estado) VALUES (?, ?, 'cursando')");
$stmt->execute([$alumnoId, $a['curso_id']]);
}
$pdo->commit();
echo json_encode(['success' => true, 'message' => 'Todos los alumnos fueron importados correctamente.']);
} catch (PDOException $e) {
$pdo->rollBack();
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error al guardar en la base de datos: ' . $e->getMessage()]);
}

1333
assets/js/main copy.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -331,7 +331,10 @@ function renderDashboard(container, data) {
function loadProfessorCourses(container) {
fetch(`api/cursos.php?profesor_id=${getProfesorId()}`)
.then((response) => response.json())
.then((courses) => {
.then((res) => {
if (!res.success) throw new Error("No se pudieron cargar los cursos");
const courses = res.data;
container.innerHTML = `
<div class="card">
<h2>Mis Cursos</h2>
@ -388,12 +391,8 @@ function loadProfessorCourses(container) {
(course) => `
<tr>
<td>${course.nombre}</td>
<td class="description-cell">${
course.descripcion || "-"
}</td>
<td><span class="badge ${getCourseTypeClass(
course.tipo
)}">${course.tipo}</span></td>
<td class="description-cell">${course.descripcion || "-"}</td>
<td><span class="badge ${getCourseTypeClass(course.tipo)}">${formatCourseType(course.tipo)}</span></td>
<td>
<span class="badge ${
course.estado === "activo"
@ -407,12 +406,8 @@ function loadProfessorCourses(container) {
</td>
<td>
<div class="action-buttons">
<button class="btn btn-sm" onclick="editCourse(${
course.id
})">Editar</button>
<button class="btn btn-sm btn-danger" onclick="deleteCourse(${
course.id
})">Eliminar</button>
<button class="btn btn-sm" onclick="editCourse(${course.id})">Editar</button>
<button class="btn btn-sm btn-danger" onclick="deleteCourse(${course.id})">Eliminar</button>
</div>
</td>
</tr>
@ -425,14 +420,18 @@ function loadProfessorCourses(container) {
</div>`;
setupCourseForm();
})
.catch((err) => {
console.error("Error al cargar cursos:", err);
container.innerHTML = `<div class="card error-card">No se pudieron cargar los cursos</div>`;
});
}
function formatCourseType(type) {
const types = {
'pildora': 'Píldora',
'inyeccion': 'Inyección',
'tratamiento': 'Tratamiento'
pildora: "Píldora",
inyeccion: "Inyección",
tratamiento: "Tratamiento"
};
return types[type] || type;
}
@ -445,7 +444,7 @@ function setupCourseForm() {
: null;
if (courseTypeSelect && competenciasInput) {
courseTypeSelect.addEventListener('change', function() {
courseTypeSelect.addEventListener('change', function () {
const isTratamiento = this.value === 'tratamiento';
competencesField.classList.toggle('oculto', !isTratamiento);
competenciasInput.required = isTratamiento;
@ -458,7 +457,7 @@ function setupCourseForm() {
competencesField.classList.toggle('oculto', !isTratamiento);
competenciasInput.required = isTratamiento;
}
const form = document.getElementById("courseForm");
if (!form) return;
@ -482,7 +481,7 @@ function setupCourseForm() {
descripcion: formData.get("descripcion"),
competencias: formData.get("competencias"),
tipo: formData.get("tipo"),
estado: formData.get("estado"),
estado: formData.get("estado"),
profesor_id: getProfesorId(),
};
@ -532,8 +531,8 @@ window.updateCourse = function (id, data) {
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);
.then((res) => {
const course = res.data.find((c) => c.id == id);
if (!course) return;
const form = document.getElementById("courseForm");
@ -562,6 +561,7 @@ window.editCourse = function (id) {
});
};
window.deleteCourse = function (id) {
if (!confirm("¿Estás seguro de eliminar este curso?")) return;
@ -613,48 +613,104 @@ function loadStudentsManagement(container) {
});
}
function renderStudentForm() {
return `
<div class="card">
<h2>Gestión de Alumnos</h2>
<form id="studentForm">
<div class="form-grid">
<div class="form-group">
<label for="nombre">Nombre*</label>
<input type="text" id="nombre" name="nombre" required>
</div>
<div class="form-group">
<label for="email">Email*</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="telefono">Teléfono</label>
<input type="tel" id="telefono" name="telefono">
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-outline" onclick="resetStudentForm()" id="cancelBtn" style="display:none;">
Cancelar
</button>
<button type="submit" class="btn">
<span id="submitText">Guardar</span>
<span class="spinner-border spinner-border-sm" id="submitSpinner" style="display:none;"></span>
</button>
</div>
</form>
</div>`;
<div class="card">
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
<h2>Gestión de Alumnos</h2>
<label class="btn btn-primary" style="margin: 0;">
Importar CSV
<input type="file" accept=".csv" onchange="handleCSVUpload(event)" style="display: none;">
</label>
</div>
<form id="studentForm">
<div class="form-grid">
<div class="form-group">
<label for="nombre">Nombre*</label>
<input type="text" id="nombre" name="nombre" required>
</div>
<div class="form-group">
<label for="email">Email*</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="telefono">Teléfono</label>
<input type="tel" id="telefono" name="telefono">
</div>
</div>
<div class="form-grid">
<div class="form-group">
<label for="tipoCurso">Tipo de Curso*</label>
<select id="tipoCurso" required>
<option value="">Seleccionar tipo</option>
<option value="inyeccion">Inyección</option>
<option value="pildora">Píldora</option>
<option value="tratamiento">Tratamiento</option>
</select>
</div>
<div class="form-group">
<label for="curso">Curso*</label>
<select id="curso" required>
<option value="">Selecciona primero un tipo</option>
</select>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-outline" onclick="resetStudentForm()" id="cancelBtn" style="display:none;">
Cancelar
</button>
<button type="submit" class="btn">
<span id="submitText">Guardar</span>
<span class="spinner-border spinner-border-sm" id="submitSpinner" style="display:none;"></span>
</button>
</div>
</form>
</div>`;
}
function setupStudentForm() {
const form = document.getElementById("studentForm");
if (!form) return;
form.addEventListener("submit", function (e) {
e.preventDefault();
submitStudentForm();
// Referencias a selects
const tipoCurso = document.getElementById("tipoCurso");
const curso = document.getElementById("curso");
// Cargar cursos dinámicamente
tipoCurso.addEventListener("change", () => {
const tipo = tipoCurso.value;
curso.innerHTML = '<option value="">Cargando...</option>';
fetch(`api/cursos.php?tipo=${tipo}`)
.then((res) => res.json())
.then((data) => {
if (data.success) {
curso.innerHTML = '<option value="">Seleccionar curso</option>';
data.data.forEach((c) => {
const opt = document.createElement("option");
opt.value = c.id;
opt.textContent = c.nombre;
curso.appendChild(opt);
});
} else {
curso.innerHTML = '<option value="">Sin resultados</option>';
}
})
.catch(() => {
curso.innerHTML = '<option value="">Error al cargar</option>';
});
});
// Configurar búsqueda
// Guardar alumno
form.addEventListener("submit", function (e) {
e.preventDefault();
submitStudentForm(); // ✅ Usa función unificada que detecta si es edición o nuevo
});
const searchInput = document.getElementById("studentSearch");
if (searchInput) {
searchInput.addEventListener("input", function () {
@ -662,6 +718,7 @@ function setupStudentForm() {
});
}
}
function submitStudentForm() {
const form = document.getElementById("studentForm");
const submitBtn = form.querySelector('button[type="submit"]');
@ -678,12 +735,12 @@ function submitStudentForm() {
nombre: formData.get("nombre"),
email: formData.get("email"),
telefono: formData.get("telefono"),
curso_id: document.getElementById("curso").value, // ✅ Cambio aquí
};
const isEdit = form.dataset.editing === "true";
const studentId = form.dataset.studentId;
const url = "api/alumnos.php" + (isEdit ? "" : "");
const url = "api/alumnos.php";
const method = isEdit ? "PUT" : "POST";
if (isEdit) {
@ -772,6 +829,24 @@ window.editStudent = function (id) {
document.getElementById("submitText").textContent = "Actualizar";
document.getElementById("cancelBtn").style.display = "inline-block";
// 👇 Precargar tipo y curso si el alumno tiene asignación
fetch(`api/alumnos-cursos.php?alumno_id=${id}`)
.then((res) => res.json())
.then((asignaciones) => {
if (asignaciones.success && asignaciones.data.length > 0) {
const curso = asignaciones.data[0];
const tipoCursoSelect = document.getElementById("tipoCurso");
const cursoSelect = document.getElementById("curso");
tipoCursoSelect.value = curso.tipo;
tipoCursoSelect.dispatchEvent(new Event("change"));
setTimeout(() => {
cursoSelect.value = curso.id;
}, 300);
}
});
form.scrollIntoView({ behavior: "smooth" });
})
.catch((error) => {
@ -780,6 +855,11 @@ window.editStudent = function (id) {
};
window.deleteStudent = function (id) {
if (!id) {
showToast("error", "ID de alumno no proporcionado");
return;
}
fetch(`api/alumnos.php?id=${id}`, {
method: "DELETE",
})
@ -796,6 +876,8 @@ window.deleteStudent = function (id) {
showToast("error", error.message || "Error al eliminar alumno");
});
};
window.confirmDeleteStudent = function (id) {
showModal(
"Confirmar eliminación",
@ -809,11 +891,12 @@ window.confirmDeleteStudent = function (id) {
{
text: "Eliminar",
class: "btn-danger",
handler: () => deleteStudent(id),
handler: "deleteStudent(" + id + ")"
},
]
);
};
window.resetStudentForm = function () {
const form = document.getElementById("studentForm");
form.reset();
@ -870,13 +953,13 @@ function renderStudentsTable(alumnos) {
<div class="search-box">
<input type="text" id="studentSearch" placeholder="Buscar alumno...">
</div>
<button class="btn btn-sm btn-primary" onclick="showAssignStudentModal()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M12 5v14"></path>
<path d="M5 12h14"></path>
</svg>
Vincular a Curso
<input type="file" id="csvInput" accept=".csv" style="display:none;" onchange="handleCSVUpload(event)">
<button class="btn btn-sm btn-primary" onclick="document.getElementById('csvInput').click()">
Importar CSV
</button>
</div>
</div>
<div class="table-responsive">
@ -1260,4 +1343,51 @@ window.resendDiploma = function (codigo) {
console.error("Error:", error);
alert("Error al reenviar el diploma");
});
window.handleCSVUpload = function (event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function (e) {
const lines = e.target.result.split("\n").map(line => line.trim()).filter(Boolean);
const alumnos = [];
for (let i = 1; i < lines.length; i++) {
const [nombre, email, telefono, tipoCurso, nombreCurso] = lines[i].split(",").map(s => s.trim());
// Validación básica de columnas mínimas
if (!nombre || !email || !tipoCurso || !nombreCurso) continue;
alumnos.push({ nombre, email, telefono, tipoCurso, nombreCurso });
}
fetch("api/importar_csv.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ alumnos }),
})
.then(res => res.json())
.then(data => {
if (data.success) {
showToast("success", data.message || "Alumnos importados correctamente");
loadStudentsManagement(document.querySelector("#students-content"));
} else {
// Si vienen errores detallados, los mostramos
if (data.conflicts && data.conflicts.length > 0) {
const list = `<ul>${data.conflicts.map(msg => `<li>${msg}</li>`).join("")}</ul>`;
showModal("Errores en Importación CSV", `<p>No se importaron alumnos por los siguientes errores:</p>${list}`);
} else {
showToast("error", data.error || "Error al importar alumnos");
}
}
})
.catch(() => {
showToast("error", "Error al procesar el archivo");
});
};
reader.readAsText(file);
};
};

View File

@ -4,7 +4,7 @@ session_start();
$host = 'localhost';
$db = 'diplomaster';
$user = 'root';
$pass = '';
$pass = 'root';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";