# Migración: Portal Docentes — Fase 4 (Comunicación)

Quinta fase de la migración de `uepca/docentes/` hacia `admin_docentes/`
(ver `migracion_de_modulo_docentes.md` para el plan completo). Cubre
mensajería con estudiantes, comunicados dirigidos a un grado/sección/materia
y observaciones de docente guía.

## Alcance

| Origen (`uepca/docentes/`) | Destino (`admin_docentes/`) | Rol |
|---|---|---|
| `contactoAlum.php`, `chat-buscar.php`, `chat-nuevo.php` | `chat-alumnos.php` + `chat-alumnos-buscar.php` + `chat-alumnos-enviar.php` | Listado de hilos de conversación (solo los que el estudiante ya inició) + responder |
| `list-archivos.php`, `comunicado-buscar.php` | `comunicados.php` | Publicar/listar/ver/eliminar comunicados a un grado/sección/materia asignados al docente |
| `observ-guia.php`, `guarda-obs-guia2.php` | `docente-guia.php` + `docente-guia-guardar.php` | Observaciones (3 por alumno/lapso) para las secciones donde el docente es guía, tomadas de un catálogo de frases reutilizable |

**Consolidación de UX** (mismo criterio que Fase 2/3): los endpoints AJAX de
listar/buscar/ver del legacy se combinaron en 1 página principal + 1-2
endpoints AJAX estrictamente necesarios por dominio, en vez de 1 archivo
por acción.

## Decisiones de diseño

- **Slugs**: `doc-chat-alumnos`, `doc-comunicados`, `doc-guia`, grupo de
  menú "Comunicación" — seed en `db/fase14-portal-docentes-comunicacion.sql`,
  aplicado contra `jesistem_siscev` y habilitado para colegio 11.
- **Gating por `docDa`**: al igual que Calificaciones/Aula Virtual, estas 3
  funcionalidades están atadas a una `materia` (`trgsmp`, bachillerato/
  liceo) — no existe equivalente para maestros de Primaria (`trgsp` no
  tiene granularidad de materia). El sidebar solo muestra "Comunicación" si
  `docDa === 'S'`, igual que ya hace el chat del lado alumno
  (`admin_alumnos/chat-docentes.php` solo lista docentes de `trgsmp`).
- **Chat — solo responde hilos existentes**: igual que el legacy, el
  docente no puede iniciar una conversación nueva con un alumno; solo
  responder hilos que el estudiante ya inició (`admin_alumnos/chat-enviar.php`
  es el único punto donde nace un hilo). `chat-alumnos-enviar.php`
  verifica que ya exista al menos una fila `chat` para esa
  `(idAlum, id_docente, id_materia)` antes de insertar — bloquea mensajes a
  combinaciones inventadas.
- **Aislamiento del chat sin depender del cliente**: `id_docente` en los
  dos endpoints AJAX se toma siempre de `$_SESSION['idAlum']`, nunca del
  POST — así un docente no puede leer ni escribir en hilos ajenos aunque
  falsifique `idAlu`/`idMat` encriptados (la fila simplemente no matchea el
  filtro `id_docente = <sesión>`).
- **Comunicados — verificación de propiedad antes de publicar**: igual que
  `material.php` (Fase 3), antes del `INSERT` se reconfirma contra
  `trgsmp{periodo}` que el docente en sesión tiene esa
  materia/grado/sección asignada — no se confía en los valores ocultos del
  formulario. Al eliminar, el `WHERE` incluye `idDocente = $_SESSION['idAlum']`.
- **Archivos de comunicados**: se guardan en `<raíz-del-repo>/archivos/`
  (mismo directorio que ya lee `admin_alumnos/comunicados.php` del lado
  alumno) y se sirven vía `https://DOMINIO/archivos/<archivo>` — mismo
  patrón/gap ya documentado en `CLAUDE.md` para `fotoalu/`/`fotodoc`/`tareas`
  (dominio legado del colegio, no migrado al esquema multi-tenant). Nombre
  de archivo saneado con `sanear_string()` (helper ya existente en
  `core/funciones.php`, reutilizado en vez de reinventar la
  transliteración que hacía `material.php` inline).
- **Docente guía — tablas nuevas**: `observa_guia` (catálogo de frases) y
  `observa_guia_alum` (3 observaciones por alumno/lapso/período) **no
  existían** en la BD multi-tenant del colegio piloto — la funcionalidad
  nunca se migró de `jesistem_uepca`. Se crean en
  `db/fase14-portal-docentes-comunicacion.sql` Parte B, mismo esquema que
  el legacy, con un seed opcional de las 29 frases institucionales reales
  ya usadas por el colegio (migradas desde `jesistem_uepca.observa_guia`,
  status='1').
- **Docente guía — catálogo editable desde la propia pantalla**: en vez de
  requerir una pantalla de administración aparte para gestionar el
  catálogo de frases (que el legacy tampoco tenía), `docente-guia.php`
  permite agregar una frase nueva directamente (modal simple, `INSERT`);
  queda disponible para todos los docentes guía del colegio (el catálogo
  es global, no por sección).
- **Docente guía — verificación de propiedad en cada guardado**: a
  diferencia del legacy (que confiaba ciegamente en los parámetros de
  `$_GET`), `docente-guia-guardar.php` resuelve el `grado`/`seccion` real
  del alumno por su `cedula` y confirma contra `trgsmp{periodo}` que el
  docente en sesión es guía (`doc_guia='1'`) de esa sección exacta antes de
  escribir — bloquea que un docente guía de una sección escriba
  observaciones sobre un alumno de otra.
