User request — especificación para desarrollo ← Todos los gadgets Ver la maqueta
Gadget 04 · User request

Reporte de Impulso Lector

wc-laravel · Desafío Lenguaje Laravel 5.7 / Vue 2 Profesor · UTP · Director Borrador para revisión 2026-09-28

El panel donde el profesor ve lo que sus alumnos hicieron en las cápsulas de Impulso Lector: quién las hizo, qué entendió y cómo lee —con la grabación y cada palabra marcada—. Es el resultados.php que pide la especificación del backend, diseñado: cómo se llega, qué muestra y cómo lo muestra.

1De dónde parte

De la especificación «WClass Publisher · Backend de resultados de alumnos» (v1.5, 2 sep 2026). Define cómo se guardan los resultados: cinco tablas (lc_capsula, lc_pregunta, lc_result_attempt, lc_result_answer, lc_result_file), un endpoint idempotente con la identidad tomada de la sesión, y el MP3 en disco. De la pantalla del profesor dice una línea: «filtros por colegio / curso / cápsula / alumno, escuchar audio, ver el detalle por pregunta y exportar». Este documento es esa línea.

Y de lo que ya existe, relevado en el código el 2026-09-28:

2Lo que hay que corregir en la especificación del backend

Contrastada con el código, la especificación tiene diferencias concretas. Ninguna cambia la arquitectura, pero todas cambian lo que el reporte puede mostrar. Conviene resolverlas antes de migrar, porque después son datos que no se guardaron.

Las tablas de identidad se llaman distinto, y alumno no es la persona

La especificación pone claves foráneas a alumnos, cursos y colegios. En el producto son alumno, curso y colegio, en singular (modelos App\Student, App\Curso, App\School).

Y alumno.id no identifica a la persona: es su inscripción en un curso y un año (alumno.alumno → usuario.id, más curso, colegio, anio, nivel). Eso le conviene al diseño —lc_result_attempt.alumno_id apuntando a alumno.id ya es el snapshot de curso y año—, pero hay que saberlo: el historial de un niño a lo largo de los años se arma por alumno.alumno, no por alumno_id. Todos los resultados del producto (test_alumno, resultados, desafio_indicador_alumno) usan alumno.id.

De la sesión: para un alumno, controladorTema.php:34-36 ya deja $_SESSION['alumno'] con su alumno.id. Si no está, la consulta de desafiocrece/Daos/Trainer.php:490-499 lo resuelve desde usuario, nivel, colegio y año.

El audio es webm, no MP3 — y en el teléfono no hay

El visor graba con MediaRecorder y el blob sale como rChunks[0].type || 'audio/webm'. lc_result_file.mime tiene que aceptar audio/webm (o convertir en el servidor), y la validación «tipo real del archivo» tiene que reconocerlo.

Y en un teléfono con el reconocimiento del navegador el visor no graba (if(!rMobile) rStartAudio();): el reconocimiento y la grabación compiten por el micrófono. La lectura se mide igual, pero no hay archivo. El reporte tiene que decir «sin grabación» en vez de mostrar un reproductor vacío —la maqueta lo hace—. Con Vosk (el motor local que se usa en Android) sí se graba.

Lo que el alumno dijo, palabra por palabra, se calcula y se tira

lcpAlignWords alinea lo dicho con el texto y marca cada palabra: ok, sub (dijo otra), del (la saltó), ins (agregó una). Con eso calcula la precisión… y no lo guarda: el reporte del visor sólo conserva posiciones en la onda (errMarks).

Sin eso, el profesor escucha dos minutos de audio sin saber dónde mirar. Tiene que ir en lc_result_answer.metricas, que ya es JSON:

{ "ppm": 84, "precision": 91, "fluidez": 75, "categoria": 2, "grado": 3,
  "motor": "webspeech", "con_audio": true,
  "ops": [ {"w":"Una","op":"ok"}, {"w":"mañana","op":"sub","dijo":"maña"},
           {"w":"zorro","op":"del"}, {"op":"ins","dijo":"este"}, ... ],
  "sub": 4, "del": 2, "ins": 1, "gap": 0 }

Pesa poco (un texto de 80 palabras son ~3 KB) y es lo que hace útil la ficha.

Los tipos de pregunta tienen otros nombres

