Avisos por capas: sonido, toast, notificación y título
En una bandeja de mensajes compartida, un agente necesita enterarse cuando llega algo nuevo. La primera versión hacía lo obvio: disparar una notificación del sistema con cada mensaje. En pocas horas era ruido. El agente, que ya estaba mirando la bandeja, recibía una notificación de algo que tenía delante, y la reacción natural fue bloquear las notificaciones del sitio. Justo el canal que más importaba quedó cerrado.
Un aviso según dónde mira la persona
La solución fue dejar de pensar en un aviso y pensar en capas. Cada situación recibe el aviso mínimo que necesita:
- Está en la pantalla que muestra el dato: solo un sonido breve. La fila nueva aparece marcada sola. Nada de notificación del sistema.
- Está en otra pantalla de la app: sonido y un aviso flotante dentro de la app, con un botón para ir al mensaje.
- Está en otra pestaña o en otra aplicación: notificación del sistema.
- Siempre: un contador en el título de la pestaña y un indicador en el menú.
La notificación, solo si la pestaña está oculta
La API de visibilidad de página indica si la pestaña está en segundo plano. Condicionar la notificación del sistema a ese estado es lo que la vuelve útil: aparece solo cuando la persona no puede ver la app. Si llegan varios mensajes seguidos, conviene usar una etiqueta común con la opción de volver a notificar, para que se reemplacen en lugar de apilarse.
El contador en el título
Es la capa más barata y probablemente la más efectiva. No pide permisos, no interrumpe y funciona con la pestaña enterrada entre muchas otras. Un número entre paréntesis al inicio del título se ve en la barra de pestañas y en la barra de tareas del sistema operativo.
Tres detalles que costaron una iteración cada uno
La lógica va en el armazón, no en la pantalla
La primera implementación vivía dentro del componente de la bandeja. En cualquier otra sección de la app no llegaba ningún aviso, porque ese componente no estaba montado. La detección y los avisos deben vivir en el armazón de la aplicación, que está presente en todas las pantallas.
El aviso flotante se cierra al usar su acción
Al pulsar "Abrir", la app navegaba al mensaje y el aviso seguía encima, tapando la pantalla a la que acababa de llevar. La función que muestra avisos devuelve un identificador y existe otra para cerrarlo, que la acción llama antes de navegar.
La acción lleva al elemento, no a la sección
En el celular, la bandeja abre primero la lista y después la conversación. Un botón que lleva a "la bandeja" deja a la persona buscando el mensaje a mano. La acción pasa el identificador de la conversación en la URL, la pantalla la abre directamente y luego limpia el parámetro sin agregar una entrada al historial.
Consultar sin traer todo
Para saber si hay algo nuevo desde cualquier pantalla, cada pocos segundos, no conviene pedir la lista completa de conversaciones. Un endpoint liviano devuelve solo el total de no leídos y de quién es el último. Es una respuesta de pocos bytes que se puede consultar seguido sin cargar al servidor ni a la red.
El sonido tiene su propia trampa
Para el sonido conviene sintetizar un tono corto con la Web Audio API en vez de cargar un archivo. Pero crear un contexto de audio por aviso deja de funcionar después de unos pocos avisos, sin ningún error visible. Esa historia está en la nota sobre el sonido que dejó de sonar.