Importador de módulos
Asistente "Crear módulo desde archivo" (import.php), lectores en Python (parse-curriculum.py, parse-document.py), plantilla Excel, bancos de preguntas e IA opcional.
El asistente Nuevo módulo (import.php, también "Crear módulo desde archivo" en Administración del sitio → Cursos) crea un módulo con la estructura oficial de la Academia a partir de un archivo, de un documento libre o desde cero. El módulo siempre queda como borrador (oculto) hasta publicarlo.
| Dato | Valor |
|---|---|
| Página | /local/deltamodules/import.php |
| Capacidad | moodle/course:create (por perfil, el Jefe de Talento Humano; y el super administrador) |
| Página externa de administración | local_deltamodules_import (settings.php, sección courses) |
| Estado entre pasos | $SESSION->deltamodimport |
| Clases | importer, template, academy, ai_author, formularios classes/form/import_*.php |
| Lectores | /parse-curriculum.py y /parse-document.py (Python 3, copiados a la raíz del contenedor) |
Pasos del asistente
Paso (step) | Qué hace |
|---|---|
upload | Elige el modo (mode) y sube el archivo. |
ai | Solo en modo documento con IA: completa los temas de a uno (ver más abajo). Se puede saltar con skip=1. |
review | Tabla con cada tema y su estado (video, enlace, pendiente, lectura, preguntas). Si el archivo trae varios módulos, se elige uno. Muestra cómo se reparten las preguntas del banco. |
config | Formulario import_config: número, título, categoría, objetivo, aplicación, cargos obligatorios, complementarios y responsables de contenido, tipo de asignación, responsable de revisión, estructura (repasos, candado, certificado) y reglas propias. Crea el módulo con academy::create_module(). Si el número ya existe, propone academy::next_module_number(). |
done | "Revisar y publicar": estado del módulo, cargar preguntas de todos los repasos con un archivo (import_questions → academy::load_questions), responsable y fecha de la próxima revisión (academy::update_review), asignar cargos (import_cargos → academy::assign_cargos), publicar o eliminar el borrador. |
publish | POST con sesskey: course_change_visibility($courseid, true). Exige moodle/course:visibility. |
cancel | Borra el banco de preguntas temporal y el estado de sesión y vuelve a Módulos. |
En modo "en blanco" el módulo, al crearse, se abre directamente en modo de edición (editmode.php). Un borrador solo se puede eliminar si no es un módulo del manual (access::is_manual) ni PTS ni APOYO-IA, y con moodle/course:delete.
Modos de entrada
Formulario import_upload. Acepta .xlsx, .pdf, .docx, .csv y .json (máximo 20 MB) y, opcionalmente, un banco de preguntas (.docx, .txt, .xml, .gift). El archivo se lee con importer::parse($path), que ejecuta python3 /parse-curriculum.py <archivo>.
| Formato | Cómo lo lee parse-curriculum.py |
|---|---|
.xlsx | Plantilla oficial: hoja Temas (A1 = "MÓDULO N: Nombre", fila 2 de títulos) y hoja opcional Preguntas, que se cuelga de cada tema por su número. Otros Excel: cada hoja es un módulo (o una columna "Módulo"), con hipervínculos. |
.pdf | Tabla "Nº, Tema (código + nombre), Objetivo, Ver video, casilla", con varios "MÓDULO N: …" en el mismo PDF; usa pypdf para texto e hipervínculos. |
.docx | Párrafos "MÓDULO N: …" seguidos de tablas de temas (con hipervínculos). |
.csv | Cualquier cabecera reconocible (Tema o Nombre, Objetivo, Enlace o URL, Código, Módulo); separador ; o ,. |
.json | Se valida y se devuelve tal cual. |
Word, Excel y CSV detectan las columnas de forma flexible (map_headers). Los códigos PowerBuilder se reconocen con una expresión regular del estilo G-3-OP-01-GE-01002.
Salida común de los lectores
Los dos scripts de Python imprimen una línea JSON por la salida estándar (el PHP toma la última línea JSON válida). importer::sanitize_modules() normaliza el resultado:
{"ok": true, "source": "archivo.xlsx", "modules": [
{"n": 13, "raw_title": "MÓDULO 13: ...", "titulo": "...",
"temas": [{"n": 1, "codigo": "G-3-OP-...", "tema": "...", "objetivo": "...",
"url": "https://youtu.be/...", "texto": "", "preguntas": []}]}
]}En caso de error: {"ok": false, "error": "..."}. Si python3 no está disponible, el asistente lo informa ("Python 3 no está disponible en el servidor"). Las dependencias pypdf, python-docx y openpyxl se instalan en el Dockerfile.
importer::clean_title() quita el prefijo "Módulo N:" y pasa a mayúscula inicial los títulos que vienen completos en MAYÚSCULAS.
Plantilla Excel
template.php (capacidad moodle/course:create) descarga Plantilla modulo Academia Deltamontero.xlsx, generada por local_deltamodules\template::spreadsheet() con PhpSpreadsheet:
| Hoja | Contenido |
|---|---|
Instrucciones | Logo (/opt/delta-reports/logo.png), pasos de uso y una fila de ejemplo. |
Temas | A1 = "MÓDULO 13: Nombre del módulo"; columnas Nº, Tema, Objetivo, Enlace del video, Código PowerBuilder; 30 filas numeradas. |
Preguntas | Nº tema, Pregunta, Tipo (lista: Opción múltiple, Verdadero o falso), Opción A a Opción D, Respuesta correcta (lista: A, B, C, D, Verdadero, Falso), Explicación. |
Las cabeceras son las que reconoce parse-curriculum.py; si se cambian, hay que cambiar ambos.
Bancos de preguntas
importer acepta preguntas en varios formatos y las convierte a lo que entiende el importador nativo de Moodle:
| Entrada | Tratamiento |
|---|---|
.xml (Moodle XML) | Directo (qformat_xml). |
.gift o texto con sintaxis GIFT | Directo (qformat_gift). |
.docx o .txt escritos a mano | importer::parse_free() los interpreta y importer::to_gift() los convierte a GIFT. |
| Aiken | Se detecta por contenido cuando no es GIFT (qbank_format). |
.xlsx (hoja Preguntas de la plantilla) | Se leen con parse-curriculum.py y se crean con builder::add_questions() (solo en "Revisar y publicar" y en las pruebas técnicas). |
Formato reconocido por parse_free():
Tema 2 (o "13.2 Nombre del tema": las preguntas siguientes van a ese tema)
1. ¿Pregunta? (numerada o no; puede seguir en varias líneas)
a) opción (A. / a- / a) ; mayúscula o minúscula)
*b) opción correcta (o "(correcta)" / "✓" al final de la opción)
Respuesta: b (o "ANSWER: B"; "Respuesta: Falso" = verdadero/falso)importer::docx_lines() lee el Word directamente del ZIP y reconstruye las letras y números de las listas automáticas de Word (que no vienen en el texto). Una sublista numerada bajo una pregunta se interpreta como opciones.
importer::qbank_preview() lee el archivo sin guardar nada y cuenta preguntas por tipo y por categoría. importer::match_topic($categoria, $temas) asigna cada categoría a un tema por su número (13.2 …, 2. …, Tema 2) o por su nombre (con o sin "Repaso:"). Las preguntas que no corresponden a un tema quedan en el banco del módulo, en la categoría "Módulo N. Título".
IA opcional (ai_author)
En modo documento, si se marcó "Asistente de IA", el paso ai llama a ai_author::enrich_topic() tema por tema (los temas con al menos 200 caracteres de texto, ai_author::MIN_TEXT):
- Usa la instancia de
local_ai_managerdel chat (conectorchatgptcon endpoint de Groq, o la instancia llamada "Chat IA - Academia Deltamontero") y el modeloopenai/gpt-oss-20b(ai_author::MODEL), temperatura 0.2 y respuesta en formato JSON. - Envía hasta 6.000 caracteres del tema y pide un objetivo breve y 3 preguntas de opción múltiple (4 opciones, una correcta) sacadas solo del texto.
- Si recibe un error 429 espera lo que indica el proveedor (máximo 20 s) y reintenta una vez.
- Valida la respuesta (
validate): descarta preguntas incompletas, con opciones repetidas o con menos de 3 o más de 5 opciones. Solo completa el objetivo si el tema no tenía y las preguntas si el tema no traía. - Si la IA falla en un tema, ese tema queda como estaba y el módulo se crea igual.
Otros caminos para crear módulos
| Camino | Lógica |
|---|---|
php /create-courses.php (CLI) | builder::create_module() sobre /opt/academia-dm/academia_dm_contenido.json, con la tabla de equivalencias de nombres cortos de /manual-academia.json. Idempotente, sin certificado. |
Función externa local_deltamodules_create_module | builder::create_module() + builder::add_certificate(). Ver Servicios web. |
php /import-checkpoints.php | Convierte las preguntas de los videos (/checkpoints/c2_*.json) en cuestionarios después de cada video interactivo. Ver Scripts de configuración. |
"Subir cursos" de Moodle (/admin/tool/uploadcourse/) redirige a Módulos → Nuevo módulo para quien no es super administrador; para el super administrador muestra un aviso que recomienda el asistente.
Repasos y editor de preguntas
Pantalla corta de nuevo repaso, editor propio de preguntas (question_editor), aviso al empezar automático o escrito a mano y seguridad de los exámenes.
Manual de la Academia
El curso de Introducción (induccion_ia) se genera desde el código con la clase manual y el script build-intro-manual.php.