15 KiB
CHANGELOG / Registro de cambios — Plataforma LMS Estafeta
Documento de seguimiento para agentes. Describe todos los cambios de esta iteración, el estado actual verificado del repositorio, archivos a subir a producción y pendientes.
Ruta local trabajada:
C:\backup\htdocs\gitlab\EstafetaRuta de ejecución del usuario (dev):C:\xampp\htdocs\git\Estafeta-plataformProducción:https://induccion-estafeta.com.mx/Rama/base: sin control de versiones (copia de htdocs).
0. Contexto y decisiones
- Login: por decisión del negocio se ingresa solo con número de empleado. No se implementó verificación de contraseña (omitido a propósito). Aun así se endureció la sesión y se agregó rate-limit.
- RTE: se decide usar únicamente
runtime/y eliminarruntimev2/. - SCORM: el RTE central
runtime/sco.phpsirve un contenedor con barra superior + botón "Regresar" e iframe que carga el paquete, con el API SCORM en la página padre (así Storyline/Rise lo encuentran subiendo por los frames). - Los endpoints de
admin/(y los del front que devuelven JSON) siempre responden JSON; nunca deben emitir HTML de avisos/errores.
1. Resumen por fases
Fase 1 — Seguridad base
admin/auth_guard.php(nuevo): valida sesión + rol admin; responde{"code":"403","msg":"No autorizado"}si no. Fuerzadisplay_errors=0y descarta salida previa (buffering) para no contaminar el JSON.- Guard añadido a 113 endpoints
admin/*.phpconrequire_once __DIR__.'/auth_guard.php';(se excluyenadmin/index.phpyadmin/audit.php, que son apoyo, no endpoints). data/.htaccess(nuevo):Options -Indexes+ bloqueo de ejecución de scripts (los SCORM son estáticos → evita RCE/XSS por archivos subidos).runtime/sco.php:display_errors/display_startup_errorsen0; se quitóallow_url_include.- Backups eliminados:
admin/index.php.back,admin/usuarios.php.back,admin/usuarios.bak.php,admin/usuarios.bak.html,admin/usuarios_manager.bak.php,runtime/getUserDashBoard.php.back,runtime/runtime.js.back, carpetaadmin/backup100322/.
Fase 2 — Endurecimiento
config.php: endurecimiento de sesión (session.use_strict_mode,use_only_cookies,cookie_httponly,cookie_samesite=Lax,cookie_securesi HTTPS). Solo aplica si no hay sesión activa.runtime/newlogin.php: reescrito con consultas preparadas,session_regenerate_id(true)(anti-fijación) y rate-limit por IP (60/10 min). Mantiene login por número de empleado.admin/usuarios_add.php/admin/usuarios_upadate.php: convertidos a sentencias preparadas.- CSRF: token en
admin/index.php(<meta name="csrf-token">), envío global enjs/admin.js(X-CSRF-Token+window.CSRF_TOKEN), verificación enauth_guard.phppara POST/PUT/DELETE, y cabeceraX-CSRF-Tokenañadida a todos los uploads Uppy. runtimev2/api.php: corregido bug=→==enLMSInitialize(comparación delesson_status/score.raw).
Fase 3 — Mantenibilidad y calidad
- N+1 eliminado en
runtime/getUserDashBoard.php: los hijos se traen con una sola consultaWHERE padre IN (...). - Auditoría:
admin/audit.php(nuevo) +logs/.htaccess(nuevo). Registra enlogs/admin.log: fecha, IP, userid, numempl, archivo, acción, detalle. - Sentencias preparadas + auditoría en:
admin/categorias_add.php,categorias_upadate.php,categorias_delete.php,recursos_add.php,recursos_update.php,recursos_delete.php,gamification_add.php,gamification_delete.php. Los*_deletesanitizan ids conintval. runtimev2/eliminada (sin referencias en el proyecto).
Correcciones posteriores (vistas admin + dashboard)
- polyfill.io eliminado de
footer.phpyruntime/rte.php(dominio comprometido en 2024; causaba 404/CORS y rompía DataTables). JSON.parseinseguro: se añadió helperparseJSON(resp)(try/catch) y se reemplazaron todos losJSON.parse(response)en:admin/usuarios.html,categorias.html,asignaciones.html,constancias.html,encuestas.html,evaluaciones.html,gamification.html,licencia.html,recursos.html.admin/usuarios.phpyadmin/usuarios_manager.php: reescritos como DataTables server-side limpio y con sentencias preparadas; se eliminó el cifrado roto de la consulta y la propiedad"query"que rompía eldrawCallback(Cannot read properties of null (reading 'query')).admin/usuarios.html: quitadas asignacionesquery = json.query; reload conajax.reload(null, false).admin/usuarios_*_list.php(8):mysqli_fetch_array→mysqli_fetch_assoc(evita claves numéricas duplicadas en el JSON).admin/exportUsers.php: exporta según rol (roladmin), sin depender delquerycifrado;exportar()envía?roladmin=.runtime/getUserDashBoard.php: blindado como endpoint JSON (display_errors=0,Content-Type: application/json, valida sesión y responde{"code":"0","msg":"Sesión no válida"}en lugar de HTML).dashboard.php: parseo tolerante de la respuesta (try/catch) con mensaje amigable.config.php:ini_setde sesión solo si no hay sesión activa (evita warnings).
2. Archivos NUEVOS
| Archivo | Propósito |
|---|---|
admin/auth_guard.php |
Guard de autenticación admin + verificación CSRF |
admin/audit.php |
Función audit_log() → escribe logs/admin.log |
admin/recursos_scorm_upload.php |
Subida de .zip SCORM: valida, descomprime en data/scormpkg/<categoria>/ y devuelve la URL de inicio |
data/.htaccess |
Options -Indexes + bloqueo de ejecución de scripts en data/ |
logs/.htaccess |
Protección de la carpeta de auditoría |
README.md |
Documentación general del proyecto (stack, funcionalidades, instalación, seguridad) |
CHANGELOG.md |
Este documento |
3. Archivos MODIFICADOS (por área)
Configuración
config.php— hardening de sesión (condicional).
Runtime / RTE
runtime/gotorte.php— lanzador central; cargaruntime/sco.phpconrequire(sin redirect) y respaldo alsco.phpdel paquete.runtime/sco.php— contenedor con barra + botón Regresar, API en el padre, iframe con cache-busting, cierre de BD,display_errors=0.runtime/commit.php—mysqli_close($link).runtime/finish.php—mysqli_close($link).runtime/runtime.js—pagehideen lugar dewindow.onunload.runtime/getUserDashBoard.php— N+1 resuelto + endpoint JSON blindado.runtime/newlogin.php— consultas preparadas + anti-fijación + rate-limit.runtime/rte.php— removido polyfill.io.
Front (alumno)
dashboard.php— scroll/resaltado de categoría al volver (?idcat=), parseo tolerante del dashboard.recursos.php— botón "Regresa al menú anterior" →dashboard.php?idcat=<cat>.footer.php— removido polyfill.io.
Admin — endpoints
admin/usuarios.php,admin/usuarios_manager.php— server-side limpio + preparadas.admin/usuarios_add.php,admin/usuarios_upadate.php— preparadas + auditoría.admin/categorias_add.php,admin/categorias_upadate.php,admin/categorias_delete.php— preparadas + auditoría.admin/recursos_add.php,admin/recursos_update.php,admin/recursos_delete.php— preparadas + auditoría.admin/gamification_add.php,admin/gamification_delete.php— preparadas + auditoría.admin/exportUsers.php— export por rol sin query cifrado.admin/usuarios_{centros,distritos,posiciones,sociedades,divisiones,unidades,ceco,supervisores}_list.php—fetch_assoc.admin/categorias.php— normalización de imagen (assetUrlinline) +'Raíz'.admin/usuarios.php/recursos.php(endpoints de tabla) — columnas/normalizaciones.- 113
admin/*.php— línea del guard añadida.
Admin — vistas (HTML)
admin/usuarios.html,categorias.html,asignaciones.html,constancias.html,encuestas.html,evaluaciones.html,gamification.html,licencia.html,recursos.html— helperparseJSON, CSRF en Uppy, preselección por id en selects.admin/index.php— meta CSRF.
JS
js/admin.js—window.CSRF_TOKEN+$.ajaxSetup({ headers: { 'X-CSRF-Token': csrf } }).
Data (paquete de ejemplo)
data/scormpkg/Premio_Nacional/index.html— busca API subiendo al padre y redirige a Genially.data/scormpkg/Premio_Nacional/sco.php,imsmanifest.xml, XSDs — paquete creado.
4. Archivos ELIMINADOS
- Carpeta
runtimev2/(completa). admin/index.php.back,admin/usuarios.php.back,admin/usuarios.bak.php,admin/usuarios.bak.html,admin/usuarios_manager.bak.php.runtime/getUserDashBoard.php.back,runtime/runtime.js.back.- Carpeta
admin/backup100322/.
5. Estado verificado del repositorio
runtimev2/→ no existe. Sin referencias en el proyecto.admin/auth_guard.php,admin/audit.php,data/.htaccess,logs/.htaccess,README.md→ existen.- Endpoints
admin/*.phpcon guard: 113. Sin guard: soloaudit.php(helper) eindex.php(layout). - No quedan archivos
*.back/*.bak(fuera devendor/ydata/). php -lsin errores en los archivos tocados.- Pruebas con BD temporal (
estafeta_lms2) realizadas y eliminadas al terminar: login, dashboard (con/sin sesión), usuarios server-side, listas, CRUD categorías/recursos con auditoría.
6. Instrucciones para subir a producción (resumen)
Obligatorio subir:
config.php,footer.php,dashboard.php,recursos.phpruntime/completo (gotorte, sco, commit, finish, runtime.js, getUserDashBoard, newlogin, rte)admin/completo (incluyeauth_guard.php,audit.php,recursos_scorm_upload.php, todos los.phpcon guard y todos los.html)js/admin.jsdata/.htaccesslogs/(carpeta con.htaccess)data/scormpkg/Premio_Nacional/
Requisitos del servidor:
- Permisos de escritura en
data/,data/scormpkg/,logs/. ZipArchivehabilitado en PHP.upload_max_filesize/post_max_sizeacordes al tamaño de los SCORM.
NO subir / ya no aplica:
runtimev2/(eliminada).config.phpya no contieneassetUrl()(se quitó).
7. Pendientes / recomendaciones (no bloqueantes)
- Credenciales: mover
config.phpa variables de entorno y rotar la clavewwserv_estafeta(aparece comentada en el archivo). Hoy sigue en texto plano. - SQL restante: migrar a sentencias preparadas los endpoints de
constancias,encuestas,evaluaciones,licenciay otros CRUD que aún interpolan SQL. - Contraseñas: la columna
passwordusagetEncrypt()(no-op). Solo hashear si algún día se habilita login con contraseña. session.save_pathdel.htaccessapunta aea-php73; revisar con el PHP real en producción.- polyfill.io dentro de
data/scormpkg/**: los paquetes antiguos aún lo referencian, pero no se usan (los sirve el RTE central). Limpiar opcionalmente. - Tests automatizados de flujos críticos (login, SCORM, alta de recursos).
- Consolidación de RTE ya resuelta con la eliminación de
runtimev2/; validar en producción el lanzamiento de Storyline/Rise/NOM-035.
8. Notas de troubleshooting
Fatal error: Unknown database 'estafeta_lms2'(local): es solo del entorno local (falta la BD). En producción existewwserv_estafeta. Opciones: crear/importar la BD local o hacerdbConnect()tolerante.Unexpected token '<' ... is not valid JSON: indica que un endpoint devolvió HTML (error PHP). Con los cambios, los endpoints JSON ya no deben emitir HTML; si reaparece, revisar en DevTools → Network la respuesta del endpoint y confirmar que el archivo subido es el nuevo.polyfill.ioCORS/404: dominio comprometido; ya removido del proyecto.- Rutas RTE:
runtime/sco.phpusa rutas raíz ($_SERVER['DOCUMENT_ROOT']."/sessionManager.php"). Si el LMS se sirve en subcarpeta, ajustar a/gitlab/estafeta/....
9. Corrección: vista de Usuarios (500 por columnas faltantes)
Síntoma: POST admin/usuarios.php, usuarios_{centros,distritos,posiciones,sociedades,divisiones,unidades,ceco,supervisores}_list.php → 500 (Internal Server Error).
Causa raíz: la BD local estafeta_lms2 conserva el esquema antiguo de user
(solo centrotrabajoid / distritoid con FK a centrotrabajo / distrito).
El código actual lee/escribe columnas denormalizadas en user
(centrotrabajonom, distritonom, posicion, sociedad, division,
unidad_organizativa, ceco, supervisor). En PHP 8.1+ mysqli lanza
mysqli_sql_exception ante Unknown column, que antes terminaba en un fatal
sin control (HTTP 500). En PHP 7.4 solo devolvía resultados vacíos.
Cambios:
bd/migration_usuarios_org_fields.sql(nuevo): agrega las columnas faltantes auser(idempotente) y rellenacentrotrabajonom/distritonomdesde las FKcentrotrabajo/distrito. Aplicar en cada entorno.admin/auth_guard.php:set_exception_handlerque descarta la salida previa y responde JSON ({"code":"500",...}) en cualquier excepción no controlada, en lugar de HTML; registra el detalle enerror_log.
Verificación local: los 10 endpoints de usuarios + usuarios.php /
usuarios_manager.php responden JSON con datos; centros y distritos se
rellenan desde las FK.
Pendiente del entorno: completar la BD local (faltan tablas licencia,
evaluaciones, encuestas, constancias, etc.); usuarios_statistical.php
falla por Table 'estafeta_lms2.licencia' doesn't exist (ahora responde JSON 500
en vez de fatal).
10. Corrección: traducción de DataTables (español)
Síntoma: las tablas de recursos, categorias, gamification (y otras del
panel) mostraban la interfaz de DataTables en inglés (Search, Show entries,
Previous/Next, "No data available", etc.).
Causa raíz: las vistas cargaban el idioma desde un CDN externo
(https://cdn.datatables.net/plug-ins/.../i18n/Spanish.json). Cuando el CDN no
está disponible (sin internet, bloqueado o CORS), DataTables cae a los textos por
defecto en inglés. usuarios.html ya usaba la copia local y sí se traducía.
Cambios: se reemplazó la URL del CDN por la copia local ../json/Spanish.json
(compatible con DataTables 1.10.20 incluida en js/datatables.min.js) en:
admin/categorias.html,gamification.html,recursos.html,constancias.html,encuestas.html,evaluaciones.html,reporte_categoria.html,reporte_curso.htmladmin/reporte_categoria.php,reporte_curso.php,reporte_distrito.php,reporte_categoria_completadav2.php,reporte_categoria_visitadosv2.php
Verificación: http://localhost/git/LMS-Estafeta/json/Spanish.json responde
HTTP 200; ya no quedan referencias a cdn.datatables.net/plug-ins en admin/.