# Notificaciones por Telegram a representantes

Avisa a los representantes por Telegram cuando: un docente carga una evaluación,
se carga/actualiza una nota de corte, o se habilita la descarga de un boletín.

El sistema que carga evaluaciones/notas **no vive en este repositorio** (no hay
código de "docentes" acá; algo externo escribe directo en la BD del colegio).
Por eso la detección se hace con **triggers de MySQL** sobre las tablas que esa
app externa escribe, no interceptando su código.

## Arquitectura

```
docente carga evaluación/nota  →  TRIGGER MySQL  →  notif_telegram_queue
                                                           │
cron (cada 10-15 min) ─── telegram-enviar-notificaciones.php
                                                           │
                                          agrupa por representante y envía
                                                           │
                                                    Telegram Bot API
                                                           │
                                                  chat del representante
```

Vinculación (una sola vez por representante):

```
alumno ve banner en el portal (admin_alumnos/layout/base.php)
        → link https://t.me/<bot>?start=<token cifrado de su cédula de rep.>
        → representante presiona "Iniciar" en Telegram
        → Telegram POST a telegram-webhook.php?colegio=<id_colegio>
        → UPDATE represe.telegram_chat_id
```

Un representante con varios alumnos solo necesita vincularse **una vez** (el
vínculo es por `represe.cedula`, no por alumno) — cualquiera de sus hijos verá
el banner hasta que se vincule, y una vez vinculado deja de aparecer para todos.

## Piezas del sistema

| Archivo | Rol |
|---|---|
| `db/colegios/colegiojuanxxiii-telegram.sql` | Columnas `represe.telegram_chat_id`/`telegram_linked_at`, tabla `notif_telegram_queue`, columnas de seguimiento en `preinscripcion`, triggers de evaluación y nota. |
| `core/telegram.php` | Helpers: `telegram_enviar_mensaje()`, `telegram_deep_link()`, cifrado/decifrado del token del deep-link. |
| `telegram-webhook.php` | Endpoint público que Telegram llama cuando el representante presiona "Iniciar". Vincula `telegram_chat_id`. |
| `telegram-set-webhook.php` | CLI: registra el webhook del bot de un colegio ante Telegram (`setWebhook`). |
| `telegram-enviar-notificaciones.php` | CLI (cron): revisa boletines habilitados por fecha y drena `notif_telegram_queue`. |
| `admin_alumnos/layout/base.php` | Banner animado (se oculta solo si el representante ya está vinculado). |

## Constantes institucionales requeridas

En `panel_admin/colegio.php` → "Constantes institucionales", por colegio:

- `TELEGRAM_BOT_TOKEN` — token del bot (de @BotFather).
- `TELEGRAM_BOT_NAME` — username del bot sin `@` (ej. `ColegioJuanXXIIIBot`).

`TELEGRAM_WEBHOOK_SECRET` se genera solo (no cargarla a mano): la crea
`telegram_registrar_webhook()` (`core/telegram.php`) al registrar el webhook, sea
desde el botón del panel o desde el CLI. Si quedó cargada a mano con un valor que
Telegram no acepta como `secret_token` (solo `[A-Za-z0-9_-]`), se reemplaza sola en
el siguiente registro.

## Puesta en marcha para un colegio (ej. colegiojuanxxiii / id 11)

1. Cargar `TELEGRAM_BOT_TOKEN` y `TELEGRAM_BOT_NAME` en Constantes institucionales.
2. Aplicar la migración a la BD del colegio:
   ```
   mysql -u root jesistem_juanxxiii < db/colegios/colegiojuanxxiii-telegram.sql
   ```
3. Registrar el webhook, con el botón **"Registrar webhook en Telegram"** de
   `panel_admin/colegio.php` (tarjeta *Notificaciones Telegram*), o por CLI:
   ```
   php telegram-set-webhook.php 11
   ```
   Ojo: registra la URL que arma `PORTAL_URL`, así que hay que hacerlo desde el
   entorno de producción — si se corre en local, Telegram queda apuntando a un
   host que no puede alcanzar.
4. Agregar el cron de envío (una sola entrada cubre **todos** los colegios configurados):
   ```
   */10 * * * * php /ruta/al/repo/telegram-enviar-notificaciones.php >> /ruta/logs/telegram.log 2>&1
   ```
5. Probar: iniciar sesión como alumno, ver el banner, presionar "Vincular ahora",
   confirmar el mensaje de bienvenida en Telegram y que el banner desaparece
   al recargar el portal.

## Mantenimiento cada año escolar (¡obligatorio!)

