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.

Estado

loadState y saveState sirven para reanudar una actividad exactamente donde estaba. Es un snapshot reemplazable, no un historial.

Puntuación

reportScore entrega una puntuación normalizada cuando la actividad tiene una nota utilizable.

Eventos

emitEvent recibe el flujo cronológico p2lrn:*. Es la fuente adecuada para analítica, auditoría y series temporales.

Progresión

El Player mantiene un estado separado con viewed, completed, passed y scorePercent para aplicar las reglas del curso.

Las métricas se obtienen mediante los contratos públicos del Player.

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.

Usuario / intento+context P2LRN→Host Adapter→API de la plataforma→estado / notas / eventos / LRS

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.

packageId

Identidad lógica del paquete.

contentRoot

Huella de integridad del contenido. Cambia cuando cambia el contenido y evita restaurar estado sobre otra revisión por accidente.

entityId

Actividad, unidad o raíz a la que pertenece el dato.

resourceId

Recurso asociado a la actividad cuando existe.

representationId

Representación concreta que está ejecutando el Player.

rendererId

Presentación/runtime elegido para esa representación.

profile / runtime

Algunos 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.

JavaScript
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;
Host Adapter se ejecuta en el contexto de confianza de la aplicación.

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.

JSON
{
  "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:answer

Incluye questionId y la respuesta normalizada de esa pregunta.

p2lrn:progress

Incluye value de 0 a 1, answered y total. Es progreso dentro del cuestionario, no porcentaje del curso completo.

p2lrn:statechange

Snapshot con respuestas, pregunta actual, número de intentos terminados, puntuación guardada, estado de juego y temporización.

p2lrn:timeupdate

Expone tiempo restante del intento y de la pregunta cuando existen límites de tiempo.

p2lrn:timeout

Indica si agotó el tiempo el intento completo o una pregunta concreta.

p2lrn:score

Entrega nota total y desglose por pregunta.

p2lrn:complete

Entrega 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.

JSON · score
{
  "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.

SCORM 1.2

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.

SCORM 2004

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.

Interacciones

cmi.interactions.* puede contener id, tipo, respuesta del alumno, resultado, latencia, respuestas correctas y objetivos cuando el SCO los publica.

Objetivos

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.

La disponibilidad de datos depende de cada SCO.

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:statechange

Estado H5P obtenido del runtime para reanudar la actividad.

p2lrn:xapi

Incluye el statement original en payload.statement, además de runtime:"h5p", source:"content-runtime" y authoritative:false.

p2lrn:score

Deriva raw, max, normalized, passed y completed desde statement.result cuando están disponibles.

reportScore

Se llama para xAPI terminal con verbos completed, passed o failed. El score sigue marcado como no autoritativo.

p2lrn:complete

Se emite al recibir uno de esos verbos terminales e incluye el statement y la puntuación derivada.

Los statements xAPI generados por el contenido no representan identidad de confianza.

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.

JSON · p2lrn-progression-state/1
{
  "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.

Estado actual

Un registro por alumno/intento/actividad/superficie. Se reemplaza con cada saveState.

Puntuaciones

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.

Eventos

Log opcional append-only con el sobre canónico, más un receivedAt del servidor.

Intento

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.

  • saveState conserva 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 un receivedAt generado 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

Nota final

reportScore proporciona raw, max, normalized y, cuando existen, passed/completed.

Reanudación

loadState + saveState permiten conservar el snapshot por intento y actividad.

Analítica por pregunta

Los cuestionarios exponen p2lrn:answer, score.questions y complete.answers.

Interacciones SCORM

Las claves cmi.interactions.* del estado guardado contienen las interacciones que el paquete haya publicado.

xAPI H5P

p2lrn:xapi conserva el statement como entrada de contenido; identidad y authority deben reconstruirse en el servidor antes de enviarlo al LRS.

Estado de desbloqueo

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.

Ciclo del intento

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.

Cambios de respuesta

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.

Colaboración

Voto, publicación/eliminación de nota, aportación y revisión entre pares generan interacciones semánticas dentro del mismo contrato de eventos.

Recursos y medios

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.