# Migración: Portal Docentes — Fase 1 (Calificaciones)

Segunda fase de la migración de `uepca/docentes/` hacia `admin_docentes/`
(ver `migracion_de_modulo_docentes.md` para el plan completo). Cubre la
carga de notas de Primaria (boletín de apreciación A-E) y de Bachillerato/
Liceo (notas numéricas por materia y estrategia). Probado end-to-end contra
colegio 11 (`jesistem_juanxxiii`) con datos sintéticos.

## Alcance

| Origen (`uepca/docentes/`) | Destino (`admin_docentes/`) | Rol |
|---|---|---|
| `bus-gra-secprof.php` + `bus-gra-sec2prof.php` | `notas-primaria.php` | Selector grado/sección (solo asignados al docente) + listado de alumnos |
| `carganotapri.php` | `notas-primaria-boletin.php` | Boletín de apreciación (A-E) por materia/indicador, un alumno |
| `guarnotas.php` | `notas-primaria-guardar.php` | Guarda el boletín en `notaprimaria{periodo}` |
| `listmateprof.php` | `mis-materias.php` | Materias asignadas al docente (Bachillerato/Liceo), con enlaces por estrategia |
| `notabachi_n.php` | `notas-bachillerato.php` | Carga masiva de notas por grado/sección/materia/estrategia |
| `guarnotabachi-n.php` | `notas-bachillerato-guardar.php` | Guarda en `cortes{periodo}`, actualiza `cortes1{periodo}`, `evalua_calendario` y recalcula `matri{periodo}` |

**Excluidos** (duplicados sin uso vigente, confirmado por grafo de enlaces
reales — no solo por nombre de archivo): `guarnotasviejo.php`,
`guarnotabachi-n2.php`, `guarnotabachi-n-vie.php`, `guarnotas-VIE.php`,
`notabachi_n_res.php`, `notabachi_n-vie.php`, `listmateprof_res.php`,
`listmateprof-vie.php`, `carganotas.php` (uepca root). Se verificó con
`grep` de `action=`/`href=` en todo `uepca/docentes/` que ninguno de estos
es alcanzable desde `index.php`.

`busGraVid.php` (Video Aula Primaria, enlazado desde el menú principal) NO
es parte de Calificaciones — se corrige la categorización original del plan
maestro, queda para la Fase 3 (Aula Virtual).

No incluidos en esta pasada (quedan para fases futuras, ya enlazados desde
`listmateprof.php`/`bus-gra-sec2prof.php` en el legacy pero fuera de
"cargar notas"): `hist-alu.php`, `list-historial.php` (históricos, Fase 5),
`rep-cortes.php` (reporte PDF, Fase 5), `revisionPdfListado.php` (Fase 5),
`observ-guia.php` (docente guía, Fase 4).

## Decisiones de diseño

- **Slugs**: `doc-notas-primaria`, `doc-notas-bachillerato`, grupo de menú
  "Calificaciones" — seed en `db/fase11-portal-docentes-notas.sql`, ya
  aplicado contra `jesistem_siscev` local y habilitado para colegio 11.
- **Gating adicional por `maeDa`/`docDa`**: además de
  `modulo_habilitado()`, el sidebar solo muestra "Notas de Primaria" si
  `$_SESSION['maeDa']=='S'` y "Notas de Bachillerato" si
  `$_SESSION['docDa']=='S'` (esas banderas ya las calcula el login, ver Fase
  0). No tener grado/sección asignado en `trgsp`/`trgsmp` oculta la opción
  aunque el módulo esté habilitado para el colegio.
- **Verificación de propiedad (mejora de seguridad sobre el legacy)**: el
  legacy `carganotapri.php` **no verificaba** que el docente tuviera
  realmente asignado el grado/sección del alumno cuya cédula recibía por
  `$_GET['cedalum']` — solo revisaba que hubiera sesión activa. Cualquier
  docente autenticado podía cargar/editar el boletín de cualquier alumno
  adivinando su cédula. Se corrigió: tanto `notas-primaria-boletin.php`
  como `notas-primaria-guardar.php` (y sus equivalentes de bachillerato)
  re-verifican contra `trgsp`/`trgsmp` que el docente en sesión tiene
  asignado ese grado/sección/materia, tanto al mostrar como al guardar (no
  solo confiar en lo que venga del formulario).
