NPM / ANALYTICS
Analytics con NPM
Integración NPM paso a paso: generación de resultados, identificación del alumno, entrega de archivos, índices estáticos y consulta en Analytics, sin base de datos.
1. Instalar Player y Analytics con NPM
Player abre los cursos; Analytics lee sus archivos de resultados. Los dos paquetes usan el contrato común .p2lrn-result, por lo que la aplicación no debe inventar un conversor de métricas.
npm install @p2lrn/player @p2lrn/analyticsComprueba la compatibilidad de las versiones de Player y Analytics. El paquete @p2lrn/analytics exporta además los auxiliares de lectura desde @p2lrn/analytics/results.
Disponibilidad: las funciones nuevas de src, el capturador de resultados y la importación múltiple existen en la rama de rediseño. Aún no se ha confirmado su publicación en NPM/CDN. Instalar los paquetes publicados actualmente no garantiza estas funciones; utilízalas con las compilaciones compatibles cuando se publiquen y validen.
2. Entender el flujo completo antes de integrar
- El profesor prepara el curso
.p2lrny define la identificación: sesión autenticada de la plataforma o código individual. - El alumno abre el curso en Player y realiza un intento. El capturador de resultados debe configurarse antes de la actividad.
- Al finalizar, Player genera el archivo
.p2lrn-result. Se guarda o descarga mediante una acción explícita. - El archivo llega al profesor mediante una devolución manual (correo, USB, carpeta autorizada) o una función de almacenamiento de archivos implementada por la plataforma.
- Analytics lo recibe por importación local o por una URL de archivo/índice en
src, verifica su SHA-256 y calcula las métricas. - Cuando llegan nuevos resultados, se importan o se añaden al índice y se actualiza el panel.
Ni el paquete del curso contiene después las respuestas de todos los alumnos, ni el capturador las envía por sí solo a un servicio remoto. No se necesita una base de datos para este flujo basado en archivos.
3. Leer un .p2lrn-result directamente con src
Crea un elemento para Analytics:
<p2lrn-analytics id="panel.analytics"></p2lrn-analytics>Después, desde el código de tu aplicación:
import '@p2lrn/analytics';
// Select by HTML id; the dot is part of the id, NOT a CSS class.
const panel = document.getElementById('panel.analytics');
panel.src = '/resultados/resultado-001.p2lrn-result';src es la ruta HTTP(S) del archivo de resultado. No debe ser la ruta del curso .p2lrn. id="panel.analytics" identifica el componente HTML; no indica el curso ni el alumno. Para localizarlo con JavaScript usa getElementById('panel.analytics'), ya que el punto requiere escape si empleas selectores CSS.
El navegador debe poder leer el archivo con los permisos correspondientes. Un archivo privado en el ordenador del profesor se abre mediante importación local, no mediante una ruta HTTP ficticia.
4. Generar resultados sin plataforma: código del profesor
El profesor asigna un código único y estable a cada alumno. El alumno introduce ese código antes de empezar y Player lo incorpora en la identidad declarada del resultado. El ejemplo incluye los controles HTML y la configuración de Player:
<label for="codigo-alumno">Código individual del alumno</label>
<input id="codigo-alumno" required autocomplete="off">
<button id="iniciar" type="button">Comenzar curso</button>
<div id="reproductor"></div>
<button id="finalizar" type="button" hidden>Descargar resultado</button>import {mountP2LRN} from '@p2lrn/player';
let player;
document.getElementById('iniciar').addEventListener('click', async () => {
const input = document.getElementById('codigo-alumno');
const code = input.value.trim();
if (!code) { input.reportValidity(); return; }
player = await mountP2LRN('#reproductor', {
src: '/cursos/curso.p2lrn',
resultIdentity: {mode:'code', issuer:'mi-centro', code}
});
document.getElementById('finalizar').hidden = false;
});
document.getElementById('finalizar').addEventListener('click', () => {
if (player?.resultRecorder)
player.resultRecorder.download('resultado.p2lrn-result');
});El botón descarga el archivo en el navegador del alumno. Ese archivo no se envía automáticamente al profesor: debe entregarse por el medio acordado. Un nuevo intento debe generar un nuevo resultado con otro identificador de intento, manteniendo el mismo código de alumno.
Los códigos deben ser distintos entre alumnos del mismo emisor. El profesor debe conservar su propia correspondencia entre código y persona, porque el código por sí solo no demuestra la identidad real.
5. Generar resultados con usuario autenticado
Si la web o LMS ya ha iniciado sesión, utiliza la identidad autenticada real de esa aplicación. issuer identifica la plataforma y subjectId al usuario de forma estable. El método de obtención de sesión del ejemplo es de tu propia aplicación, no una función incluida en P2LRN.
import {mountP2LRN} from '@p2lrn/player';
// Authenticated session data from YOUR host app (not a P2LRN API).
const session = await miPlataforma.obtenerSesionAutenticada();
const player = await mountP2LRN('#reproductor', {
src: '/cursos/curso.p2lrn',
resultIdentity: {
mode: 'platform',
issuer: session.issuer,
subjectId: session.userId,
label: session.displayName
}
});
// After the learner finishes (e.g. from a button):
const result = await player.resultRecorder.finish();
// Your host may save this unchanged in protected file storage.
// Do not treat the identity claim as proof without host checks.La plataforma debe comprobar permisos y asociar el archivo al usuario autenticado si lo guarda. mode:'platform' dentro del JSON no equivale por sí solo a una firma o una prueba de identidad. La validación de calificaciones oficiales corresponde al sistema autorizado, no al archivo local.
6. Recibir varios resultados localmente, sin servidor
Cuando los alumnos devuelven archivos por USB, correo o una carpeta autorizada, el profesor puede importarlos en una sola operación. El panel también ofrece los botones Abrir resultados y Abrir carpeta (según el navegador). Programáticamente:
<p2lrn-analytics id="panel.analytics"></p2lrn-analytics>
<input id="ficheros" type="file" accept=".p2lrn-result" multiple>
<button id="importar" type="button">Importar archivos</button>import '@p2lrn/analytics';
document.getElementById('importar').addEventListener('click', async () => {
const files = [...document.getElementById('ficheros').files];
if (!files.length) return;
const panel = document.getElementById('panel.analytics');
await panel.importOfflineResults(files); // Verifies SHA-256 and merges attempts.
});importOfflineResults(files) verifica los archivos, descarta duplicados idénticos y reúne los intentos. Si un archivo de la selección falla la comprobación, la operación se rechaza. Los resultados permanecen en la memoria del panel hasta que se vuelvan a importar; Analytics no los almacena automáticamente de forma permanente.
7. Varios alumnos desde una URL: índice estático
Para consultar los archivos sin seleccionarlos manualmente, almacénalos en un directorio web protegido y crea un índice:
resultados/
resultado-001.p2lrn-result
resultado-002.p2lrn-result
index.p2lrn-results.json{
"schema": "https://p2lrn.org/schema/analytics-results-index/1",
"results": [
"resultado-001.p2lrn-result",
"resultado-002.p2lrn-result"
]
}El array results contiene rutas relativas al índice. Mantén el mismo origen web para sus archivos y el índice, y un máximo de 500 resultados por carga.
// Same component, multiple result files through a static index:
panel.src = '/resultados/index.p2lrn-results.json';También puedes obtener el dataset ya calculado con la API auxiliar:
import {loadAnalyticsResultSource} from '@p2lrn/analytics/results';
const dataset = await loadAnalyticsResultSource(
'/resultados/index.p2lrn-results.json'
);
document.getElementById('panel.analytics').data = dataset;El índice no almacena notas ni respuestas; únicamente enumera los archivos de resultados. Si un archivo nuevo no está en el índice, Analytics no lo encontrará al consultar esa URL.
8. Crear y actualizar el índice: manual o con Node
Actualización manual
- Guarda cada archivo recibido en el directorio sin sobrescribir intentos antiguos; utiliza nombres de archivo distintos.
- Crea
index.p2lrn-results.jsoncon el JSON anterior, o abre el índice que ya existe. - Agrega al array
resultslos nombres de los archivos nuevos y conserva los anteriores. - Guarda el JSON, actualiza los archivos en tu alojamiento autorizado y pulsa Actualizar en Analytics. Revisa la caché si aún aparece el índice antiguo.
Actualización automática desde Node
Descarga el script generador del índice y guárdalo junto al directorio de resultados. Ejecútalo tras recibir nuevos archivos:
node p2lrn-results-index.mjs ./resultadosEl script funciona sin dependencias NPM ni base de datos: examina archivos directos .p2lrn-result, verifica esquema y SHA-256, ordena los nombres y reemplaza el índice completo. Si encuentra resultados inválidos, no actualiza el índice. No los envía a Internet. El paso de distribuir los archivos al profesor o publicarlos con acceso restringido sigue siendo responsabilidad de quien los administra.
9. Dónde se conservan los archivos y cómo llegan al profesor
Los alumnos descargan sus .p2lrn-result y los entregan. El profesor guarda los archivos en su ordenador y los importa. No requiere índice, servidor ni base de datos.
Se copian archivos e índice a una carpeta protegida del alojamiento web. Analytics consulta esa URL. La aplicación no recibe ni descubre resultados automáticamente.
La aplicación puede aceptar la entrega de un archivo, comprobar el usuario y guardar el resultado íntegro en almacenamiento privado de archivos. Debe implementar su propio flujo autorizado de recepción, actualización del índice y acceso de profesores. No hace falta convertir el archivo ni instalar una base de datos por obligación.
Un servidor estático público o un CDN de JavaScript no garantiza privacidad. No publiques datos personales de alumnos en un directorio sin autorización. Para importaciones completamente offline, necesitas distribuir los recursos del panel localmente: el CDN por Internet no funciona desconectado.
10. Integridad, agrupación e incidencias
Analytics relaciona intentos utilizando la identidad del resultado, assignment.packageId y attempt.id. Con plataforma agrupa por emisor e ID de usuario; sin plataforma, por emisor y código. Si dos alumnos usan el mismo código, pueden aparecer como uno. Los archivos heredados sin identidad explícita pueden resultar ambiguos.
SHA-256 permite comprobar que el contenido coincide con su digest, pero no es una firma digital y no prueba quién respondió. Un archivo modificado puede rechazarse; una persona con acceso podría recomputar el hash. Las puntuaciones obtenidas del navegador son informativas hasta validación oficial.
Comprueba la recepción del archivo, su inclusión en el índice, la recarga del panel y la caché del alojamiento.
Verifica ruta, mismo origen del índice y sus entradas, sesión autenticada y permisos de almacenamiento.
Solicita el resultado original. No modifiques manualmente el JSON.
Selecciona archivos locales e instala/aloja localmente los recursos de Analytics si necesitas funcionar completamente offline.
11. Integración avanzada opcional
Si tu plataforma ya proporciona un JSON autorizado de métricas, puedes utilizarlo desde src, panel.data o panel.dataSource. No se exige un endpoint para trabajar con archivos .p2lrn-result.
// Advanced integration only, if an authorized Host already provides metrics:
panel.src = '/api/p2lrn/analytics/snapshot';
// Or: panel.data = dataset;
// Or: panel.dataSource = source;Consulta la guía CDN con el flujo completo, la referencia de métricas y la entrega offline para casos específicos.
12. Player y Analytics en una plataforma propia
Los alumnos autenticados no descargan ni envían resultados. Player conserva automáticamente el mismo formato .p2lrn-result con estado in_progress y después submitted en un directorio privado del servidor. Analytics lee un índice protegido por permisos docentes, separado de los cursos .p2lrn y sin base de datos adicional.
import {createPlayerAnalyticsSession,createHttpResultHost} from '@p2lrn/player';
const host=createHttpResultHost({baseUrl:'/api/p2lrn/results',csrfToken:()=>plataforma.csrfToken});
const player=document.createElement('p2lrn-player');
const session=await createPlayerAnalyticsSession(player,{identity:{mode:'platform',issuer:usuario.issuer,subjectId:usuario.id},courseId:'ID_REAL_DEL_CURSO',host});
// Attach the Host before Player mounts so saved activity states are restored.
player.setAttribute('src','/cursos/curso.p2lrn');
document.querySelector('#player').replaceChildren(player);
// Llama a session.finish() cuando el alumno termine el curso.const panel=document.getElementById('panel.analytics');
panel.src=host.indexUrl('ID_REAL_DEL_CURSO');
await panel.refresh();El backend de referencia se importa mediante @p2lrn/player/server/results. El integrador debe proporcionar authenticate, authorize y checkCsrf para comprobar la identidad del alumno y sus permisos, así como disponer de una carpeta privada y persistente con escritura. Ni el directorio del curso ni la descarga manual intervienen. Si no se puede guardar, Player recibe un error en lugar de confirmar el resultado. El código del Host debe rechazar datos de usuarios diferentes y no publicar los resultados como archivos públicos.
Consulta la especificación spec/RESULTS-ANALYTICS.md del repositorio. Estas APIs están en el código de integración; comprueba que la versión publicada de NPM/CDN incluya el contrato antes de utilizarlo.