Academia Deltamontero · Documentación
Desarrollolocal_deltamodules

Módulos y estándar

Estructura oficial de los módulos de la Academia y cómo se crea y se mantiene con academy, builder, standard y health.

Un módulo de la Academia es un curso de Moodle dentro de la categoría Academia Deltamontero (o de una subcategoría suya). Cuatro clases se reparten el trabajo:

ClaseArchivoPapel
academyclasses/academy.phpEstructura oficial de un módulo nuevo y operaciones de alto nivel (certificado, completado, revisión anual, bancos de preguntas).
builderclasses/builder.phpFábrica de bajo nivel: crea cursos, secciones, actividades, preguntas y el certificado.
standardclasses/standard.phpEstándar que deben cumplir todos los módulos (los existentes y los nuevos), aplicado en cada despliegue y al instante al crear o editar una actividad.
healthclasses/health.phpRevisión de los módulos: encuentra lo que impide avanzar y lo repara cuando se puede.

¿Qué es un módulo de la Academia?

standard::is_academy(int $courseid) decide si un curso sigue el estándar: es true cuando la categoría del curso es la categoría raíz cuyo nombre es academy::CATEGORY ('Academia Deltamontero') o una subcategoría suya (se compara el path de la categoría). El sitio (SITEID) nunca lo es. El resultado se guarda en una caché estática por petición.

Casi todo lo automático (reglas, observadores, avisos de borrador, pantalla corta de nuevo repaso) solo actúa sobre cursos para los que is_academy() devuelve true. El curso de la Prueba Técnica de Selección (seleccion_pruebas_tecnicas, número de ID PTS) y el curso de Introducción se tratan aparte.

Estructura oficial (academy)

academy::create_module(array $def) crea un módulo con exactamente la estructura de los módulos 1 a 12. La usa el asistente "Crear módulo desde archivo" (Importador).

ElementoCómo queda
Nombre completoMódulo N. Título
Nombre cortom{N}_{titulo} en minúsculas, sin tildes, máximo 40 caracteres de título (importer::suggest_shortname)
Número de IDMNN con dos dígitos (academy::idnumber, p. ej. M13)
CategoríaLa indicada o, por defecto, la raíz "Academia Deltamontero" (builder::ensure_category)
Formatotopics, una sección por tema, idioma es, completado activado, showgrades = 0
VisibilidadOculto (borrador) hasta publicarlo
DescripciónBloque con la marca academy::MARK (data-delta-manual="1") con Objetivo y Aplicación principal
SeccionesN.1 Tema, N.2 Tema… con el objetivo como resumen
Contenido de cada temaVideo interactivo si la URL es de YouTube; enlace si no lo es; una página "Lectura: …" si el tema trae texto; el código PowerBuilder va en el idnumber del contenido principal
RepasoRepaso: Tema por tema con video (si se marcó "repasos"), o si el tema trae preguntas, o si el banco de preguntas tiene una categoría para ese tema
CandadoCon "candado", cada repaso queda bloqueado hasta completar el contenido principal de su tema
CertificadoSección final "Certificado" con mod_customcert y la plantilla de la regla "Diseño del certificado"
Completado del cursoTodas las actividades con seguimiento, menos el certificado (apply_course_completion)
Campos personalizados del cursoultima_revision, proxima_revision (+12 meses), responsable_revision, asignacion_por_cargo (HTML con "Obligatorio para" y "Complementario / según asignación para"), tipo_asignacion (1 Obligatorio, 2 Complementario, 3 Por desarrollo, 4 Por cliente)
MatrículasPor cohorte de cargo para los cargos obligatorios (rol student) y para los responsables de contenido (rol delta_resp_contenidos)

Definición que recibe create_module