- **Simplificación de flujo**: el selector de grado/sección y el listado de
  alumnos de primaria se combinaron en un solo archivo
  (`notas-primaria.php`, alterna por querystring) en vez de los 2 archivos
  separados del legacy (`bus-gra-secprof.php` → `bus-gra-sec2prof.php`).
- **UI/UX**: reskin completo a SB Admin 2, mismo patrón de tarjetas que el
  resto de `admin_docentes/`. El boletín de primaria pasa de 9 bloques de
  HTML copy-pasteados (uno por materia, ~500 líneas) a un solo bucle sobre
  un array de materias/indicadores construido dinámicamente desde
  `grado{periodo}.mate1..mate9` + `boletas{periodo}`.
- **Seguridad**: todas las queries reescritas con prepared statements. El
  legacy concatenaba `$_POST`/`$_GET` directo en el 100% de este módulo.

## Hallazgos de esquema (específicos de `jesistem_juanxxiii`) y bugs corregidos

Todos encontrados por prueba end-to-end real, no por lectura de código —
justifican por qué vale la pena probar cada fase contra una BD real antes
de darla por cerrada:

1. **`preinscripcion.iniciaAdultos`/`terminaAdultos` no existen en esta
   BD** — el legacy `notabachi_n.php` los usa para el rango de fechas de
   grado≥66 ("adultos"), pero `jesistem_juanxxiii` tiene esas columnas
   renombradas a `iniciaMaestro`/`terminaMaestro` (mismo propósito, nombre
   distinto — deriva histórica del colegio, no del período). Corregido en
   `notas-bachillerato.php`. **Si se migra este módulo para otro colegio,
   verificar el nombre real de estas columnas antes de asumir el del
   legacy.**
2. **`notaprimaria{periodo}` tiene 31 columnas `NOT NULL` sin default**
   fuera de las que usa el flujo normal: todo `notap8*`/`notap9*`
   (indicadores 1-5 de las materias 8 y 9, los 3 lapsos) y `literal`. Un
   `INSERT` nuevo que solo mande las columnas del lapso actual (como hace
   el legacy) revienta bajo `sql_mode` estricto. `notas-primaria-guardar.php`
   completa esas columnas con `''` en un alumno sin boletín previo.
3. **`cortes{periodo}` tiene `nota61`/`nota62`/`nota63` `NOT NULL` sin
   default** — una 6ª "estrategia" fuera del rango 1-5 que usa este flujo,
   sin uso conocido. Mismo tratamiento: se completan con `''` en un
   `INSERT` nuevo.
4. **`matri{periodo}` tiene ~50 columnas `NOT NULL` sin default** ajenas a
   notas (`idAlumno`, `grado`, `fechaIngreso`, `mat1..mat13`, `rev1..rev13`,
   `convenio`, etc.) que se completan en el proceso de inscripción/matrícula,
   no en la carga de notas. El legacy intenta un `INSERT` mínimo
   (`ced_alu`, campo de nota, campo de inasistencia) que **también
   fallaría bajo `sql_mode` estricto** — solo "funciona" en producción
   porque en la práctica todo alumno matriculado ya tiene su fila en
   `matri{periodo}` creada por el proceso de inscripción antes de que un
   docente le cargue notas, así que la rama INSERT casi nunca se ejecuta
   contra datos reales. **Decisión**: en vez de replicar un `INSERT`
   incompleto (que además de romperse en modo estricto, crearía un
   registro de matrícula a medias), `notas-bachillerato-guardar.php`
   **omite** la actualización del promedio si el alumno no tiene fila en
   `matri{periodo}` — no le corresponde a este módulo sintetizar una
   matrícula. `cortes{periodo}` (donde sí vive la nota cruda por
   estrategia) se guarda siempre, sin esta limitación.
5. **`ANOESCM` (constante) pasada directo a `bind_param()`** — PHP 8+ exige
   pasar variables por referencia a `bind_param`, no constantes. Fatal
   error `could not be passed by reference`. Corregido en
   `notas-primaria.php` y `notas-bachillerato.php` asignando a una variable
   local primero. Si se reutiliza este patrón en fases futuras, recordar
   esta limitación.

## Verificación