Las notas viven en tablas con el período en el nombre (`cortes2627`,
`materiass2627`, …), así que el trigger de notas apunta a tablas concretas y
**hay que recrearlo** cuando arranca el nuevo período (ej. `cortes2728`):

```sql
DROP TRIGGER IF EXISTS trg_cortes2627_notif;

DELIMITER $$
CREATE TRIGGER trg_cortes2728_notif
AFTER UPDATE ON cortes2728
FOR EACH ROW
BEGIN
  IF CONCAT_WS('|', NEW.nota11,NEW.nota21,NEW.nota31,NEW.nota41,NEW.nota51,NEW.nota61,
                    NEW.nota12,NEW.nota22,NEW.nota32,NEW.nota42,NEW.nota52,NEW.nota62,
                    NEW.nota13,NEW.nota23,NEW.nota33,NEW.nota43,NEW.nota53,NEW.nota63)
     <> CONCAT_WS('|', OLD.nota11,OLD.nota21,OLD.nota31,OLD.nota41,OLD.nota51,OLD.nota61,
                       OLD.nota12,OLD.nota22,OLD.nota32,OLD.nota42,OLD.nota52,OLD.nota62,
                       OLD.nota13,OLD.nota23,OLD.nota33,OLD.nota43,OLD.nota53,OLD.nota63)
  THEN
    INSERT INTO notif_telegram_queue (ced_rep, tipo, mensaje, origen_id)
    SELECT R.cedula, 'nota',
      CONCAT('📊 Se actualizaron notas de ', A.nombre, ' ', A.apellido, ' en ', M.nombremate, '.'),
      NEW.id
    FROM alumcer A
    JOIN represe R ON R.cedula = A.ced_rep
    JOIN materiass2728 M ON M.codigo = NEW.cod_materia
    WHERE A.cedula = NEW.ced_alu AND R.telegram_chat_id IS NOT NULL;
  END IF;
END$$
DELIMITER ;
```

Solo cambia el sufijo del período en tres lugares: el nombre del trigger, la
tabla `AFTER UPDATE ON`, y el JOIN a `materiass<periodo>`. El trigger de
evaluación (`trg_evalua_calendario_notif`) y la tabla `notif_telegram_queue`
**no** cambian de nombre entre años — no requieren tocarlos.

Si el colegio cambia de rango de grados para primaria/liceo, revisar también
las condiciones `grado >= 61` / `grado BETWEEN 41 AND 60` en
`telegram-enviar-notificaciones.php` (función `_revisar_boletines`).

## Onboardear un colegio nuevo

1. Crear `db/colegios/<carpeta>-telegram.sql` copiando el de colegiojuanxxiii,
   ajustando el nombre de tabla `cortes<periodo>`/`materiass<periodo>` al
   período activo de ese colegio.
2. Aplicarlo a la BD del colegio.
3. Cargar `TELEGRAM_BOT_TOKEN`/`TELEGRAM_BOT_NAME` en Constantes institucionales.
4. `php telegram-set-webhook.php <id_colegio>`.
5. Nada que tocar en el cron: `telegram-enviar-notificaciones.php` ya recorre
   todos los colegios con `TELEGRAM_BOT_TOKEN` configurado.

## Por qué "boletín habilitado" NO es un trigger

`preinscripcion.publicarBoleta`/`publicarPrimaria` son fechas ("a partir de
tal día se puede descargar"), no una acción puntual. Un trigger reaccionaría
en el momento en que el admin *escribe* la fecha (que puede ser días antes de
que realmente aplique), no cuando el boletín *realmente* queda disponible.
Por eso ese evento se resuelve comparando `CURDATE()` contra la fecha en cada
corrida del cron (`_revisar_boletines()` en `telegram-enviar-notificaciones.php`),
con `notif_boleta_valor`/`notif_primaria_valor` para no reenviar el mismo aviso
cada corrida.

## Seguridad

- El webhook valida el header `X-Telegram-Bot-Api-Secret-Token` contra
  `TELEGRAM_WEBHOOK_SECRET` — sin él, cualquiera que adivine la URL podría
  simular una vinculación falsa.
- El token del deep-link usa el mismo cifrado (`encriptar()`/`desencriptar()`)
  que el resto del portal, adaptado a base64url para caber en el parámetro
  `start` de Telegram (que no admite `+ / =`).
- El trigger de nota/evaluación solo encola si `represe.telegram_chat_id IS
  NOT NULL`: un representante no vinculado nunca acumula mensajes atrasados;
  al vincularse solo recibe notificaciones de eventos futuros.