$def = [
    'n' => 13,                       // número del módulo
    'titulo' => 'Nombre del módulo',
    'catid' => 0,                    // 0 = categoría Academia Deltamontero
    'objetivo' => '...', 'aplicacion' => '...',
    'responsable' => 'Talento Humano',
    'tipo' => 1,                     // 0..4 (ver academy::TIPOS)
    'obligatorios' => [/* cohortid */], 'complementarios' => [], 'editores' => [],
    'repasos' => true, 'candado' => true, 'certificado' => true,
    'rules' => ['attempts' => 3],    // reglas propias del módulo (opcional)
    'qbank' => ['path' => '...', 'name' => '...', 'format' => 'gift'],   // opcional
    'temas' => [
        ['tema' => '...', 'objetivo' => '...', 'codigo' => 'G-3-OP-...', 'url' => 'https://youtu.be/...',
         'texto' => '...', 'preguntas' => [/* formato de builder::add_questions */]],
    ],
];

Las reglas propias (rules) se guardan antes de crear el contenido, para que cada video y repaso nazca con ellas. Devuelve courseid, quizcms (cmids de los repasos creados), contadores (videos, links, pending, questions, readings), bank (resultado del banco de preguntas), cert y criteria.

Otras operaciones de academy

MétodoQué hace
exam_settings()Configuración común de todos los cuestionarios: intentos y tiempo de la regla general, sin ventana segura, una pregunta por página, respuestas mezcladas, navegación secuencial, calificación por la nota más alta, revisión del intento sin mostrar la respuesta correcta ni la retroalimentación.
exam_notice($candidate, $attempts, $gradepass, $grade, $courseid)Recuadro "Antes de empezar, lee esto" con los intentos, la nota para aprobar, la regla de volver a ver el video y el control de cambio de pantalla. Con $candidate = true es el aviso de la prueba técnica (aclara que aprobar no garantiza la contratación).
custom_notice, custom_notice_html, strip_custom_notice, with_notice, strip_noticeManejo del aviso automático y del escrito a mano (marca data-delta-exam-notice="custom"). Ver Repasos y editor.
next_module_number()Siguiente número libre a partir de los idnumber M\d\d.
cargo_cohorts()Cohortes con idnumber que empieza por cargo_, sin el prefijo "Cargo: " del nombre.
review_owners()Sugerencias de responsables de revisión (cuatro fijas más los valores usados).
import_bank(...)Importa un banco Moodle XML, GIFT o Aiken al banco del curso con el importador nativo; reparte cada pregunta al repaso de su tema según el nombre de su categoría (importer::match_topic).
course_topics($courseid)Temas de un módulo ya creado (sin la sección 0 ni "Certificado") y el repaso de cada uno.
load_questions($courseid, $path, $name)Carga preguntas desde un solo archivo en un módulo existente; crea los repasos que falten.
import_into_quiz($cmid, $path, $name)Carga todas las preguntas de un archivo en un cuestionario (pruebas técnicas).
assign_cargos($courseid, $cohortids)Matricula cargos obligatorios y reescribe "Obligatorio para" del campo asignacion_por_cargo.
review_courses()Cursos con revisión anual: idnumber M\d\d+ y PTS.
gate_certificate($courseid)Restricción del certificado. Ver Certificados.
apply_course_completion($courseid)Criterio de completado del curso: todas las actividades visibles con seguimiento, sin el certificado y sin los temas que son solo para un grupo.
update_review($courseid, $responsable, $next)Responsable y próxima revisión desde "Revisar y publicar".

Fábrica de bajo nivel (builder)

