1. Arquitectura
| Pieza | Archivo |
|---|---|
| Bloque en el curso | block_sence.php → classes/local/attendance.php, plantilla templates/session_form.mustache, AMD locker y timer; bloqueo en el servidor en lib.php (block_sence_after_require_login() → attendance::locked()) |
| Retorno de SENCE (sin sesión) | retorno.php + retorno_lib.php (token firmado, coherencia, glosa de errores del Anexo 2) |
| Panel | Páginas index, credentials, courses, blocks, records, connection, errors, students, notices, users, audit, licence, docs, export; presentación en classes/local/ui.php y plantillas panel*.mustache; tablas ordenables y con buscador en amd/src/tables.js |
| Lógica | otecs, course_config, attendance, inactivity (4.0.2), placement, reports, connection, notices, transport, exporter, audit, panel, panelusers, rut, migration |
| Código común Norteduc | norteduc_license, norteduc_panel_perms, norteduc_updates (copias idénticas, /root/moodle-plugins/shared/sync.py) |
No hay servicios externos fuera de SENCE (formulario del navegador), el servidor de licencias (activación una vez y version/sence.json diario) y el SMTP opcional. No hay CDN en la interfaz.
2. Flujo con SENCE
- El bloque arma un formulario POST hacia
https://sistemas.sence.cl/rce/Registro/IniciarSesion(o/rcetest/si la OTEC está en pruebas) con RutOtec, Token, LineaCapacitacion, RunAlumno (normalizado), CodSence, CodigoCurso (ID de acción del grupo), UrlRetoma y UrlError (/blocks/sence/retorno.php) e IdSesionAlumno = token firmadouserid.courseid.expira.hmac(24 h). - SENCE devuelve al navegador con un POST cross-site a
retorno.php, que corre conNO_MOODLE_COOKIES: valida la firma, comprueba que RUN, ID de acción y código SENCE coincidan con lo esperado y que el token no haya iniciado otra sesión, escribe{block_sence}y{block_sence_intento}y redirige con GET al curso (la cookie SameSite=Lax viaja). - El cierre llega sin IdSesionSence; se acepta aunque el token haya vencido si su firma es válida y hay una sesión abierta con él.
- Se mantiene el camino previo a 3.5 (POST directo a la página del curso) para sitios con SameSite=None, con las mismas comprobaciones.
codigoCurso, lineaCap…) para complementos que las leen, como Registro de avances.3. Base de datos
| Tabla | Contenido |
|---|---|
block_sence | Sesiones aceptadas (sin cambios de 3.x; índices nuevos por curso/usuario e idsesionalumno) |
block_sence_intento | Cada retorno: ok, error (GlosaError), token_invalido, incoherente; desde 4.0.2 también los cierres por inactividad (tipo=inactivo, resultado=inactividad, con el RUN y el ID de acción) |
block_sence_otec | Nombre, RUT, token, ambiente |
block_sence_course | Configuración por curso (código, línea, vigencia, OTEC, cierre, becarios). Las columnas required, showlogo, renewal y sessionhours se conservan por compatibilidad, pero desde 4.0.1 no se leen: course_config::fixed_rules() fija required=1, showlogo=1, renewal=daily |
block_sence_group | Grupo de Moodle → ID de acción y OTEC |
block_sence_conn | Segundos y conexiones por alumno, curso y día |
block_sence_mail | Avisos enviados o fallidos |
block_sence_puser | Usuarios del panel y permisos por sección (JSON) |
block_sence_audit | Bitácora de cambios (sin secretos) |
Preferencias de usuario (4.0.2, solo estudiantes con franquicia): block_sence_lastactive (última actividad) y block_sence_idlecourse (curso SENCE de esa actividad); se ignoran si son anteriores al inicio de sesión actual ($USER->currentlogin).
Ajustes en config_plugins: codigo_activacion, comprobante_activacion, retornosecret, notices_*, mailtransport, smtp*, update_*. La configuración por curso no viaja en los respaldos de curso.
4. Migración 3.x → 4.0
db/upgrade.php (2026100200) crea las tablas, agrega índices y llama a \block_sence\local\migration::from_3x() (idempotente): OTEC desde el JSON otecs y testenv; por cada instancia del bloque en un curso, su configuración (otec, codigoCurso, lineaCap, grupoBecas, asistenciaObligatoria, senceTiempoCierre, muestraLogo, vnModo); grupos «SENCE-XXXXXXX». asistenciaObligatoria, muestraLogo y la renovación de 3.x se ignoran: se guardan las reglas fijas (required=1, showlogo=1, renewal=daily). db/upgrade.php 2026100401 (4.0.1) alinea las filas ya existentes con un UPDATE sin cambiar el esquema. Se eliminan conectividad_enabled, validanet_url, validanet_apikey, require_activation, otecs, testenv; una OTEC con RUT o token ilegible se respalda en otecs_3x_backup.
4.1 Reglas fijas de SENCE (4.0.1 y 4.0.2)
- Asistencia obligatoria para estudiantes con franquicia (
course_config::learner()tiposence): sin la sesión del día,attendance::content()devuelvelock=truey el AMDlockerreemplaza el contenido del curso por el botón y el logo. En el servidor,block_sence_after_require_login()(callback derequire_login()con$cm) redirige a/course/view.phpcon el avisost_locked(o lanzamoodle_exceptionen AJAX y servicios web): cubre/mod/*/view.php, las demás páginas del módulo ypluginfile.phpde sus archivos. attendance::locked(): nunca para administradores ni para el equipo del curso (attendance::is_staff():moodle/course:viewhiddensectionsomoodle/course:viewhiddenactivities; docente, profesor sin edición, gestor); nunca si el curso no está configurado, no pide la sesión SENCE, está fuera de vigencia o no tiene el bloque; nunca para el grupo de becarios ni (desde 4.0.2) para quien no está en un grupo SENCE:attendance::franchised()exigecourse_config::learner()de tiposence; para el grupo SENCE, mientrassession_valid()sea falso. La cadenast_notauthorisedy el bloqueo de «sin grupo» de 4.0.1 se eliminaron.- Estudiantes sin grupo SENCE = no franquiciados (4.0.2): tipo
nonese trata como becario:attendance::learner()devuelve la notast_becassin botón ni logo (nada si el curso no tiene grupos SENCE,course_config::has_sence_groups()),reports::without_session(), avisos e informes solo cuentan el tiposence, el Resumen cuenta solo alumnos con franquicia y muestra el aviso informativoatt_nogroupen cursos con grupos SENCE,students.phplos muestra como «Sin franquicia» con el filtrotype(sence/none) y no revisa su RUN.course_config::problems()ya no marca «sin grupos» como problema: un curso sin grupos SENCE se ve como «Sin grupos SENCE» y el docente recibe la notanote_nogroups. El grupo de becarios queda opcional. Equipo del curso excluido de los informes con la misma regla queis_staff(). - Cierre por inactividad a los 60 minutos (4.0.2, Res. Ex. 4027), solo con
attendance::franchised():inactivity::page()(gancho del pie, también en el recuadro de bloqueo) marca el seguimiento y cargablock_sence/inactivity: mouse, teclado, rueda, desplazamiento, toque y puntero en la página y en marcos del mismo origen (reproductor SCORM, rescaneo cada 3 s), informados aajax.php?action=activecada minuto; a los 55 minutos (warn_at()) consultaaction=status(otras pestañas, seguimiento SCORM) y abre unrole="alertdialog"con el foco en «Seguir conectado»; a los 60 (limit()) va ainactive.php(sesskey), que comprueba en el servidor y llama ainactivity::close(): registro enblock_sence_intento,require_logout()y página de acceso con$SESSION->logininfomsg(idle_closed, en el idioma del estudiante). En el servidor,block_sence_after_require_login()llama ainactivity::on_request()en cadarequire_login(): si pasaron 60 minutos, cierra antes de atender (página: redirección al acceso; AJAX o servicios web:require_login_session_timeout_exception); si no, las peticiones de página (no AJAX), incluidasmod/scorm/datamodel.phpy los archivos del paquete, cuentan como actividad (escritura como máximo cada 30 s). No depende de$CFG->sessiontimeoutni cierra la sesión SENCE (requiere ClaveÚnica): al volver el mismo día la sesión SENCE sigue válida. El tiempo de conexión no suma el tiempo inactivo (el contador ya se detiene a los 5 minutos sin actividad). Solo pruebas automáticas:$CFG->block_sence_idle_test(segundos) enconfig.phpacorta el límite. - Participantes y Calificaciones no se bloquean: el bloqueo cubre
/course/view.php,/course/section.php,/course/resources.phpy/mod/…(con o sin$cm). - Una vez al día — texto del panel: «Una vez al día: el participante con franquicia inicia sesión SENCE cada día que trabaja en el curso, para que la suma de sesiones sea coherente con las horas del curso (Guía SENCE e-learning 2026). El cierre de sesión es opcional.» Fuente: «Guía de uso cursos e-learning, perfil participante» v2.0 (SENCE, 2026) (sesión = «instancia de participación efectiva»; la sumatoria de las sesiones debe ser coherente con las horas acreditadas; al menos una sesión para la Declaración Jurada; cierre opcional, «en caso de existir»).
session_valid()compara el día calendario del último inicio con el de hoy enAmerica/Santiago(course_config::TIMEZONE, independiente de la zona del servidor). Conclosesessionmarcado, una sesión cerrada exige un nuevo inicio. - Logo:
attendance::logo()va siempre en el pie del bloque cuando el estudiante con franquicia ve el bloque (no para becarios ni estudiantes sin grupo SENCE). - Los formularios de «Cursos y grupos» y «Bloque en cursos» muestran estas reglas con
ui::fixed()(no editables);course_config::save()ignora esos campos yvalidate()ya no los revisa.
4.2 Auditoría de ajustes frente a SENCE (4.0.1, actualizada en 4.0.2)
Regla: el complemento viene preconfigurado con lo que SENCE exige y no ofrece opciones que SENCE pueda rechazar.
| Ajuste | Estado | Fuente |
|---|---|---|
| Asistencia obligatoria para estudiantes con franquicia (curso tapado y actividades bloqueadas en el servidor) | Fijo | Guía participante e-learning v2.0 (2026): el registro de conexión acredita la participación y se necesita al menos una sesión para la Declaración Jurada |
| Nuevo inicio de sesión: una vez al día (día calendario, hora de Chile) | Fijo | Guía participante e-learning v2.0 (2026): la sumatoria de las sesiones debe ser coherente con las horas acreditadas |
| Logo de SENCE bajo el botón de asistencia | Fijo | Regla de Norteduc (identifica el registro oficial) |
| Direcciones de inicio y cierre (rce en producción, rcetest en pruebas) | Fijo según el ambiente de la OTEC | Manual de integración v1.1.6, §3.2, §3.3, §4, §5 y Anexo 1 |
| UrlRetoma y UrlError = /blocks/sence/retorno.php (máx. 100 caracteres; el panel avisa si se supera) | Fijo | Manual de integración v1.1.6, §3.2 (largo 100; errores 201, 202 y 203) |
| Inicio por redirección del navegador (POST) con ClaveÚnica y enlace a claveunica.gob.cl junto al botón | Fijo | Manual de integración v1.1.6, §2 y §3.2; Res. Ex. 2436 (2019) y 2584 (2021) citadas en la Guía participante e-learning v2.0 (2026) |
| Formato de parámetros: RutOtec y RunAlumno sin puntos con guion, token en mayúsculas, IdSesionAlumno firmado (menos de 149 caracteres) | Fijo (normalización automática) | Manual de integración v1.1.6, §3.2 (errores 207 y 209) |
| CodSence vacío en la línea 1; CodigoCurso = ID de acción del grupo | Fijo | Manual de integración v1.1.6, §3.2 y anexo de programas sociales |
| Mensajes de error de SENCE explicados en español | Fijo | Manual de integración v1.1.6, §3.2 y Anexo 2 |
| Zona horaria del día SENCE: America/Santiago | Fijo | Manual de integración v1.1.6, parámetro ZonaHoraria; regla «una vez al día» |
| Cronómetro de la sesión en pantalla y alerta 10 minutos antes de que termine | Fijo (nuevo en 4.0.1) | Manual de integración v1.1.6, §2 Paso 3, recomendaciones a y b |
| Tiempo de conexión: solo tiempo activo (deja de contar tras 5 minutos sin actividad; nueva conexión tras 30 minutos) | Fijo | Res. Ex. 4027 (ventana de 45 minutos entre clics; cierre por inactividad a los 60 minutos): el criterio del complemento es más estricto |
| Cierre de la sesión de Moodle por inactividad a los 60 minutos (aviso a los 55 con «Seguir conectado»), solo estudiantes con franquicia; no cierra la sesión SENCE del día | Fijo (nuevo en 4.0.2; ya no depende del «Tiempo de espera» de Moodle, que queda como información en el Resumen) | Res. Ex. 4027 (2022), Instructivo de diseño e-learning: cierre por inactividad obligatorio a los 60 minutos |
| RUT de la OTEC | Dato obligatorio, validado (dígito verificador) | Manual de integración v1.1.6, §3.2 (error 209) |
| Token de la OTEC | Dato obligatorio, validado (formato de 36 caracteres) | Manual de integración v1.1.6, §3.1 (errores 211, 212 y 303) |
| Ambiente de pruebas o de producción | Dato obligatorio | Manual de integración v1.1.6, §4 y §5 |
| Línea de capacitación (1, 3 o 6) | Dato obligatorio, solo valores del manual | Manual de integración v1.1.6, §3.2 (error 206) |
| Código SENCE (10 dígitos; vacío en la línea 1; -1 solo con OTEC en pruebas) | Dato obligatorio, validado | Manual de integración v1.1.6, §3.2 y §4 (error 204) |
| ID de acción de cada grupo (7 a 50 caracteres, menos en FPT, guiones en programas sociales; -1 solo en pruebas) | Dato obligatorio, validado | Manual de integración v1.1.6, §3.2 y §4 (error 205) |
| Vigencia del curso (fuera de fechas el bloque no se muestra) | Dato de la OTEC (opcional) | Manual de integración v1.1.6, Anexo 2 (error 309); Guía participante e-learning v2.0 (2026) |
| Estudiantes sin grupo SENCE: no franquiciados (sin asistencia, bloqueo, avisos ni cierre por inactividad) | Fijo (nuevo en 4.0.2) | Decisión de Norteduc (04-10-2026): solo quien va por franquicia tiene un ID de acción; no hay que inscribir a los demás en un grupo (cursos abiertos) |
| Grupo de becarios | Dato de la OTEC (opcional desde 4.0.2, por compatibilidad) | Regla de Norteduc: quienes no tienen franquicia no registran asistencia |
| OTEC por curso o por grupo | Dato de la OTEC | Manual de integración v1.1.6, §3.2 (RutOtec y Token en cada POST) |
| Pedir la sesión SENCE (desmarcado: solo tiempo de conexión) | Opcional; el panel avisa si el curso tiene grupos SENCE | Para cursos sin franquicia; no genera rechazos de SENCE |
| Pedir cierre de sesión | Opcional | Guía participante e-learning v2.0 (2026): el cierre se registra «en caso de existir»; no causa rechazos |
| Región y posición del bloque | Opcional | Presentación; no afecta a SENCE |
| Avisos por correo, informes por correo y envío de correos (SMTP) | Opcional (licencia) | Comunicación interna; no afecta a SENCE |
| Usuarios del panel y bitácora | Opcional (licencia) | Administración interna; no afecta a SENCE |
4.3 Filtro por OTEC (4.0.4)
- Solo con
reports::multi_otec()(dos o más filas enblock_sence_otec): parámetrootecvalidado porreports::otec_param()y agregado areports::filters()comootecid; campoui::otec_field()(select con<label for>) enui::record_filters(),students.phpeindex.php. - Pertenencia (
reports::otec_map()): curso →course_config::otec_of($cfg); grupo SENCE (no becarios, con ID de acción yotecidpropio) →course_config::otec_of($cfg, $g->otecid), guardado solo si difiere de la del curso. Registro (block_sence,block_sence_intento) →reports::otec_of_record($map, courseid, codigocurso): el ID de acción enviado a SENCE (CodigoCurso) identifica el grupo; si no coincide con un grupo de otra OTEC, la del curso. Estudiante →reports::learners()agregaotecid(grupo SENCE con OTEC propia, si no la del curso). Tiempo de conexión (sin ID de acción) →reports::otec_of_learner(). 0 = «Sin OTEC». - SQL:
reports::otec_sql($prefix, $f, $params)=(courseid IN cursos de la OTEC AND NOT (pares curso/ID de acción de otra OTEC)) OR (pares curso/ID de acción de la OTEC), conCOALESCE(codigocurso, ''); usado porsessions(),attempts(),errors()ysummary($now, $otecid). Los cursos y alumnos del Resumen y de Alumnos y RUN se filtran conotec_courses()y eloteciddel estudiante. - Columna «OTEC» (
ui::otec_cell(): razón social + RUT) enrecords.phpystudents.php;exporter::data()agrega «OTEC» y «RUT OTEC» en sesiones, intentos y alumnos y devuelve la OTEC de cada fila;exporter::by_otec()arma una hoja por OTEC (+ «Sin OTEC» si hay filas) ignorando el filtro OTEC, conexporter::sheet_name()(31 caracteres, sin: \ / ? * [ ], nombres únicos).export.php?byotec=1paraexporter::BYOTEC_TYPES; mismo control de licencia que la exportación normal (exporter::allowed()). Los informes por correo siguen filtrando por OTEC a nivel de curso (mailreports::filters()).
5. Acceso, permisos y licencia
- Página Licencia (4.0.2):
license::plan_box()muestra «Qué incluye la versión gratuita» solo sin plan de pago vigente (también cuando la anual vence y termina la gracia) y, con licencia, «Tu licencia incluye todas las funciones» con el vencimiento (anual) o «actualizaciones para siempre» (perpetua);license::show_buy()oculta «Comprar licencia» con licencia vigente (la anual lo muestra para renovar en sus últimos 30 días o en la gracia). - Gratis: solo
is_siteadmin(). Las filas deblock_sence_puserse conservan pero se ignoran. - Con licencia: secciones
credentials(nuevo: sin acceso),courses,block,records(nuevo: solo lectura) ynotices(nuevo: sin acceso). Cada página llama apanel::require_section()y cada acción vuelve a exigir «edit» en la clase. Usuarios, bitácora, licencia y cuenta SMTP: solo administrador. - Producto
sence, mayor de licencia 3: los códigos de 3.x siguen valiendo (perpetuos). Funciones de pago: varias OTEC, exportaciones (salvo la de asistencia), avisos, usuarios, bitácora. Desde 4.0.1exporter::FREE_TYPES = ['sessions']: la exportación de la asistencia enviada es gratuita;export.phpexige licencia solo siexporter::allowed()es falso yui::export_button()muestra el candado en las demás. - Licencia anual: aviso en el panel desde 30 días antes (
NOTICE_DAYS) y correo aget_admins()(usuario noreply, SMTP de Moodle) 30, 15, 3 y 1 día antes (REMINDER_DAYS), evaluado por la tarea diariacheck_updates: una vez por hito y por fecha de vencimiento (nlic_reminded_<exp>_<n>en la configuración del bloque), solo el hito más cercano si se saltaron, y de nuevo al renovar con otraexp. 15 días de gracia (GRACE_DAYS) y luego versión gratuita sin borrar datos.
6. Tareas, ganchos y eventos
\block_sence\task\send_notices(cada hora; actúa una vez al día a la hora elegida),\block_sence\task\send_reports(cada hora;mailreports::run()envía cada informe una vez por período:due_period()da Daaaammdd, Waaaa-ss o Maaaamm en hora de Chile y se guarda enmreports_lastperiodantes de enviar) y\block_sence\task\check_updates(diaria).- Correo:
transport::send($to, $subject, $html, $attachments)(correo de Moodle conemail_to_user()o SMTP propio conmoodle_phpmailer; adjuntos ruta ⇒ nombre). Páginamail.php(solo administrador): transporte, contraseña oculta y «Probar conexión» (registrokind=smtptestenblock_sence_mail). Informes:mailreports.php, ajustesmreports_*, historialkind=report|reporttest; adjuntoexporter::to_file('sessions', …)(PhpSpreadsheet a un archivo temporal que se borra tras enviar). - Gancho
before_footer_html_generation: además del contador,attendance::clock()cargablock_sence/sessionclockpara quien tiene franquicia y la sesión del día (tiempo restante hasta la medianoche de Chile o el fin de la vigencia, alerta a los 10 minutos). - Tablas:
block_sence/tables(30 filas por página, «Anterior»/«Siguiente», búsqueda y orden sobre todas las filas, casillas ordenables; las filas de otras páginas quedan ocultas en el DOM, así las casillas se envían con el formulario). Componente pensado para copiarse a los demás complementos (data-perpage, data-prev, data-next, data-pageof, data-results, data-pagerlabel en el contenedor). - Gancho
primary_extend: entrada «SENCE» en el menú principal solo para quien puede usar el panel. Ganchobefore_footer_html_generation: cargablock_sence/heartbeaten páginas de cursos SENCE vigentes para alumnos matriculados;ajax.phpnunca acredita más segundos que los transcurridos. - Evento
\block_sence\event\settings_changeden cada cambio de configuración.
7. Privacidad
classes/privacy/provider.php declara y exporta/borra sesiones, intentos, tiempo de conexión y avisos (contexto de curso) y usuarios del panel, bitácora y avisos recibidos (contexto de sistema). Declara también la transferencia a SENCE y al SMTP y, desde 4.0.2, las preferencias de usuario del cierre por inactividad (user_preference_provider).
8. Pruebas y simulador
- PHPUnit:
vendor/bin/phpunit --testsuite block_sence_testsuite(pruebas en Moodle 4.5 y 5.0, incluidas las de las reglas fijas: bloqueo y desbloqueo del grupo SENCE, becarios nunca, logo siempre, sesión válida el mismo día y nueva el día siguiente; desde 4.0.2, estudiantes sin grupo y cursos sin grupos SENCE sin bloqueo, cierre por inactividad solo para franquiciados y caja de licencia según el plan; desde 4.0.4, filtro y descarga por OTEC:reports_test::test_otec_filter). Pruebas de extremo a extremo:dev/e2e_sence401.py(Playwright;--solo402para lo de 4.0.2, con el reloj de inactividad acelerado a 60 segundos solo durante esa parte). - Simulador local de SENCE (solo desarrollo, fuera del ZIP):
dev/mock_sence.php. En elconfig.phpdel Moodle de pruebas:$CFG->block_sence_rce_urls = ['start' => '…/dev/mock_sence.php?op=start', 'close' => '…?op=close'];. Permite aceptar o rechazar (208, 212, 100;210) sin llamar a SENCE con datos de personas. - Conjunto ficticio para un aula demo:
php blocks/sence/dev/seed_demo.php --otecrut=<RUT de la OTEC de pruebas>(crea la categoría «Pruebas SENCE», 4 cursos más uno abierto sin grupos SENCE, 36 alumnos más 2 sin grupo y 1 comprador del curso abierto, con RUN inventados desde 30.100.001, 2 docentes y 30 días de historial; no lee el token) y--reset(borra solo lo que creó: su categoría, sus usuarios marcados con departamento SEED-SENCE y sus filas). Pruebas de extremo a extremo con Playwright sobre el simulador. - Datos 3.x ficticios para probar la actualización:
dev/seed_3x.php. Cadenas:dev/strings.pygeneralang/esylang/enordenadas.