- **Lapso**: los docentes no tienen `$_SESSION['lapsoActivo']` (esa
  variable solo se calcula para alumnos en `login.php`, ver
  `migracion_de_modulo_docentes.md` sección 2). `docente-guia.php` resuelve
  el lapso activo con la misma consulta que ya usa `mis-materias.php`
  (`preinscripcion.lapso`), con override opcional por querystring
  (`?lapso=1|2|3`), igual patrón que `lapsoMod` en Calificaciones.

## Activar el módulo para un colegio

1. Aplicar `db/fase14-portal-docentes-comunicacion.sql` contra `siscev`
   (Parte A) y contra la BD del colegio (Parte B) — ya aplicado en local
   para el colegio 11 (incluye el seed de 29 frases).
2. Confirmar que el colegio tiene `trgsmp{periodo}.doc_guia` poblado para
   al menos una fila si se va a usar Docente Guía (columna ya existía,
   usada también por el `docente-guia.php` de solo-lectura del lado
   alumno).

## Verificación

Cuentas sintéticas creadas para esta sesión, autorizadas explícitamente por
el usuario, y **borradas al terminar**:

- Docente sintético (cédula `90000077`, clon de un docente real cargo=3
  para heredar columnas `NOT NULL`) con `trgsmp2526` asignado a grado
  61/sección 1/materia 6111 "Fundamento Humano Cristiano" con
  `doc_guia='1'` (mismos valores reales ya usados por un docente real en
  esa sección, para no inventar un grado/sección/materia inexistente).
- Alumno sintético (cédula `90000078`, clon de un alumno real de esa misma
  sección) para el hilo de chat y las observaciones de docente guía.

Flujo probado de punta a punta vía HTTP (login real + cookies de sesión,
servidor `php -S` local — no había vhost respondiendo en esta máquina para
`http://micolegio.local`):

- `home.php`, `chat-alumnos.php`, `comunicados.php`, `docente-guia.php` →
  200, sin errores/warnings en el HTML de respuesta ni en el log del
  servidor.
- **Chat**: insertado un mensaje sintético "del alumno" directo por SQL
  (simulando lo que ya hace `admin_alumnos/chat-enviar.php`, no se probó
  ese lado porque no es parte de esta fase) → `chat-alumnos.php` mostró la
  tarjeta con badge "1 nuevo". `chat-alumnos-buscar.php` devolvió el
  historial y marcó `visto='1'` en BD (verificado por `SELECT` directo).
  `chat-alumnos-enviar.php` insertó la respuesta del docente
  (`envia='1'`, verificado por `SELECT`). Intento de responder a una
  materia no asignada (`id_materia=9999` encriptado) → `isSuccessful:false`,
  nada insertado (protección de "solo responde hilos existentes"
  funcionando).
- **Comunicados**: publicar con archivo adjunto (multipart) →
  **encontrado y corregido un bug real**: `bind_param('sssiiiiiisi', ...)`
  tenía un tipo de menos (11 caracteres para 12 parámetros) → `Fatal
  error: ArgumentCountError`. Corregido a `'sssiiiiiissi'` (12
  caracteres). Reintentado → 302 a `?publicado=1`, fila verificada en
  `tbl_documentos` (grado/sección/materia/idDocente correctos, `activo`
  por default `'1'`) y archivo verificado en `archivos/`. Listado →
  aparece con su título. Eliminar → 302 a `?eliminado=1`, fila borrada y
  archivo físico eliminado (`unlink` confirmado).
- **Docente guía**: `docente-guia.php` sin querystring detectó
  automáticamente la sección 61/1 como la única donde el docente sintético
  es guía y listó los alumnos reales de esa sección + el sintético.
  `docente-guia-guardar.php` guardó `id_observa`/`id_observa2` para el
  alumno sintético (verificado por `SELECT` en `observa_guia_alum`);
  recargando con `?lapso=1` los combos mostraron la selección persistida
  (`selected` en las opciones correctas — con el lapso activo real de la
  BD, que es `3`, no se veía porque el guardado de prueba fue con
  `lapso=1` explícito, no es un bug). Cédula inexistente →
  `isSuccessful:false` sin escribir nada. "Agregar frase nueva" → 302,
  frase insertada en `observa_guia` (borrada en la limpieza).
- Los 6 archivos nuevos de esta fase y `layout/base.php` pasan `php -l`
  sin errores.

**No verificado en este ambiente**: el guardado de una observación sobre un
alumno de una sección donde el docente NO es guía (para confirmar el
bloqueo de propiedad en `docente-guia-guardar.php`) — el clasificador de
permisos bloqueó esa prueba puntual porque hubiera requerido usar la
cédula de un alumno **real** para el intento (aunque el intento debía
fallar). Se confirmó la lógica por revisión de código: el `WHERE` cruza
`alumcer.grado/seccion` del alumno consultado contra `trgsmp.doc_guia='1'
AND ced_prof=<sesión>`, así que cualquier alumno fuera de la sección
asignada del docente no matchea y el `if (!$esGuia)` corta antes del
`INSERT`/`UPDATE`.

## Hallazgo de código (no es higiene de datos esta vez)

A diferencia de la Fase 3, no se encontró basura de pruebas manuales
previas en la BD real. El único hallazgo fue el bug de `bind_param` en
`comunicados.php` descrito arriba, capturado y corregido durante esta
misma sesión de verificación antes de dar la fase por terminada.
