React y TypeScript sobre Vite, con Capacitor empaquetando a Android. El APK escribe en el calendario del sistema y programa notificaciones locales: el aviso sale con la aplicación cerrada y el evento queda en la agenda que la persona ya consulta. En la web no hay calendario del sistema que tocar, así que el mismo botón descarga un archivo .ics con las sesiones sin cancelar.
Los recordatorios del móvil no los empuja el servidor: la aplicación lee las mismas reglas de la API y proyecta el ciclo entero hacia el futuro, una notificación por momento, saltando las sesiones empezadas o canceladas. El identificador de cada una sale de hashear grupo · sesión · regla · momento, así que resincronizar al mismo grupo no genera duplicados. Y hay un techo de doscientas alarmas programadas, porque Android las admite y una mesa con muchas sesiones te come la cola del sistema.
Dos detalles que solo se escriben después de leerse la documentación de Android: hay que pedir el permiso de alarma exacta aparte del permiso de notificar —si no, el sistema agrupa los avisos y llegan tarde—, y al cambiar una regla hay que cancelar las pendientes de ese grupo antes de volver a programar, filtrando por el identificador del grupo que la propia app guardó en la carga extra de cada aviso.
El servidor no se entera de ninguna de las dos cosas. La API es idéntica para el navegador que para el móvil, así que la app es un cliente más y no una segunda fuente de verdad que mantener sincronizada.
En desarrollo, el SMTP apunta a Mailpit en el puerto 1025: los mensajes se construyen y se reciben de verdad, pero ninguno sale a internet. Se puede repetir un flujo de avisos completo sin bombardear buzones reales, y esa comodidad es la que mantiene el correo bajo prueba.