funcionalidad de búsqueda en alumnos completa

This commit is contained in:
alexis.palestina 2025-06-19 15:48:13 -06:00
parent b9fad9acb8
commit 29ed4d7847
6 changed files with 430 additions and 1571 deletions

View File

@ -36,6 +36,26 @@ try {
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
@ -71,60 +91,81 @@ try {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error al registrar: ' . $e->getMessage()]);
}
break;
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']
]);
// 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 = ?
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 = ?
");
$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;
$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;

View File

@ -29,18 +29,29 @@ switch ($method) {
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);
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 (?, ?, ?, ?, ?, ?)
@ -53,42 +64,53 @@ switch ($method) {
$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;
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 = ?,
estado = ?
WHERE id = ?
");
$stmt->execute([
@ -99,14 +121,14 @@ switch ($method) {
$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) {
@ -114,30 +136,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']);
}
?>
?>

View File

@ -1,67 +1,109 @@
<?php
header('Content-Type: application/json');
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;
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'] ?? [];
$input = json_decode(file_get_contents('php://input'), true);
$alumnos = $input['alumnos'] ?? [];
if (empty($alumnos)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'No se enviaron alumnos']);
exit;
echo json_encode(['success' => false, 'error' => 'No se recibieron alumnos']);
exit;
}
function limpiar($s) {
return trim($s, "\"' ");
}
// Paso 1: Validar todos los cursos
$errores = [];
$profesorId = $_SESSION['profesor']['id'] ?? null;
$importados = 0;
foreach ($alumnos as $index => $alumno) {
$nombreCurso = trim($alumno['nombreCurso']);
$tipoCurso = trim($alumno['tipoCurso']);
$fila = $index + 2;
$stmt = $pdo->prepare("SELECT id FROM cursos WHERE nombre = ? AND tipo = ? AND profesor_id = ?");
$stmt->execute([$nombreCurso, $tipoCurso, $profesorId]);
$curso = $stmt->fetch();
$nombre = limpiar($alumno['nombre'] ?? '');
$email = limpiar($alumno['email'] ?? '');
$telefono = limpiar($alumno['telefono'] ?? '');
$tipoCurso = strtolower(limpiar($alumno['tipoCurso'] ?? ''));
$nombreCurso = limpiar($alumno['nombreCurso'] ?? '');
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 (!$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,
'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()]);
echo json_encode([
'success' => false,
'conflicts' => $errores
]);
} else {
echo json_encode([
'success' => true,
'message' => "$importados alumnos importados exitosamente."
]);
}

View File

@ -965,3 +965,36 @@ 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;
}

File diff suppressed because it is too large Load Diff

View File

@ -120,47 +120,68 @@ function getProfesorId() {
}
function showModal(title, content, buttons = []) {
const modalHtml = `
<div class="modal-overlay" id="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>${title}</h3>
<button class="close-btn" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">${content}</div>
<div class="modal-footer">
${buttons
.map(
(btn) => `
<button class="btn ${btn.class}"
onclick="${
typeof btn.handler === "function"
? `(${btn.handler.toString()})()`
: btn.handler
}">
${btn.text}
</button>
`
)
.join("")}
</div>
</div>
// Remover modal existente si hay
const existing = document.getElementById("modal-overlay");
if (existing) existing.remove();
const modalOverlay = document.createElement("div");
modalOverlay.className = "modal-overlay";
modalOverlay.id = "modal-overlay";
const modal = document.createElement("div");
modal.className = "modal";
modal.innerHTML = `
<div class="modal-header">
<h3>${title}</h3>
<button class="close-btn" id="modal-close-btn">&times;</button>
</div>
<div class="modal-body">${content}</div>
<div class="modal-footer" id="modal-footer"></div>
`;
const modalContainer = document.createElement("div");
modalContainer.innerHTML = modalHtml;
document.body.appendChild(modalContainer);
modalOverlay.appendChild(modal);
document.body.appendChild(modalOverlay);
document.body.style.overflow = "hidden";
// Cerrar con el botón de la esquina
document.getElementById("modal-close-btn").addEventListener("click", closeModal);
// Agregar botones y handlers
const footer = document.getElementById("modal-footer");
buttons.forEach((btn) => {
const button = document.createElement("button");
button.className = "btn " + btn.class;
button.innerText = btn.text;
button.addEventListener("click", () => {
if (typeof btn.handler === "function") {
btn.handler();
} else if (typeof btn.handler === "string") {
if (btn.handler.endsWith(")")) {
eval(btn.handler); // deleteStudent(5)
} else if (typeof window[btn.handler] === "function") {
window[btn.handler](); // closeModal
}
}
});
footer.appendChild(button);
});
}
function closeModal() {
window.showFullDescription = function (descripcion) {
showModal("Descripción del Curso", `<div class="modal-description">${descripcion}</div>`);
};
window.closeModal = function () {
const modal = document.getElementById("modal-overlay");
if (modal) {
modal.remove();
document.body.style.overflow = "";
}
}
};
function showToast(type, message) {
const toast = document.createElement("div");
@ -343,7 +364,7 @@ function loadProfessorCourses(container) {
<input type="text" name="nombre" required>
<label>Descripción</label>
<textarea name="descripcion" maxlength="250" rows="4" style="width: 100%; padding: 0.75rem; margin: 0.5rem 0 1rem 0; border: 1px solid #e2e8f0; border-radius: 6px; font-family: 'Inter', sans-serif;"></textarea>
<textarea name="descripcion" maxlength="250" rows="4" style="width: 100%; padding: 0.75rem; margin: 0.5rem 0 1rem 0; border: 1px solid #e2e8f0; border-radius: 6px; font-family: 'Inter', sans-serif;"></textarea>
<label>Tipo de Curso *</label>
<select id="courseType" name="tipo" required>
@ -387,38 +408,64 @@ function loadProfessorCourses(container) {
</thead>
<tbody>
${courses
.map(
(course) => `
<tr>
<td>${course.nombre}</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"
? "active"
: course.estado === "completado"
? "completed"
: course.estado === "archivado"
? "archived"
: "inactive"
}">${course.estado}</span>
</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>
</div>
</td>
</tr>
`
)
.map((course) => {
const desc = course.descripcion || "-";
const safeDesc = desc.replace(/"/g, "&quot;").replace(/'/g, "\\'");
return `
<tr>
<td>${course.nombre}</td>
<td class="description-cell">
<span class="truncated-text" id="desc-${course.id}">
${desc}
</span>
<span class="read-more-container" id="readmore-${course.id}"></span>
</td>
<td><span class="badge ${getCourseTypeClass(course.tipo)}">${formatCourseType(course.tipo)}</span></td>
<td>
<span class="badge ${
course.estado === "activo"
? "active"
: course.estado === "completado"
? "completed"
: course.estado === "archivado"
? "archived"
: "inactive"
}">${course.estado}</span>
</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>
</div>
</td>
</tr>`;
})
.join("")}
</tbody>
</table>
</div>
</div>`;
// Evaluar si se debe mostrar "Leer más" en cada descripción
setTimeout(() => {
courses.forEach(course => {
const span = document.getElementById(`desc-${course.id}`);
const leerMas = document.getElementById(`readmore-${course.id}`);
if (span && leerMas && span.scrollWidth > span.clientWidth) {
const safeDesc = (course.descripcion || "")
.replace(/'/g, "\\'")
.replace(/"/g, "&quot;")
.replace(/\n/g, "\\n");
leerMas.innerHTML = `
<a href="#" class="read-more" onclick="showFullDescription('${safeDesc}'); return false;">
Leer más
</a>`;
}
});
}, 0);
setupCourseForm();
})
.catch((err) => {
@ -954,12 +1001,6 @@ function renderStudentsTable(alumnos) {
<input type="text" id="studentSearch" placeholder="Buscar alumno...">
</div>
<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">
@ -1343,51 +1384,64 @@ 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);
};
};
window.handleCSVUpload = function (event) {
const file = event.target.files[0];
if (!file) return;
const resultadoDiv = document.getElementById("resultadoCSV");
if (resultadoDiv) resultadoDiv.innerHTML = "";
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());
if (!nombre || !email || !telefono || !tipoCurso || !nombreCurso) {
continue;
}
alumnos.push({ nombre, email, telefono, tipoCurso, nombreCurso });
}
if (alumnos.length === 0) {
showToast("error", "El archivo CSV no contiene datos válidos");
if (resultadoDiv) resultadoDiv.innerHTML = `<div class="alert alert-danger">No se encontraron alumnos válidos en el archivo.</div>`;
return;
}
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");
if (resultadoDiv) resultadoDiv.innerHTML = `<div class="alert alert-success">${data.message}</div>`;
loadStudentsManagement(document.querySelector("#students-content"));
} else {
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}`);
if (resultadoDiv) resultadoDiv.innerHTML = `<div class="alert alert-danger">${list}</div>`;
} else {
showToast("error", data.error || "Error al importar alumnos");
if (resultadoDiv) resultadoDiv.innerHTML = `<div class="alert alert-danger">${data.error || "Error desconocido"}</div>`;
}
}
})
.catch(() => {
showToast("error", "Error al procesar el archivo");
if (resultadoDiv) resultadoDiv.innerHTML = `<div class="alert alert-danger">Error al procesar el archivo.</div>`;
});
};
reader.readAsText(file);
};