Antes, el aviso de “alguien ha comunicado una baja” o “alguien se ha apuntado”
se generaba desde el propio navegador: planificador.html sondeaba
Supabase cada 60 segundos mientras la página estaba abierta, y desde ahí
mandaba el mensaje de Telegram. En cuanto cerrabas la app, bloqueabas el
móvil un rato, o Android mataba la pestaña en segundo plano (todos los
fabricantes lo hacen para ahorrar batería — Xiaomi/Huawei/Samsung de forma
especialmente agresiva), el sondeo se paraba y no llegaba nada más. Esto es
así en cualquier sitio web que dependa de JavaScript de página para
avisar — no es un fallo puntual, es la naturaleza del mecanismo.
La solución: que sea Supabase quien mande el aviso a Telegram, en el momento exacto en que se guarda la fila en la base de datos — sin pasar por tu navegador para nada. Así llega igual con el móvil bloqueado o la app cerrada.
Esto se hace con dos piezas de Supabase, ambas configurables desde su panel web (no hace falta instalar nada ni usar la terminal):
supabase/functions/notificar-telegram/index.ts,
ya escrita en este repo) — recibe el aviso de Supabase y llama a la API
de Telegram.bajas (o en refuerzos), llama a esa función”./newbot →
sigue los pasos → te da un token (algo como 123456789:AAbc...)./start) para que sepa quién eres.notificar-telegram.supabase/functions/notificar-telegram/index.ts
de este repositorio.TELEGRAM_BOT_TOKEN = el token del paso 1TELEGRAM_CHAT_ID = tu Chat ID del paso 1(SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY ya están disponibles
automáticamente en toda Edge Function, no hace falta añadirlas.)
Baja nueva → Telegrambajasnotificar-telegramPOST (por defecto)Apunte nuevo → Telegramrefuerzosnotificar-telegramAl elegir el tipo “Supabase Edge Functions” (en vez de “HTTP Request”), Supabase añade la autenticación necesaria automáticamente — no hay que tocar cabeceras a mano.
sustituciones.html en cualquier navegador),
comunica una baja de prueba o apúntate a un turno.planificador.html esté completamente cerrado.notificar-telegram → pestaña
Logs, para ver si la función se ejecutó y qué error dio (token/chat ID
mal puestos, tabla inesperada, etc.).Con lo anterior te avisa cuando alguien se da de baja o se apunta. Lo que no avisaba (ni dejaba rastro con hora en ninguna tabla) es lo contrario: un apunte cancelado, una baja anulada o alguien quitado de un turno. Esto lo resuelve.
supabase/01_actividad.sql → Run. Crea la tabla
actividad y unos disparadores que anotan, con hora, cada alta/baja de apuntes,
bajas y equipos, lo haga quien lo haga y aunque la app esté cerrada. También hace
que una baja que se vuelve a registrar sobre otra anulada cuente como nueva
(antes no generaba aviso, porque el sistema la trataba como una modificación).notificar-telegram → pega de nuevo el código actualizado de
index.ts → Deploy.bajas y marca también Update (además de Insert).actividad, evento Insert, tipo Supabase Edge
Functions → notificar-telegram. (Solo genera aviso para apunte cancelado y
baja anulada; lo demás ya tenía el suyo.)El registro guarda 90 días (lo limpia la tarea del paso B).
Cada hora, Supabase revisa los turnos de las próximas 48 h y te avisa por Telegram de los que no llegan al mínimo de voluntarios, con quién hay, quién dio baja y el enlace a Sustituciones. No repite el aviso: solo vuelve a escribir si cambia la situación (alguien se apunta o se da de baja), y te dice “✅ Ya cubierto” cuando se resuelve. No escribe entre las 22:00 y las 08:00 (hora de Madrid).
avisos-turnos → pega el contenido de
supabase/functions/avisos-turnos/index.ts
(un solo archivo) → Deploy.TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_IDCRON_SECRET = un texto largo cualquiera (inventado por ti)MIN_EQ (mínimo por turno, 3 por defecto) y HORIZONTE_H (48).supabase/02_avisos.sql, cambia
CAMBIA_ESTE_SECRETO por el mismo texto de CRON_SECRET y ejecútalo. Crea la
tabla avisos_enviados y programa la tarea horaria.Probar sin esperar a la hora en punto, con la función en modo simulación (no envía nada, solo muestra qué avisaría):
curl -X POST "https://inifdflvblnonzowbxie.supabase.co/functions/v1/avisos-turnos?dry=1" -H "x-cron-secret: TU_SECRETO"
Para un aviso real, la misma llamada sin ?dry=1 (dentro de 08:00–21:59).
Turnos anulados. Si en el planificador anulas un turno (Bajas → 🚫 Anular
turno), esta función deja de avisar de él. Si ya la tenías desplegada, vuelve
a pegar el código actualizado de index.ts y pulsa Deploy (necesita la tabla
de la Ampliación C; si aún no existe, la ignora).
avisos-turnos → Logs, y en SQL:
select * from net._http_response order by id desc limit 5;Al importar cuadrantes de meses anteriores (Estadísticas → 📥 Importar turnos anteriores), las franjas en blanco o canceladas se guardan en una tabla propia.
supabase/03_turnos_no_realizados.sql →
Run. Crea la tabla turnos_no_realizados (solo fechas y franjas, sin nombres).select * from public.turnos_no_realizados; debe devolver 0 filas sin error.Sin este paso, la importación sigue funcionando pero solo guarda las asignaciones (la ventana de importación lo avisa).
Para indicar “en abril hubo al menos 2 bajas” sin saber cuáles (Estadísticas → 🩺 Calidad de los datos → ✎ Indicar bajas).
supabase/04_ajustes_estadisticas.sql → Run.
Crea la tabla ajustes_estadisticas (un mes, un número y una nota).select * from public.ajustes_estadisticas; debe devolver 0 filas sin error.Sin este paso, el botón “Indicar bajas” te avisa de que falta la tabla.
Para meses como julio y agosto, gestionados con el formulario de apuntes de Sustituciones y sin cuadrante, los avisos que el bot te mandó a Telegram guardan quién se apuntó y quién se dio de baja. La app los lee del chat exportado.
supabase/05_archivo_apuntes_bajas.sql → Run.
Crea apuntes_archivo y bajas_archivo. Son tablas aparte a propósito: refuerzos
y bajas disparan avisos a Telegram y no quieres un mensaje por cada apunte antiguo.result.json. Revisa la
vista previa (apuntes y bajas nuevos, cuáles ya existían, cuántos turnos llegan a 3),
asigna a quién corresponde cada nombre dudoso y pulsa Importar. Se puede
deshacer desde la misma ventana.Límites que conviene saber:
MIN_EQ (3) o más voluntarios, y quienes asistieron suman en sus estadísticas.Para un mes que no aparece en la app y que no se puede recuperar: sin actividad (no hubo turnos, p. ej. agosto) o sin datos (los hubo, pero no queda registro, p. ej. julio si el bot que avisaba era otro). Así deja de avisarse como hueco y no cuenta en las cifras.
supabase/06_marca_mes.sql → Run
(requiere haber ejecutado antes el 04).Para turnos cuyo registro no cuenta lo que pasó, sobre todo de meses gestionados fuera de la app: el que se iba a caer por las bajas y salió gracias a los apuntes, el que solo estaba en apuntes y salió adelante, o el que tenía 3 confirmadas y acabaron siendo 4.
supabase/07_turnos_resultado.sql → Run (si ya lo
habías ejecutado, vuelve a ejecutarlo: añade la columna tipo).Lo indicado manda sobre el registro. Si marcas cualquiera de las dos pero el turno no llega a
MIN_EQ (3) personas, no cuenta como realizado. Solo se pueden indicar turnos que ya existen en
la app (si falta alguno, impórtalo antes).
La sección 🙋 Turnos que salieron gracias a los apuntes da la cifra de todo el histórico (sin depender del periodo) con el desglose por mes, separando 🛟 salvados tras una baja y 🆕 solo en apuntes; junta lo que el registro muestra y lo indicado a mano (✍).
El cuadrante ahora avisa de los apuntes. Antes, un voluntario apuntado en Sustituciones para un turno que ya tenía equipo (o que resolvía el motor) no aparecía por ningún sitio del cuadrante — solo “disponibles adicionales” calculado por disponibilidad general, no por apuntes reales. Ahora, si alguien está apuntado a un turno concreto, sale ahí con ✋, aunque el turno ya esté confirmado. Y en Apuntes, cada tarjeta muestra si ese turno ya tiene equipo confirmado (y cuántos) antes de la lista de apuntados, para no confundir “hay un apunte” con “el turno está sin cubrir”.
Franjas alternativas. En Calendario, cada franja habilitada se puede marcar como 🔀 alternativa (clic otra vez sobre el botón ya habilitado). Se sigue ofreciendo igual en Sustituciones, pero si al final nadie la cubre no cuenta como turno perdido en las estadísticas — se ofrecía por si acaso, no era un hueco que hubiera que cubrir sí o sí. Si sí se cubre, cuenta como cualquier otro turno.
Los apuntes de días ya pasados dejan de mostrarse. En Sustituciones → Mis apuntes, un apunte de un día que ya pasó no aparece más (si se confirmó, ya está en el cuadrante; si no, no hay nada que hacer con él). El dato se conserva en la base de datos para las estadísticas, solo deja de listarse.
Hasta aquí todos los avisos van siempre al mismo sitio: tu Telegram, como coordinador. Esto es distinto: que cada voluntario reciba su propio correo cuando se registra su baja, cuando se confirma un turno suyo, cuando le quitan uno ya confirmado, y un recordatorio el día antes de su turno (con una nota de “lleva la llave” si es portador). También manda un recibo por correo al guardar la disponibilidad por bloques.
El email de cada voluntario lo pones tú, desde su ficha en el planificador — no se pide en el formulario público, para no fiarse de que cada uno lo escriba bien.
supabase/12_notificar_email.sql,
cambia CAMBIA_ESTE_SECRETO por un texto largo inventado por ti → Run.
Añade voluntarios.email, la tabla recordatorios_enviados y la tarea
diaria.notificar-email → pega
index.ts → Deploy.
Secrets: RESEND_API_KEY (paso 1), RESEND_FROM (p. ej.
Turnos Puerto <avisos@asispuerto.org>, con el remitente verificado en
Resend).Actividad → correo voluntarioactividad, Events: solo Insertnotificar-emailrecordatorios-turnos → pega
index.ts → Deploy.
Desactiva Verify JWT (la protege CRON_SECRET, igual que
avisos-turnos). Secrets: RESEND_API_KEY, RESEND_FROM (los
mismos del paso 3) y CRON_SECRET_RECORDATORIOS = el mismo texto del
paso 2 (no CRON_SECRET: ese ya lo usa avisos-turnos).curl -X POST "https://inifdflvblnonzowbxie.supabase.co/functions/v1/recordatorios-turnos" -H "x-cron-secret: TU_SECRETO"
notificar-email o recordatorios-turnos
→ Logs. Un voluntario sin correo guardado nunca da error, simplemente
no recibe nada (comprobar con select nombre, email from public.voluntarios
where email is not null;).planificador.html instalado como PWA (icono en pantalla de inicio,
pantalla completa) sigue funcionando igual; el manifest y el service
worker ahora son archivos reales (manifest.webmanifest, sw.js) en vez
de generarse al vuelo, lo que hace la instalación en Android más fiable.En vez de (o además de) Telegram, existe Web Push: notificaciones del
sistema con el nombre e icono de “Turnos Puerto” en vez de llegar como
mensaje de Telegram. Necesita generar un par de claves VAPID, guardar la
“suscripción” push de tu móvil en Supabase, y que notificar-telegram (o
una función hermana) llame al endpoint de push en vez de a Telegram. Es más
trabajo de configurar y, en algunos móviles con gestión de batería muy
agresiva, menos fiable que Telegram. Si lo quieres más adelante, dímelo y lo
montamos.