MétodoQué hace
create_module(array $modulo, ?int $catid)Formato heredado (JSON academia_dm_contenido.json y función MCP): id (shortname) y titulo, con temas o temas_agrupados. Idempotente por shortname: si existe, devuelve su id. Crea el curso visible.
ensure_category($name)Devuelve o crea una categoría raíz por nombre.
is_youtube_url, normalize_youtube_urlDetecta y normaliza enlaces de YouTube a https://www.youtube.com/watch?v=ID.
add_item(...)Crea un contenido de un tema según su tipo: video, enlace, texto, aviso, imagen, archivo, pagina, prueba. Devuelve si usó el código PowerBuilder del tema.
create_quiz($courseid, $sectionnum, $name, $intro)Cuestionario con academy::exam_settings(), nota máxima 10, completado al recibir calificación y el aviso inicial. Registra el cmid en builder::$createdquizzes.
add_questions($cmid, $qs, $title)Convierte preguntas a GIFT y las importa con qformat_gift al banco del cuestionario; recalcula la nota.
add_certificate($courseid, $templateid)Sección nueva "Certificado" con mod_customcert ("Certificado del curso", entrega I en línea). Idempotente: si el curso ya tiene certificado, devuelve su cmid. Necesita la plantilla del sitio.
load_cert_template($cmid, $templateid)Copia páginas y elementos de una plantilla del sitio al certificado del módulo (reemplaza los anteriores).

Formato de las preguntas que reciben add_questions y academy::create_module:

$qs = [
    ['q' => 'Enunciado', 't' => 'mc', 'opts' => ['A', 'B', 'C'], 'ok' => 1, 'fb' => 'Retroalimentación'],
    ['q' => 'Enunciado', 't' => 'vf', 'ok' => 0],   // vf: ok 0 = Verdadero, 1 = Falso
];

Los videos se crean con type = 'yt', source = 'url', completionpercentage = 0 y extendedcompletion = {"watchtillend":1} (completado nativo del plugin al llegar al final). Los enlaces se abren en una pestaña nueva (display = 3) y se marcan como hechos al abrirlos.

Estándar de la Academia (standard)

standard hace que todos los módulos se comporten igual según sus Reglas. Se aplica:

  • en cada despliegue, desde infrastructure/moodle/normalize-academy.php (vía health::repair y standard::sweep_pending);
  • al instante, con el observador module_changed sobre course_module_created y course_module_updated;
  • cuando Talento Humano cambia una regla (apply_changes).

Videos: apply_video($cm)

  • Con la regla video_player = 1, displayoptions queda en {"distractionfreemode":1} y conserva solo usecustomposterimage y posterimagesize (portada propia de los videos alojados en R2). Un displayoptions vacío no se toca.
  • Con video_completion = end: extendedcompletion.watchtillend = 1, completionpercentage = 0, completado automático sin completionview. Con view: se quita watchtillend y se marca al abrirlo.

Repasos: apply_quiz($cm, $isnew, $force)

  1. Anota el cuestionario en la configuración examsecured (lista JSON de ids) para que configure-exam-security.php no le vuelva a imponer las reglas de examen.
  2. Intentos y tiempo: los del cuestionario. Solo si la regla attempts o timelimit está en $force (Talento Humano la acaba de cambiar) se impone el valor de la regla.
  3. Nota para aprobar: si el cuestionario no tiene (gradepass <= 0) o si passpercent está en $force, se fija grade * passpercent / 100 en el ítem de calificación y se recalcula el completado de quien ya rindió.
  4. Aviso inicial: con exam_notice = 1, se reescribe con los valores reales; un aviso escrito a mano se respeta.
  5. Completado: automático por calificación (completiongradeitemnumber = 0), con completionpassgrade = 1 si la regla quiz_completion es pass y hay nota para aprobar.
  6. Candado: con quiz_lock = 1, la restricción de acceso exige completar todos los videos interactivos de la misma sección (se agregan los que falten); con 0, se quitan esas condiciones. Solo se toca una restricción raíz con operador &.

Módulo completo: apply_course($courseid, $force)

Aplica apply_video y apply_quiz a cada actividad, luego apply_certificate (solo si cambió cert_enabled o cert_template), academy::gate_certificate y academy::apply_course_completion. Si cambiaron video_completion o quiz_completion, reinicia el estado de completado (reset_all_state) de las actividades afectadas para recalcular el avance de todos con la regla nueva. apply_changes($changes) recorre los módulos devueltos por rules::save() con un límite de tiempo de 600 s.