La especificación nombra «emparejar, arrastrar, zonas, ordenar». Esas son las etiquetas; los qtype reales (creador.js:1418) son matching, dnd / dndmulti, hotspot y ordering. Y la lectura no es un tipo sino dos: reading y readingspeed. Son 21 en total. lc_pregunta.tipo tiene que guardar el literal, no la etiqueta.

Hoy no hay puntaje por pregunta ni habilidad

3Quién lo usa

No repetir lo del informe de Desafío Mate

informeDesafioMate/daos/informeDao.php:33-43 (obtenerCursos) trae todos los cursos del nivel, sin filtrar por profesor. Un profesor puede ver los resultados de un curso que no es suyo. Y las rutas de reportesLectura/* (routes/api.php:1809-1863) no tienen middleware de autenticación. En este reporte, cada consulta comprueba en el servidor que el curso es del usuario de la sesión, igual que el action=audio de la especificación.

4Dónde vive y cómo se llega

En Laravel, en el módulo de Desafío Lenguaje, junto a los reportes de lectura que ya existen: un componente Vue nuevo bajo reportes-lenguaje/ y sus rutas en Desafio\ReportesLecturaController (o uno propio), leyendo las tablas lc_* que están en la misma base. La especificación propone un resultados.php suelto; hacerlo en Laravel le da el marco, el login y el estilo que el profesor ya conoce. El endpoint que recibe los resultados (resultados_api.php) puede quedar donde la especificación lo pone: es el que necesita ser same-site con la cápsula.

Se entra directo al reporte, con el primer curso del profesor (su jefatura) y todas las cápsulas del año. No hay una pantalla intermedia de cursos: el curso se cambia con el selector, y la maqueta arranca ahí.

Tres caminos de entrada

  1. Desde la portada: el bloque Desafío Crecer (includes/link-desafio.php) → Desafío Lenguaje (el POST a loginFromWebclass que ya existe) → Impulso Lector. Sólo si el colegio lo tiene en colegio_plan_desafio.
  2. Desde el menú de Desafío Lenguaje, como una opción más. Los nombres del submenú de la maqueta son propuesta.
  3. Desde la cápsula: cuando la abre un profesor, el botón «Ver reporte» del visor lleva a este reporte con esa cápsula elegida y en la pestaña Preguntas, en vez de mostrar el reporte local del navegador (que para el profesor está vacío: no es él quien la resolvió).

5Qué muestra

El reporte de un curso

Filtros en este orden: año —primero, porque acota todo lo demás: qué cursos tiene el profesor ese año y qué cápsulas se asignaron—, curso y cápsula (una, o todas; la lista va de la más reciente a la más antigua, con su fecha de asignación).

No lleva tarjetas de cifras arriba. Los números están en los reportes, donde se pueden leer con su contexto; un resumen suelto encima de la tabla compite con ella y no dice qué hacer. El contenido ocupa todo el ancho de la pantalla.

La tabla distingue completaron · abrieron y no terminaron · no la abrieron. Las dos últimas sólo se pueden separar con el action=start que la especificación deja opcional: hay que hacerlo.

Todas las tablas se ordenan por cualquier columna

Un toque en el título ordena de menor a mayor; el segundo, al revés; una flecha dice el sentido. Se ordena por el valor, no por el texto: 9% va antes que 60%, «Muy lenta» antes que «Lenta», y los nombres por apellido sin que las tildes los saquen de su letra. Lo que no tiene dato —«sin abrir», sin lectura— va siempre al final, en los dos sentidos: no es ni lo más bajo ni lo más alto. El orden elegido se mantiene al cambiar de curso.

Los números cortos (porcentajes, palabras por minuto, intentos) van centrados en su columna.

Con «Todas», sólo la tabla de alumnos — y se desplaza hacia el lado

En septiembre un curso lleva 12 a 14 cápsulas: una columna por cápsula no cabe en la pantalla. La tabla se desplaza dentro de su caja (la página no se ensancha), de la cápsula más antigua a la más reciente, y quedan fijos el número y el nombre del alumno a la izquierda y su comprensión promedio y su última lectura a la derecha: al desplazar no se pierde de quién es cada fila. El encabezado de cada cápsula lleva su fecha y el título en dos líneas.

Las pestañas Preguntas, Lectura y Habilidades aparecen sólo con una cápsula elegida. Mezclar las preguntas de catorce cápsulas no dice nada, y una pestaña que obliga a elegir antes de mostrar algo parece vacía.

Con una cápsula elegida, cuatro pestañas:

PestañaQué respondeCómo
Alumnos¿Quién la hizo y cómo le fue? Una fila por alumno. Con una cápsula: estado, comprensión, ppm, fluidez, precisión, intentos y fecha. Con todas: una columna por cápsula (comprensión y ppm), como el informe de avance de Desafío Mate. Se ordena por columna.
Preguntas¿Qué se entendió y qué no? Una tarjeta por pregunta, en el orden de la cápsula: tipo, diapositiva, habilidad, % correcto y qué se eligió en cada alternativa —con la miniatura si la alternativa es imagen—. Las abiertas muestran sus respuestas.
Lectura en voz alta¿Cómo lee el curso? La tabla de fluidez del grado, el reparto del curso en las seis categorías, y una fila por alumno de la más lenta a la más rápida: ppm, categoría, precisión, palabras cambiadas, omitidas, duración y «Escuchar». Con todas las cápsulas, la evolución de cada alumno.
Habilidades¿En qué tipo de pregunta falla? Una dona por habilidad para el curso y una tabla alumno × habilidad, con «Reforzar: …» en la que está bajo. Es el mismo formato de donas de la ficha de Desafío Lenguaje.

La ficha del alumno

Exportar

CSV con punto y coma y BOM —el mismo formato del «Ver reporte» del visor, que el Excel en español abre bien—: una fila por alumno y pregunta, con habilidad, respuesta, correcta, resultado, si se calificó con la clave actual o la anterior, y las métricas de lectura. Respeta los filtros.

6Cómo lo muestra: las reglas

Comprensión y fluidez van separadas, siempre

El visor calcula un solo «logro» de lectura: Math.round(prec*0.6 + flu*0.4). En el reporte eso no se usa. Un alumno que lee rápido y no entiende y uno que entiende y lee lento sacan el mismo número, y necesitan cosas opuestas. La comprensión es el % de preguntas correctas que se corrigen solas (sin la lectura en voz alta y sin las abiertas); la fluidez son las palabras por minuto y su categoría. En la maqueta hay alumnos al 100% en una y en «muy lenta» en la otra.

Revisiones de la clave y preguntas retiradas

Es la tabla «Edición de preguntas y revisiones» de la especificación, llevada a la pantalla:

7Las consultas que sostienen la pantalla

Dos que conviene escribir una vez y reusar, porque todo el reporte depende de ellas:

-- El intento que cuenta: el último completado de cada alumno en la cápsula
SELECT a.*
FROM lc_result_attempt a
JOIN (SELECT alumno_id, MAX(finalizado_en) f
        FROM lc_result_attempt
       WHERE capsula_id = ? AND estado = 'completado' AND curso_id = ?
       GROUP BY alumno_id) u
  ON u.alumno_id = a.alumno_id AND u.f = a.finalizado_en
WHERE a.capsula_id = ?;

-- Los alumnos del curso, con quien no la abrió (LEFT JOIN, no JOIN)
SELECT al.id, ud.nombre_usuario, ud.apellido_paterno, ...
FROM curso c
JOIN alumno al          ON al.curso = c.id AND al.estado = 1 AND al.habilitado = 1
JOIN usuario u          ON u.id = al.alumno AND u.idrol = 31
JOIN usuario_detalle ud ON ud.idusuario = u.id
LEFT JOIN lc_result_attempt t ON t.alumno_id = al.id AND t.capsula_id = ?
WHERE c.id = ?;

La segunda sigue el patrón de informeDesafioMate/daos/informeDao.php:186-201. Con MySQL 8.4 (relevado para el gadget 01) la primera se puede escribir también con ROW_NUMBER().

8Lo que hay que agregar al modelo

DóndeQuéPor qué
lc_preguntahabilidad VARCHAR(32) NULL — 'localizar', 'interpretar', 'reflexionar' nueva Sin ella no hay pestaña de habilidades. Se elige en el editor, por pregunta; la sincronización del catálogo la copia desde creador.json.
lc_preguntaoa VARCHAR(32) NULL nueva Opcional hoy; es lo que permitiría cruzar con el informe de aprendizaje.
lc_result_answer.metricaslas ops palabra por palabra, sub/del/ins, motor, con_audio §2. Es lo que marca el texto en la ficha.
lc_result_file.mimeaceptar audio/webm§2.
lc_reevaluacion(pregunta_id, rev_desde, rev_hasta, respuestas, suben, bajan, usuario_id, creado_en) nueva El «dejando registro» de la especificación: sin tabla, no hay dónde.

9Criterios de aceptación

  1. Se llega desde la portada (Desafío Crecer → Desafío Lenguaje → Impulso Lector) directo al reporte, y desde la cápsula con ella elegida.
  2. Los filtros van en el orden año, curso, cápsula.
  3. Con «Todas» y 14 cápsulas, la tabla se desplaza dentro de su caja; el alumno y sus promedios quedan fijos y la página no se ensancha.
  4. Las pestañas Preguntas, Lectura y Habilidades sólo aparecen con una cápsula elegida.
  5. Todas las tablas se ordenan por cualquier columna, por valor, con lo vacío al final.
  6. La ficha se imprime sola, con su cabecera, sin el marco ni el reproductor.
  7. Un profesor ve sólo sus cursos; la comprobación es del servidor, no de la pantalla.
  8. Completaron + abrieron sin terminar + no la abrieron = los alumnos del curso.
  9. En una pregunta de opción única, las barras suman las respuestas; en una alternativa-imagen se ve la imagen.
  10. La comprensión no incluye la lectura en voz alta ni las abiertas.
  11. La fluidez se clasifica con la tabla del grado, en las seis categorías del visor.
  12. En la ficha, las palabras marcadas coinciden con los conteos y la precisión sale de ellas.
  13. Sin grabación, se dice; con grabación, se escucha y sólo la sirve action=audio con permiso.
  14. Las respuestas con clave anterior se marcan; «Re-evaluar» dice cuántas suben y bajan, lo hace y deja registro.
  15. Una pregunta retirada sigue visible para quien la respondió, marcada.
  16. Con dos intentos, cuenta el último completado y la ficha deja ver el otro.
  17. El CSV respeta los filtros y abre bien en Excel en español.

10Qué hay que decidir antes de programar

  1. Los tramos de logro. El producto tiene tres escalas: el informe de evaluación (informes/general.php: <60 / 60-81 / >82, con huecos en 60, 81 y 82), el informe de indicadores de Desafío Lenguaje (≤20% «bajo promedio») y la ficha de Desafío Lenguaje (0-30 / 31-80 / 81-100). La maqueta usa 60 / 80. Tiene que ser una.
  2. ¿Palabras dichas o palabras correctas por minuto? El visor calcula A.saidTotal / minutos: cuenta también las que leyó mal y las que agregó. Las normas de velocidad lectora suelen medir palabras leídas correctamente. Hay que confirmar contra qué midió la tabla, porque con la cuenta actual un alumno que se equivoca mucho puede caer en una categoría más alta de la que le corresponde. En la maqueta pasa: una alumna lee 116 palabras por minuto, «muy rápida», con 73% de precisión y 11 palabras omitidas.
  3. ¿El grado de la norma es el del alumno o el de la cápsula? La maqueta usa el del alumno (su curso): un niño de 4° que lee una cápsula de 3° se mide contra 4°. El visor hoy toma el de la cápsula.
  4. ¿Cuenta el último intento o el mejor? La maqueta usa el último completado.
  5. ¿El profesor puede corregir las abiertas? Hoy no puntúan. Si se corrigen a mano, necesitan dónde guardar esa nota.
  6. La entonación está desactivada en el visor (wantProsody=false): no se muestra hasta que se active y se valide.
  7. La retención del audio, que la especificación ya deja abierta, define si la ficha puede mostrar lecturas del año anterior.
Sobre la maqueta

Los resultados son de ejemplo, generados con semilla fija —siempre los mismos— para dos cursos de 3° básico y catorce cápsulas con textos propios, de marzo a septiembre. Las tres de septiembre son las completas (una versión propia de la fábula del zorro y el gallo, un texto informativo sobre el pingüino de Humboldt y la leyenda del Caleuche); las once anteriores traen la lectura y tres preguntas. La voz del reproductor es sintética: el navegador lee lo que el alumno dijo, a su ritmo. Las alternativas-imagen son dibujos de reemplazo de las imágenes de la cápsula.

Atajo: ?capsula=c1 (o c2, c3) entra con una cápsula elegida, que es como llega el «Ver reporte» de la cápsula. En c1 está la pregunta con la clave corregida; en c2, la retirada.

← Todos los gadgets