SEGUIMIENTO / MÉTRICAS
Métricas, progreso y analítica
P2LRN separa el contenido portable de los datos de seguimiento. El Player expone estado, puntuaciones, eventos y progresión; la plataforma integradora decide qué datos persistir y cómo relacionarlos con usuarios, intentos, informes y sistemas de analítica.
1. Fuentes de datos de seguimiento
Durante la reproducción, P2LRN expone cuatro tipos de información con finalidades diferentes: estado de reanudación, puntuaciones, eventos y progresión.
loadState y saveState sirven para reanudar una actividad exactamente donde estaba. Es un snapshot reemplazable, no un historial.
reportScore entrega una puntuación normalizada cuando la actividad tiene una nota utilizable.
emitEvent recibe el flujo cronológico p2lrn:*. Es la fuente adecuada para analítica, auditoría y series temporales.
El Player mantiene un estado separado con viewed, completed, passed y scorePercent para aplicar las reglas del curso.
Host Adapter y los eventos p2lrn:* son las interfaces previstas para seguimiento y persistencia. El HTML visible del Player y el contenido interno de los iframes SCORM/H5P forman parte de la presentación, no del contrato de datos.
2. Arquitectura de integración
El Player permanece independiente de la base de datos del host. La aplicación integradora aporta la identidad del usuario y del intento; Host Adapter relaciona esos datos privados con los identificadores portables expuestos por P2LRN.
Identidad, matrícula, grupo, convocatoria e intento pertenecen a la plataforma integradora. No se almacenan dentro del .p2lrn y P2LRN no impone su modelo de datos.
3. Contexto e identificación de actividades
El context del Player utiliza identificadores del paquete y del grafo. Su combinación permite asociar cada dato de seguimiento con la versión exacta del contenido sin introducir identificadores privados del LMS en el paquete.
packageIdIdentidad lógica del paquete.
contentRootHuella de integridad del contenido. Cambia cuando cambia el contenido y evita restaurar estado sobre otra revisión por accidente.
entityIdActividad, unidad o raíz a la que pertenece el dato.
resourceIdRecurso asociado a la actividad cuando existe.
representationIdRepresentación concreta que está ejecutando el Player.
rendererIdPresentación/runtime elegido para esa representación.
profile / runtimeAlgunos estados de sistema, como progresión, identifican su superficie mediante estos campos.
Una clave de persistencia robusta suele combinar learnerId y attemptId de la plataforma con packageId, contentRoot, entityId y la superficie (rendererId, profile o runtime).
4. Integración mediante Host Adapter
El siguiente ejemplo separa los datos privados de la plataforma del contrato portable de P2LRN. currentUser, currentAttempt y learningApi pertenecen a la aplicación integradora; el Player sólo invoca las funciones definidas por Host Adapter.
const session = Object.freeze({
learnerId: currentUser.id,
attemptId: currentAttempt.id,
});
function metricKey(context) {
return {
learnerId: session.learnerId,
attemptId: session.attemptId,
packageId: context.packageId,
contentRoot: context.contentRoot,
entityId: context.entityId ?? null,
surface:
context.profile ??
context.rendererId ??
context.runtime ??
'package',
};
}
const adapter = P2LRN.createHostAdapter({
async loadState(context) {
return learningApi.loadState(metricKey(context));
},
async saveState(state, context) {
await learningApi.saveState(metricKey(context), state);
},
async reportScore(score, context) {
await learningApi.saveScore(metricKey(context), score);
},
async emitEvent(event, context) {
await learningApi.appendEvent({
...metricKey(context),
event,
});
},
});
document.querySelector('p2lrn-player').adapter = adapter;Tokens, endpoints privados e identidad del usuario deben permanecer fuera del paquete. La API de la plataforma debe autenticar y autorizar cada operación de persistencia.
5. Contrato de los callbacks
loadState(context)Se llama al abrir una actividad o la progresión. Devuelve el último snapshot compatible o null. No es un evento analítico.
saveState(state, context)Recibe el estado completo que permite reanudar. Debe sustituir el snapshot anterior de esa misma clave lógica.
reportScore(score, context)Recibe una puntuación estructurada. Puede invocarse más de una vez en contenidos que realizan commits sucesivos; la política de «última», «mejor» o «por intento» pertenece a la plataforma integradora.
emitEvent(event, context)Recibe el sobre canónico del evento. Las llamadas se serializan en orden por Adapter; es la vía recomendada para un log analítico.
6. Estructura canónica de los eventos
Todos los eventos que pueden persistirse comparten la misma envoltura. El payload cambia según el tipo de evento y context permite atribuirlo a la actividad correspondiente.
{
"schema": "https://p2lrn.org/schema/runtime-event/1",
"version": "1",
"type": "p2lrn:score",
"timestamp": "2026-10-16T08:30:00.000Z",
"context": {
"packageId": "urn:uuid:...",
"contentRoot": "sha256:...",
"entityId": "urn:uuid:...",
"resourceId": "urn:uuid:...",
"representationId": "urn:uuid:...",
"rendererId": "https://p2lrn.org/renderer/questionnaire/standard"
},
"payload": {
"graded": true,
"raw": 8,
"max": 10,
"normalized": 0.8
}
}En listeners DOM, los campos del payload también aparecen directamente en event.detail y la envoltura completa está disponible en event.detail.event. En HostAdapter.emitEvent() se recibe directamente el evento canónico.
7. Datos disponibles en cuestionarios y juegos
Los cuestionarios y los juegos basados en cuestionario comparten la misma sesión. El contrato permite obtener respuestas, avance, tiempo, intentos, puntuación final y detalle por pregunta sin depender de la interfaz visible.
p2lrn:answerIncluye questionId y la respuesta normalizada de esa pregunta.
p2lrn:progressIncluye value de 0 a 1, answered y total. Es progreso dentro del cuestionario, no porcentaje del curso completo.
p2lrn:statechangeSnapshot con respuestas, pregunta actual, número de intentos terminados, puntuación guardada, estado de juego y temporización.
p2lrn:timeupdateExpone tiempo restante del intento y de la pregunta cuando existen límites de tiempo.
p2lrn:timeoutIndica si agotó el tiempo el intento completo o una pregunta concreta.
p2lrn:scoreEntrega nota total y desglose por pregunta.
p2lrn:completeEntrega puntuación, respuestas finales y el motivo de finalización.
La puntuación nativa tiene este formato. normalized va de 0 a 1 cuando la corrección es automática; si el modo de corrección pertenece al host, graded es false y normalized es null.
{
"graded": true,
"raw": 8,
"max": 10,
"normalized": 0.8,
"questions": [
{
"questionId": "q1",
"graded": true,
"correct": true,
"hits": 1,
"errors": 0,
"pointsAwarded": 2,
"pointsPossible": 2
}
]
}El detalle de aciertos, errores, puntos obtenidos y preguntas sin responder se obtiene mediante score.questions y el estado de respuestas. La interfaz visible no debe utilizarse como fuente para reconstruir estos datos.
8. Datos de seguimiento en SCORM 1.2 y SCORM 2004
SCORM mantiene su modelo CMI. P2LRN conserva ese estado y lo entrega mediante saveState y p2lrn:statechange. El objeto contiene state.values; las claves exactas dependen de la versión y de lo que el SCO realmente escriba.
Nota: cmi.core.score.raw, max, min. Estado: cmi.core.lesson_status. Posición: cmi.core.lesson_location. Tiempo: cmi.core.session_time y cmi.core.total_time.
Nota: cmi.score.raw, max, min, scaled. Estado: cmi.completion_status y cmi.success_status. Progreso: cmi.progress_measure. Posición: cmi.location. Tiempo: cmi.session_time y cmi.total_time.
cmi.interactions.* puede contener id, tipo, respuesta del alumno, resultado, latencia, respuestas correctas y objetivos cuando el SCO los publica.
cmi.objectives.* conserva objetivos y sus notas/estados cuando el paquete los utiliza.
P2LRN deriva reportScore al hacer Commit o Terminate si hay una nota utilizable y ha cambiado. Para SCORM 2004 usa primero cmi.score.scaled; si no existe, puede normalizar raw / max. El campo passed sale de cmi.success_status en 2004 o de cmi.core.lesson_status en 1.2.
El soporte de una clave CMI en el runtime no implica que todos los paquetes la escriban. El estado debe conservarse tal como se recibe y cada campo debe tratarse como opcional.
9. Datos de seguimiento en H5P y xAPI
H5P comunica estado para reanudación y también puede emitir xAPI. P2LRN reenvía cada statement como p2lrn:xapi y, cuando contiene un resultado, deriva una puntuación común para que la plataforma integradora pueda obtener la nota básica sin interpretar cada tipo de contenido H5P.
p2lrn:statechangeEstado H5P obtenido del runtime para reanudar la actividad.
p2lrn:xapiIncluye el statement original en payload.statement, además de runtime:"h5p", source:"content-runtime" y authoritative:false.
p2lrn:scoreDeriva raw, max, normalized, passed y completed desde statement.result cuando están disponibles.
reportScoreSe llama para xAPI terminal con verbos completed, passed o failed. El score sigue marcado como no autoritativo.
p2lrn:completeSe emite al recibir uno de esos verbos terminales e incluye el statement y la puntuación derivada.
Antes de enviarlos a un LRS, el servidor de la plataforma debe reconstruir actor, authority, registration/attempt y cualquier otro dato controlado por el LMS.
10. Estado de progresión del curso
La progresión utiliza un estado separado de los estados internos de cada actividad. El Player lo recalcula a partir de rendered, finalización y puntuación, lo guarda mediante Host Adapter y emite p2lrn:progressionchange cuando cambia.
{
"version": "p2lrn-progression-state/1",
"entities": {
"urn:uuid:actividad-1": {
"viewed": true,
"completed": true,
"passed": true,
"scorePercent": 82.5,
"updatedAt": "2026-10-16T08:30:00.000Z"
}
}
}p2lrn:rendered puede marcar una actividad como vista; p2lrn:complete la marca completada; los scores aportan passed y scorePercent. Después se aplican required, gateFollowing, completionRequirement, minScore y unlockWhen.
p2lrn:progress representa progreso interno de una actividad.El progreso global del curso se obtiene a partir del estado de progresión y de las reglas declaradas por sus entidades. Por ejemplo, un cuestionario puede utilizar p2lrn:progress para indicar cuántas preguntas se han respondido.
11. Modelo de persistencia
La persistencia suele separar el snapshot de reanudación del historial analítico. El esquema físico pertenece a la plataforma integradora; la separación por responsabilidad evita mezclar datos con ciclos de vida distintos.
Un registro por alumno/intento/actividad/superficie. Se reemplaza con cada saveState.
Cada reporte puede conservarse por intento o someterse a una política explícita de última o mejor nota. P2LRN no define esa política.
Log opcional append-only con el sobre canónico, más un receivedAt del servidor.
Entidad de la plataforma integradora con inicio, cierre, matrícula, usuario y reglas de negocio. No forma parte del paquete.
Para reanudar con seguridad, la clave debe incluir contentRoot. Si el contenido cambia, la plataforma puede migrar el estado o iniciar un intento compatible en lugar de aplicar silenciosamente un snapshot de otra versión.
12. Gestión de eventos de alta frecuencia
Los eventos de alta frecuencia no deben convertirse automáticamente en una escritura persistente por cada cambio. p2lrn:timeupdate, respuestas y commits sucesivos pueden agruparse según los requisitos de analítica.
saveStateconserva el último snapshot de reanudación y no sustituye a un historial de eventos.- La analítica temporal puede persistirse de forma asíncrona o por lotes a partir de
emitEvent. - El evento incluye
timestamp; para auditoría conviene añadir además unreceivedAtgenerado por el servidor. - El tiempo de sesión de negocio debe medirse también a partir de la apertura y cierre del intento en el servidor, sin depender únicamente del reloj del navegador.
- En SCORM pueden conservarse los tiempos CMI publicados por el SCO; en cuestionarios temporizados, el estado de temporización; en H5P, las duraciones xAPI deben tratarse como datos generados por el contenido.
13. Reintentos, duplicados e idempotencia
emitEvent se entrega en orden dentro de un mismo Adapter, pero el evento P2LRN no define un identificador durable de base de datos. Cuando el transporte pueda reintentar peticiones, el servidor debe utilizar una clave de ingestión propia.
Una estrategia habitual consiste en generar un identificador al recibir el evento y, cuando se requiera deduplicación determinista, combinar intento + tipo + timestamp + contexto + hash del payload. La política exacta depende de si la analítica debe conservar repeticiones legítimas.
14. Casos de integración habituales
reportScore proporciona raw, max, normalized y, cuando existen, passed/completed.
loadState + saveState permiten conservar el snapshot por intento y actividad.
Los cuestionarios exponen p2lrn:answer, score.questions y complete.answers.
Las claves cmi.interactions.* del estado guardado contienen las interacciones que el paquete haya publicado.
p2lrn:xapi conserva el statement como entrada de contenido; identidad y authority deben reconstruirse en el servidor antes de enviarlo al LRS.
p2lrn-progression-state/1 conserva el estado sobre el que el Player aplica las reglas de progresión.
15. Privacidad y límites de confianza
El paquete P2LRN es portable y no debe contener el historial personal del alumno. Host Adapter pertenece al contexto de confianza del host. Los eventos de contenido pueden describir la actividad realizada, pero identidad, matrícula, intento, permisos y autoridad final pertenecen a la plataforma integradora.
La referencia de Eventos documenta el contrato de cada evento; Progresión describe las reglas de finalización y Integración explica la conexión con los servicios de la plataforma.
Analítica pedagógica enriquecida
Además de estado, nota y finalización, el runtime nativo permite observar el recorrido pedagógico con eventos semánticos que no dependen del HTML visible.
p2lrn:attempt diferencia un intento nuevo, una reanudación y una entrega final. Esto evita inferir sesiones a partir de tiempos o recargas del navegador.
p2lrn:interaction con action:"answer-change" permite medir reconsideración y navegación por pregunta sin duplicar la respuesta cruda en el evento analítico.
Voto, publicación/eliminación de nota, aportación y revisión entre pares generan interacciones semánticas dentro del mismo contrato de eventos.
Las aperturas e interacciones relevantes pueden incorporarse al historial con su contexto de actividad/recurso, manteniendo separada la presentación de la analítica.
La plataforma puede combinar estos eventos con contentRoot, entityId, intento y usuario del host para construir métricas de participación, reanudación, colaboración y uso del contenido sin introducir esos identificadores privados en el .p2lrn.
API de learning events y tiempo activo
La Web SDK de alpha.11 puede proyectar los eventos runtime a un flujo de aprendizaje ordenado sin cambiar el sobre p2lrn:*.
createLearningEvent()Normaliza un runtime event con schema, versión, id, secuencia, categoría y contexto.
createLearningEventStream()Mantiene secuencia y, opcionalmente, sessionId/attemptId.
observeLearningEvents()Observa las familias conocidas en un EventTarget y emite el modelo normalizado.
createActivityTracker()Mide tiempo activo considerando visibilidad, foco e interacción. Por defecto la interacción se acota al Player para no contabilizar clics de UI ajena.
El tiempo activo es telemetría opt-in; no sustituye por sí solo una regla pedagógica, una nota oficial o una autoridad temporal externa.
P2LRN Analytics
Cuando necesitas consultar estas métricas para muchos alumnos y cursos, P2LRN Analytics proporciona un panel embebible por CDN o NPM. Mantiene separadas la actividad observada y las notas autoritativas del Host.