Cuenta de docente sintética (`90000002`, cargo maestro, autorizada por el
usuario) con asignación real de prueba: `trgsp2526` a grado 51/sección 1
(Primaria) y `trgsmp2526` a grado 61/sección 1/materia 6111 "Fundamento
Humano Cristiano" (Bachillerato). Dos alumnos sintéticos (`90000003` en
grado 51/sección 1, `90000004` en grado 61/sección 1), clonados de alumnos
reales del mismo grado/sección para heredar las columnas `NOT NULL`,
**sin representante (`ced_rep`) asignado** — verificado explícitamente
antes de guardar notas, para descartar que los triggers de notificación
Telegram sobre `cortes{periodo}` (ver
`documentacion/telegram-notificaciones.md`) dispararan un mensaje real a
algún representante.

Flujo probado de punta a punta vía HTTP (login real + cookies de sesión):

- `notas-primaria.php` (selector y listado) → 200, sin errores/warnings.
- `notas-primaria-boletin.php` → muestra correctamente los indicadores
  configurados en `boletas2526` para grado/sección/lapso.
- `notas-primaria-guardar.php` → guarda 5 apreciaciones + días hábiles +
  inasistencia + observación + literal; verificado por `SELECT` directo
  que persistió en las columnas correctas (`notap131`, `notap231`, etc.) y
  que la vista posterior las refleja (`checked` en los radios correctos).
- `mis-materias.php` → 200, muestra la materia/grado/sección asignados con
  el porcentaje evaluado correcto.
- `notas-bachillerato.php` → 200, respeta la ventana de fechas
  (`preinscripcion`) y el flag `editable` correspondiente al lapso.
- `notas-bachillerato-guardar.php` → probado dos veces (creación y edición
  de la misma estrategia): `cortes2526` y `cortes12526` se actualizan
  correctamente, `evalua_calendario` inserta en la primera vez y actualiza
  (no duplica) en la segunda cuando el título coincide con `oldObserv`.
- Aislamiento: re-verificado que un docente sin `trgsp`/`trgsmp` para un
  grado/sección/materia dado no puede ver ni guardar notas ahí (redirige
  con `?error=acceso`), tanto en las páginas de vista como en las de
  guardado (no solo confiando en el formulario).
- Los 6 archivos nuevos y 2 modificados (`layout/base.php`, `core/modulos.php`
  no tocado esta fase) pasan `php -l` sin errores.

**No verificado en este ambiente** (limitación de la sesión de prueba, no
del código): el recálculo de `matri{periodo}` cuando el alumno **sí** tiene
fila de matrícula previa (mi alumno sintético no tenía una, y crear una
matrícula sintética completa —50+ columnas— se consideró fuera de alcance
razonable para esta prueba). La lógica se revisó exhaustivamente por
código: es una traducción directa 1:1 de la aritmética del legacy
(`guarnotabachi-n.php`, promedio ponderado por los `porcentaje{corte}{lapso}`
de `cortes1`, `round(..., 0, PHP_ROUND_HALF_UP)`), usando el mismo patrón
`SELECT`+`UPDATE` con prepared statements que ya se verificó funcionando en
`cortes{periodo}`. Recomendado verificar este camino específico contra un
alumno real matriculado antes de habilitar el módulo para uso general.

## Activar el módulo para un colegio

1. Aplicar `db/fase11-portal-docentes-notas.sql` contra `siscev` (ya
   aplicado en local; falta en producción).
2. En `panel_admin` → colegio → módulos → habilitar **Notas de Primaria** y
   **Notas de Bachillerato**.
3. **Antes de habilitar para un colegio que no sea Juan XXIII**: verificar
   los nombres reales de columnas listados en "Hallazgos de esquema"
   arriba (`preinscripcion.iniciaMaestro`/`terminaMaestro` vs
   `iniciaAdultos`/`terminaAdultos`, y las columnas `NOT NULL` sin default
   de `notaprimaria`/`cortes`/`matri`) — pueden variar entre colegios según
   cómo se creó su BD originalmente.

## Pendiente / fuera de este alcance

- Resto de fases (Evaluaciones en línea, Aula Virtual, Comunicación,
  Reportes) — ver `migracion_de_modulo_docentes.md`.
- Verificar el recálculo de `matri{periodo}` contra un alumno con
  matrícula real existente (ver "No verificado" arriba).
- `hist-alu.php`/`list-historial.php`/`rep-cortes.php`/`revisionPdfListado.php`/
  `observ-guia.php` — enlazados desde `listmateprof.php`/`bus-gra-sec2prof.php`
  en el legacy pero fuera del alcance de "cargar notas"; se omitieron esos
  botones en `mis-materias.php`/`notas-primaria.php` hasta que se migren en
  sus fases correspondientes.