Al crear o editar una actividad: module_changed

  • Video: apply_video.
  • Cuestionario: apply_quiz; si se creó desde /course/modedit.php, guarda $SESSION->deltanewquiz para que la siguiente página sea su editor (questions.php); además registra una función de cierre (core_shutdown_manager) que vuelve a aplicar apply_quiz cuando Moodle termina de guardar la nota para aprobar.
  • Al crear cualquier actividad o al tocar un certificado: recalcula gate_certificate y apply_course_completion.

Volver a ver el video

Regla: quien agota sus intentos sin aprobar vuelve a ver el video del tema y recibe intentos nuevos (retakeattempts, 2 por defecto; 0 lo desactiva).

Marcar como pendiente

El observador attempt_submitted (evento \mod_quiz\event\attempt_submitted) comprueba needs_retake(): tiene nota para aprobar, la regla da intentos nuevos, su mejor nota no alcanza y ya usó todos los intentos permitidos. Si es así, agrega el id del cuestionario a la preferencia local_deltamodules_retake (standard::RETAKE_PREF). No bloquea en ese momento para que pueda ver la revisión de su intento.

Procesar al volver

El hook standard::before_http_headers revisa los pendientes del curso actual. En la revisión del intento (mod-quiz-review) solo muestra un aviso; en el curso, en un video o en el repaso ejecuta process_retake().

Reiniciar el video y dar intentos

process_retake() borra el progreso del reproductor (interactivevideo_log, interactivevideo_completion), deja el video como no completado y crea o actualiza una excepción de usuario del cuestionario (mod_quiz\local\override_manager) con intentos = usados + retakeattempts. Desde la página del repaso redirige al video.

sweep_pending($courseid) marca como pendientes a quienes ya habían agotado sus intentos antes de existir la regla (lo ejecuta normalize-academy.php).

Revisión de módulos (health)

health::check_all() revisa todos los módulos de la Academia (menos PTS) y health::check($courseid) devuelve problemas con nivel error, warn o info, si los arregla el botón Reparar (fix) y un enlace a donde corregirlos (action). Lo usa modcheck.php (Módulos → Revisar los módulos).

RevisiónNivel¿Reparable?
Seguimiento de completado apagado en el cursoerrorSí
Video que no se marca como hecho según la reglaerrorSí
Video sin enlaceerrorNo (enlace al formulario)
Video de YouTube eliminado, privado o que no permite insertarseerrorNo (enlace al formulario)
Repaso sin preguntaserrorNo (enlace al editor)
Repaso sin nota para aprobar (con la regla "al aprobarlo")errorSí
Repaso sin completado automáticoerrorSí
Restricción que pide una actividad que ya no existeerrorSí (se quita la condición)
Restricción que pide una actividad ocultaerrorNo
Módulo sin certificado con la regla "Entrega certificado" activawarnNo
Módulo con actividades pero sin criterio de completadowarnSí
Tema visible vacíoinfoNo
Personas que vieron un video completo y quedaron sin "Hecho"errorSí (recalcula)
Personas que ven videos sin tener el módulo asignadoinfoNo

La comprobación de YouTube (youtube_status) consulta el servicio oEmbed de YouTube en tandas de 10 con curl_multi, forzando IPv4 y respetando $CFG->proxyhost. Interpreta 200 = reproducible, 401/403 = privado o sin permiso de inserción, 404 = eliminado. Las respuestas se recuerdan 6 horas (health::YT_TTL) en la configuración ytcheck; una consulta sin respuesta no se guarda.

health::repair($courseid) activa el completado si estaba apagado, quita de las restricciones las condiciones sobre actividades inexistentes, aplica standard::apply_course() y recalcula el completado de las vistas completas que quedaron sin "Hecho". Devuelve cuántos avances recalculó. No cambia intentos, tiempo ni nota configurados en cada repaso.

En esta página