# Informe QA — Módulo RA3 CDT1 (formato SAQI)

**Fecha:** 2026-08-27 · **Ciclo:** MVP → QA · **Estado final:** ✅ 21/21 funnel · 35/35 unit+int

## 1. MVP entregado
- Tarjeta **"RA3 CDT1 – Evaluación: Mol y Composición Porcentual"** insertada en el menú
  `/home/adoniz/apps/quimica/templates/index.html` (parche idempotente `patch_index.py`,
  ancla `<div class="card proximamente">`), con enlace `/quimica/ra3-cdt1` que el Funnel
  normaliza a `/ra3-cdt1` en el backend.
- Verificado en producción: `https://adoniz-acer.tail60dd6a.ts.net/quimica/` muestra la tarjeta.

## 2. Batería ejecutada (`qa_funnel.py`, contra producción vía Funnel)

### L4 Funcional (menu y navegación)
| Prueba | Resultado |
|---|---|
| `/quimica/` responde 200 y contiene enlace ra3-cdt1 | PASS |
| Los 5 enlaces de módulos del menú devuelven 200 vía funnel | PASS (incl. `/quimica/ra3-cdt1`) |

### L3 Funcional (flujo de evaluación)
| Prueba | Resultado |
|---|---|
| iniciar-ra3 entrega token 32-hex | PASS |
| incidente-ra3 válido acepta | PASS |
| guardar-ra3 con respuestas vacías → académico 0 sin caer | PASS |
| reenvío con el mismo token → HTTP 409 | PASS |

### ATK-SEC Adversarial
| Prueba | Resultado |
|---|---|
| JSON malformado → 400 (sin 500/502 del backend) | PASS |
| Content-Type incorrecto → sin caída | PASS |
| Payload de 1 MB → sin caída | PASS |
| Inyección CSV (=…, +…), XSS `<script>`, salto de línea en apellidos → sin 500 | PASS |
| Token desconocido rechazado (400) | PASS |
| Tipo de incidente fuera de allow-list rechazado (400) | PASS |
| La página NO expone la clave de respuestas | PASS |

## 3. Defectos hallados y resolución

| ID | Severidad | Descripción | Estado |
|---|---|---|---|
| QA-001 | Alta | El detector de `ventana_ajena` nunca disparaba: la condición `document.activeElement !== document.body` cancelaba el reporte justo cuando el estudiante tenía un campo enfocado (caso normal). | **Corregido** (commit en plantilla): reporte directo con enfriamiento de 3 s y ventana de gracia de 2 s tras un incidente de pantalla completa (evita doble −10 en un solo Alt-Tab). |
| QA-002 | Baja (entorno) | 502 intermitente en la primera petición tras reconfigurar el Funnel (handshake de Tailscale). No es defecto del app: el 2.º run dio 21/21. | Observado, sin acción |
| QA-003 | Info | Flask dev server procesa en serie (sin `threaded=True`): con muchos estudiantes simultáneos podría haber cola. El módulo usa `Lock` y es idempotente; suficiente para un aula. | Documentado |

## 5. Ciclo QA-004/005 — Interacción, pruebas en video y limpieza (2026-08-27)

### Bug QA-004 (confirmado por el docente)
**Síntoma:** dejar preguntas en blanco y pulsar "Enviar" contaba como incidente.
**Causa:** el `confirm()` nativo robaba el foco del sistema → `window.blur` → se sancionaba
`ventana_ajena`.
**Solución (cumple especificación pedida):** se reemplazó por un **modal en la página**
(`#modal` con "Aceptar / Cancelar") que nunca roba el foco. Mensaje al haber blancos:
"*Haz dejado algunas preguntas en blanco, ¿seguro que deseas finalizar?*".
- **Cancelar** → regresa a la evaluación sin guardar ni sancionar.
- **Aceptar** → guarda y las blancas valen **0 puntos** (sin incidente por el envío).
- Además `reportarIncidente` ignora reportes mientras `estado.enviando` es true.

### QA-005 — Pruebas E2E en video, headless con perfil aislado
- `e2e_video/playwright.config.js`: headless + `slowMo: 450`, tipeo a 450 ms/carácter
  (ritmo humano), video `on` (1280×720), perfil efímero aislado (no reacciona a tareas del
  equipo ni a foco del sistema). `actionTimeout: 180s` (el tipeo humano excede 30s).
- `e2e_video/tests/evidencia.spec.js` — cinco evidencias, a ritmo humano slowMo 450 ms:
  - **E1 – Ejemplar**: responde todo bien → nota **100** (0 incidentes, 0 alertas IA). ✅
  - **E2 – Tramposo**: sale de pantalla completa, F12, pega, doble clic derecho → incidentes
    y nota castigada a 0; además valida que **Cancelar** del modal regresa sin sancionar y que
    **Aceptar** guarda con blancos=0. ✅
  - **E3 – Reprobado**: responde todo mal → nota reprobatoria **< 60**. ✅
  - **E4 – Límite 59 (reprueba)**: todo bueno menos el ítem de %O en composición (3 pts)
    → nota exacta **59** (0 incidentes, 0 alertas IA). ✅
  - **E5 – Límite 60 (aprueba)**: IV y V perfectos, I/II/III en blanco → nota exacta **60**
    (0 incidentes, 0 alertas IA). ✅
- Resultado final: **5/5 passed (~3.8 m)**. Evidencia en video:
  - `e2e_video/salidas/evidencia-E1---.../video.webm` (7,8 MB)
  - `e2e_video/salidas/evidencia-E2---.../video.webm` (0,9 MB)
  - `e2e_video/salidas/evidencia-E3---.../video.webm` (2,6 MB)
  - `e2e_video/salidas/evidencia-E4---.../video.webm` (~2,0 MB)
  - `e2e_video/salidas/evidencia-E5---.../video.webm` (~2,1 MB)
- Reporte HTML: `e2e_video/reportes-html/index.html`.
- Detalle de calificación en `evaluacion_ra3/qa_informe.md`? → Sí (este mismo archivo).

### Defectos corregidos al publicar
- Timeout de `pressSequentially` (30s < tipeo humano) → `actionTimeout: 180_000`.
- `keyboard.press("Escape")` no sale de fullscreen en headless_shell → usar
  `document.exitFullscreen()` vía `page.evaluate`.

## 6. Limpieza tras la prueba (obligatorio)
Tras ejecutar las evidencias, se **resetea el CSV de producción** (solo encabezado) para que el
primer estudiante real arranque limpio:
```bash
# /home/adoniz/apps/quimica/
printf "\xef\xbb\xbfNo,Apellidos,Nombres,Carrera,Nota\r\n" > resultados_ra3_cdt1.csv
printf "\xef\xbb\xbffecha,token,estudiante,tipo\r\n" > incidencias_ra3_cdt1.csv
```
Hecho el 2026-08-27 tras la corrida final.
