El Porqué: La Visión del Usuario Final
Antes de escribir una sola línea de código, es fundamental entender la experiencia que queremos crear. Este módulo no es solo una lista de preguntas; es un centro de conocimiento dinámico diseñado para resolver dudas de forma instantánea y contextual.
Un Recorrido por el Módulo FAQ
Imaginemos a Ana, una alumna del curso de "Administración de Condominios".
- El Descubrimiento: Ana está en su aula virtual y tiene una duda sobre la normativa de mascotas. En el menú lateral de "Herramientas del Aula", ve un nuevo enlace: Preguntas Frecuentes. Hace clic.
- La Interfaz: Se le presenta una página limpia con el título del FAQ del curso. Ve un buscador prominente y, debajo, las preguntas organizadas por categorías ("Legal", "Contabilidad", etc.) en un formato de acordeón.
- La Búsqueda: Ana escribe "mascotas" en el buscador. La página se actualiza y le muestra solo las preguntas relevantes, como "¿Puede el reglamento prohibir la tenencia de mascotas?".
- La Respuesta: Al hacer clic en la pregunta, esta se expande para mostrar una respuesta clara y concisa. Además del texto, ve una infografía que resume la ley y una foto de un letrero de ejemplo para áreas comunes, todo dentro de la misma respuesta.
Esta es la experiencia fluida, intuitiva y rica en contenido que vamos a construir.
El Cómo: Guía Técnica de Implementación
FASE 1: Preparación del Entorno y Base de Datos
Objetivo
Crear la estructura de carpetas para organizar nuestros archivos y definir las tablas en la base de datos que almacenarán toda la información de manera eficiente y escalable.
1.1 Estructura de Directorios
En la raíz del proyecto, crea la siguiente estructura. Esto mantendrá el código del módulo encapsulado y la gestión de imágenes ordenada.
/ (raíz del proyecto)
|
+-- /faq/
| |-- admin_faq.php (Panel para gestionar las FAQs)
| |-- faq_curso.php (La página que verán los usuarios)
| |-- /img_preguntas/ (Las imágenes de las preguntas irán aquí)
| |-- /img_respuestas/ (Las imágenes de las respuestas irán aquí)
|
+-- ... (otros directorios)
1.2 Creación de las Tablas de la Base de Datos
Ejecuta las siguientes dos sentencias SQL. Hemos separado las imágenes en su propia tabla para permitir múltiples imágenes por pregunta o respuesta, una arquitectura mucho más flexible.
Consejo de Maestro
Separar las imágenes en una tabla `faq_imagenes` (una relación "uno a muchos") es una práctica de diseño de bases de datos superior. Evita tener un número limitado de columnas de imágenes en la tabla principal y permite añadir, quitar y ordenar imágenes sin alterar el registro de la pregunta.
Tabla `faq_preguntas`
CREATE TABLE faq_preguntas (
id INT AUTO_INCREMENT PRIMARY KEY,
curso_id INT NOT NULL, -- Vincula con `cursos_sence.id`
pregunta TEXT NOT NULL,
respuesta TEXT NOT NULL,
categoria VARCHAR(100),
palabras_clave VARCHAR(255) NULL,
orden INT DEFAULT 0,
estado ENUM('activo', 'inactivo') NOT NULL DEFAULT 'activo',
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX (curso_id)
);
Tabla `faq_imagenes`
CREATE TABLE faq_imagenes (
id INT AUTO_INCREMENT PRIMARY KEY,
faq_id INT NOT NULL, -- Vincula con `faq_preguntas.id`
tipo_asociacion ENUM('pregunta', 'respuesta') NOT NULL,
ruta_imagen VARCHAR(255) NOT NULL,
descripcion_img VARCHAR(255) NULL,
orden INT DEFAULT 0,
INDEX (faq_id)
);
FASE 2: Desarrollo del Backend (PHP)
Objetivo
Construir la lógica del servidor que permitirá a los administradores gestionar el contenido (CRUD: Crear, Leer, Actualizar, Borrar) y a los usuarios visualizarlo de forma dinámica y segura.
2.1 Panel de Administración (`faq/admin_faq.php`)
Este es el corazón de la gestión. El código debe manejar la carga de datos para edición, el guardado de nuevos registros y, muy importante, el procesamiento de múltiples archivos de imagen.
Consejo de Maestro: Manejo de Archivos
La subida de archivos es un punto crítico de seguridad. El script debe validar la extensión (ej. 'jpg', 'png') y el tipo MIME del archivo para prevenir la subida de scripts maliciosos. Además, al renombrar el archivo con el ID de la pregunta y el ID del curso, garantizamos que no habrá colisiones de nombres y mantenemos una trazabilidad clara.
A continuación, un esqueleto comentado del código PHP que debe ir al inicio del archivo `admin_faq.php`.
<?php
// CONECTAR A LA BASE DE DATOS Y VALIDAR SESIÓN DE ADMINISTRADOR
// include_once('../include/conecta_mysql_otec.php');
// session_start();
// if (!isset($_SESSION['es_admin']) || !$_SESSION['es_admin']) {
// die("Acceso denegado.");
// }
// PROCESAMIENTO DEL FORMULARIO (CUANDO SE ENVÍA)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['guardar_faq'])) {
// 1. OBTENER Y VALIDAR DATOS DEL FORMULARIO (pregunta, respuesta, curso_id, etc.)
$curso_id = (int)$_POST['curso_id'];
// ... obtener las demás variables ...
// 2. INICIAR TRANSACCIÓN (para asegurar que todo se guarde correctamente)
$mysqli->begin_transaction();
try {
// 3. INSERTAR/ACTUALIZAR DATOS DE TEXTO EN `faq_preguntas`
// Si es una nueva pregunta:
$stmt = $mysqli->prepare("INSERT INTO faq_preguntas (curso_id, pregunta, ...) VALUES (?, ?, ...)");
$stmt->bind_param("is...", $curso_id, $pregunta, ...);
$stmt->execute();
$faq_id = $mysqli->insert_id; // Obtenemos el ID de la nueva pregunta. ¡Crucial!
// Si es una edición:
// $stmt = $mysqli->prepare("UPDATE faq_preguntas SET pregunta = ?, ... WHERE id = ?");
// 4. PROCESAR IMÁGENES (Ejemplo para las imágenes de la respuesta)
if (isset($_FILES['imagenes_respuesta']) && !empty($_FILES['imagenes_respuesta']['name'][0])) {
$imagenes = $_FILES['imagenes_respuesta'];
$directorio_destino = 'img_respuestas/';
foreach ($imagenes['name'] as $key => $nombre_original) {
// VALIDAR CADA IMAGEN (TAMAÑO, TIPO, ERRORES)
// Crear nuevo nombre de archivo: {id_pregunta}_{id_curso}_{nombre_original_sanitizado}
$nombre_sanitizado = preg_replace("/[^a-zA-Z0-9_.-]/", "_", $nombre_original);
$nuevo_nombre = $faq_id . '_' . $curso_id . '_' . time() . '_' . $nombre_sanitizado;
$ruta_final = $directorio_destino . $nuevo_nombre;
// Mover el archivo al directorio
move_uploaded_file($imagenes['tmp_name'][$key], $ruta_final);
// Insertar la ruta en la tabla `faq_imagenes`
$stmt_img = $mysqli->prepare("INSERT INTO faq_imagenes (faq_id, tipo_asociacion, ruta_imagen) VALUES (?, 'respuesta', ?)");
$stmt_img->bind_param("is", $faq_id, $ruta_final);
$stmt_img->execute();
}
}
// 5. CONFIRMAR TRANSACCIÓN
$mysqli->commit();
echo "FAQ guardada con éxito.";
} catch (Exception $e) {
// 6. REVERTIR TRANSACCIÓN SI ALGO FALLA
$mysqli->rollback();
echo "Error al guardar: " . $e->getMessage();
}
}
// LÓGICA PARA CARGAR DATOS (para el formulario de edición y la lista de FAQs)
?>
2.2 Visualización para el Usuario (`faq/faq_curso.php`)
Este script se encarga de mostrar la información de forma segura y eficiente.
<?php
// CONECTAR A LA BASE DE DATOS Y VALIDAR SESIÓN DEL USUARIO
// include_once('../include/conecta_mysql_otec.php');
// session_start();
// if (!isset($_SESSION['aula_curso_id'])) {
// die("Acceso no autorizado o sesión no iniciada.");
// }
// $curso_id = $_SESSION['aula_curso_id'];
// LÓGICA DEL BUSCADOR
$termino_busqueda = isset($_GET['q']) ? $_GET['q'] : '';
$sql_where = "";
if (!empty($termino_busqueda)) {
$termino_like = '%' . $mysqli->real_escape_string($termino_busqueda) . '%';
$sql_where = "AND (pregunta LIKE '$termino_like' OR respuesta LIKE '$termino_like' OR palabras_clave LIKE '$termino_like')";
}
// CONSULTA PRINCIPAL
$sql_faqs = "SELECT * FROM faq_preguntas WHERE curso_id = ? AND estado = 'activo' $sql_where ORDER BY categoria, orden";
// Preparar y ejecutar la consulta...
// $faqs = resultado de la consulta...
// AHORA, DENTRO DEL BUCLE QUE MUESTRA LAS FAQS, HACEMOS LA CONSULTA DE IMÁGENES
// foreach ($faqs as $faq) {
// $faq_id = $faq['id'];
// $imagenes_pregunta = $mysqli->query("SELECT * FROM faq_imagenes WHERE faq_id = $faq_id AND tipo_asociacion = 'pregunta' ORDER BY orden");
// $imagenes_respuesta = $mysqli->query("SELECT * FROM faq_imagenes WHERE faq_id = $faq_id AND tipo_asociacion = 'respuesta' ORDER BY orden");
// // ...luego mostrar los resultados en el HTML
// }
?>
FASE 3: Integración y Frontend
Objetivo
Construir la interfaz visual que el usuario final verá y usará, y conectarla con el resto del aula virtual para una experiencia unificada.
3.1 Conectando el Módulo al Aula Virtual
El punto de entrada al FAQ será un enlace en el menú del aula. Abre el archivo principal del aula (ej. `aula_virtual_p.php`) y añade el siguiente código en la lista de "Herramientas del Aula":
<li class="nav-item">
<a class="nav-link" href="faq/faq_curso.php">
<i class="fas fa-question-circle me-2"></i>Preguntas Frecuentes
</a>
</li>
3.2 Interfaz del FAQ para el Usuario (`faq_curso.php`)
El diseño debe ser limpio. Un buscador en la parte superior y un acordeón de Bootstrap para las preguntas, agrupadas por categoría.
<!-- Buscador -->
<form method="get" action="faq_curso.php">
<div class="input-group mb-4">
<input type="text" name="q" class="form-control" placeholder="Escribe tu duda aquí...">
<button class="btn btn-primary" type="submit">Buscar</button>
</div>
</form>
<!-- Acordeón -->
<div class="accordion" id="faqAccordion">
<?php foreach ($faqs as $faq): ?>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq-<?php echo $faq['id']; ?>">
<?php echo htmlspecialchars($faq['pregunta']); ?>
</button>
</h2>
<div id="faq-<?php echo $faq['id']; ?>" class="accordion-collapse collapse" data-bs-parent="#faqAccordion">
<div class="accordion-body">
<p><?php echo nl2br(htmlspecialchars($faq['respuesta'])); ?></p>
<?php // Bucle para mostrar imágenes de respuesta ?>
<img src="<?php echo htmlspecialchars($img['ruta_imagen']); ?>" class="img-fluid rounded my-2" alt="Imagen de apoyo">
<?php // Fin del bucle ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>