Manejo de Notificaciones Push con Expo
En el desarrollo de aplicaciones móviles modernas, la capacidad de comunicarse con los usuarios más allá de la sesión activa es fundamental. Las notificaciones push representan uno de los mecanismos más efectivos para re-enganchar usuarios, informar sobre actualizaciones o recordar acciones pendientes. En el ecosistema de React Native con Expo, esta funcionalidad, que puede parecer compleja por su interacción con servicios nativos de iOS y Android, se simplifica enormemente gracias a un conjunto de herramientas unificadas y bien documentadas. Esta lección te guiará desde la configuración básica hasta la implementación de flujos avanzados de notificaciones, asegurando que comprendas no solo el "cómo", sino también el "por qué" detrás de cada paso, preparando tu aplicación para un despliegue en producción.
Concepto Clave: El Canal de Comunicación Asíncrono
Imagina que tu aplicación es una casa y el usuario es su residente. Una notificación push es como un cartero que llega a la puerta con un mensaje importante, incluso cuando el residente no está esperando específicamente correo. El sistema operativo del dispositivo (iOS o Android) actúa como el administrador de la urbanización: define las reglas sobre cuándo puede tocar el timbre el cartero, cómo debe presentarse y si el residente ha dado permiso para recibir paquetes. Expo, en esta analogía, es el servicio de correos consolidado que contrata el desarrollador. En lugar de tener que negociar por separado con los servicios de correo de Apple (APNs) y Google (FCM), Expo proporciona una única API y un servidor de envío (Push Notifications Service) que maneja toda la complejidad de traducir y enviar el mensaje al servicio correcto según el dispositivo del usuario.
El flujo fundamental implica tres actores principales: tu servidor backend (o un servicio en la nube como Firebase Cloud Functions), el Servicio de Notificaciones Push de Expo (Expo Push Notifications Service - EPNS), y la aplicación cliente instalada en el dispositivo del usuario. La magia comienza cuando la aplicación se instala y solicita permiso al usuario para recibir notificaciones. Si se concede, la aplicación obtiene un identificador único llamado Expo Push Token. Este token es una dirección postal específica para ese dispositivo y esa aplicación. Tu servidor guarda esta "dirección". Cuando necesitas enviar una notificación, tu servidor envía un mensaje junto con el token al EPNS. Expo se encarga de encontrar el dispositivo correcto (ya sea a través de APNs o FCM) y entregar la notificación, la cual será mostrada por el sistema operativo, incluso si la aplicación está en segundo plano o completamente cerrada.
Cómo Funciona en la Práctica: Configuración y Flujo Paso a Paso
El primer paso práctico es configurar tu proyecto Expo. Necesitarás instalar el paquete expo-notifications. Este paquete es la piedra angular, ya que proporciona las APIs para solicitar permisos, manejar tokens, recibir notificaciones y programar notificaciones locales. La instalación se realiza fácilmente con el comando npx expo install expo-notifications. Es crucial realizar esta instalación con el comando `expo install` en lugar de npm o yarn genéricos, ya que garantiza que obtendrás una versión compatible con el SDK de Expo que estés utilizando. Una vez instalado, el siguiente paso es configurar el archivo app.json (o app.config.js) con los identificadores y permisos necesarios para cada plataforma.
El flujo de trabajo se puede dividir en dos grandes ramas: la configuración del cliente (tu app) y la configuración del servidor (para enviar notificaciones). En el cliente, tu tarea es: 1) Solicitar permisos al usuario de manera estratégica (no al inicio de la app, sino en un contexto donde entienda el valor), 2) Obtener y guardar el Expo Push Token en tu backend, 3) Escuchar y manejar las notificaciones cuando llegan (tanto en primer como en segundo plano), y 4) Gestionar la interacción del usuario al tocar la notificación. En el servidor, necesitarás un endpoint seguro para almacenar los tokens asociados a los usuarios y luego, cuando sea necesario, construir un objeto de mensaje y hacer una solicitud HTTP POST al endpoint del EPNS (https://exp.host/--/api/v2/push/send) con tu token de acceso de Expo.
Un paso crítico y a menudo subestimado es la configuración de credenciales para iOS y Android. Para iOS, necesitarás generar un Key de Autenticación de APNs (o un Certificado Push) en tu cuenta de Apple Developer y subirlo a tu proyecto en el dashboard de Expo. Para Android, Expo genera automáticamente las credenciales de Firebase Cloud Messaging (FCM) cuando construyes el proyecto, pero debes asegurarte de que tu aplicación esté correctamente configurada en el archivo app.json. Sin estos pasos, las notificaciones no llegarán a los dispositivos físicos, aunque funcionen en el simulador durante el desarrollo.
Código en Acción: Implementación Completa en el Cliente
A continuación, veremos un ejemplo completo y funcional de cómo configurar el manejo de notificaciones en el lado del cliente. Este código asume que tienes un componente principal (como App.js) y un contexto o sistema de estado para manejar la sesión del usuario. El objetivo es registrar el token al iniciar sesión y configurar los listeners para las notificaciones entrantes.
// App.js
import React, { useEffect, useRef, useState } from 'react';
import { Text, View, Button, Alert, Platform } from 'react-native';
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
// Configurar el manejo de notificaciones cuando llegan
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export default function App() {
const [expoPushToken, setExpoPushToken] = useState('');
const [notification, setNotification] = useState(false);
const notificationListener = useRef();
const responseListener = useRef();
useEffect(() => {
// Solicitar permisos y registrar para notificaciones push
registerForPushNotificationsAsync().then(token => {
if (token) {
setExpoPushToken(token);
// ¡IMPORTANTE! Envía este token a tu servidor backend aquí.
console.log('Token para guardar en backend:', token);
sendTokenToBackend(token);
}
});
// Este listener se activa cuando una notificación llega y la app está en primer plano
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
// Este listener se activa cuando el usuario TOCA una notificación
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
console.log('Usuario tocó la notificación:', response);
// Navegar a una pantalla específica basada en los datos de la notificación
const data = response.notification.request.content.data;
if (data?.screen) {
// Usa tu navegador (React Navigation) para ir a la pantalla 'data.screen'
// navigation.navigate(data.screen);
}
});
// Limpieza al desmontar el componente
return () => {
Notifications.removeNotificationSubscription(notificationListener.current);
Notifications.removeNotificationSubscription(responseListener.current);
};
}, []);
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
Alert.alert('Error', 'Se necesitan permisos para recibir notificaciones.');
return null;
}
// Obtener el token push de Expo
token = (await Notifications.getExpoPushTokenAsync({
projectId: Constants.expoConfig?.extra?.eas?.projectId, // Necesario para EAS
})).data;
console.log('Token obtenido:', token);
} else {
Alert.alert('Debes usar un dispositivo físico para notificaciones push.');
}
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
async function sendTokenToBackend(token) {
// Reemplaza con la llamada a tu API real
try {
const response = await fetch('https://tu-api.com/save-push-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userAuthToken}`, // Asume un token de autenticación
},
body: JSON.stringify({
expoPushToken: token,
userId: currentUserId, // Asegúrate de tener el ID del usuario
}),
});
const data = await response.json();
console.log('Token guardado en backend:', data);
} catch (error) {
console.error('Error al enviar token al backend:', error);
}
}
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Token: {expoPushToken ? `${expoPushToken.substring(0, 30)}...` : 'Cargando...'}</Text>
<Button
title="Simular Notificación Local"
() => {
await Notifications.scheduleNotificationAsync({
content: {
title: 'Recordatorio Local',
body: '¡Esto es una notificación programada localmente!',
data: { screen: 'Details' },
},
trigger: { seconds: 2 },
});
}}
/>
</View>
);
}
Envío desde el Servidor: Ejemplo con Node.js
Una vez que tu aplicación cliente está registrando y enviando tokens a tu servidor, necesitas la capacidad de enviar notificaciones. El siguiente ejemplo muestra un endpoint de servidor simple construido con Node.js y Express que recibe una solicitud para enviar una notificación a un token específico. Este código debe residir en tu backend seguro.
// server.js (Backend Node.js/Express)
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
// Tu Access Token de Expo. ¡NUNCA lo expongas en el cliente!
// Obtenlo de: https://expo.dev/accounts/[tu-usuario]/settings/access-tokens
const EXPO_ACCESS_TOKEN = 'tu_access_token_de_expo_aqui';
const EXPO_PUSH_URL = 'https://exp.host/--/api/v2/push/send';
// Endpoint para enviar una notificación push
app.post('/send-notification', async (req, res) => {
const { to, title, body, data } = req.body;
// Validación básica
if (!to || !title) {
return res.status(400).json({ error: 'Faltan campos "to" (token) o "title".' });
}
const message = {
to, // El Expo Push Token
sound: 'default',
title,
body: body || '',
data: data || {}, // Datos adicionales para manejar en el cliente
// Opciones avanzadas:
// priority: 'high', // Para Android
// subtitle: 'Subtítulo', // Para iOS
// badge: 1, // Número para el badge de la app (iOS)
};
// Opcional: Enviar a múltiples tokens usando un array en 'to'
// const message = { to: ['token1', 'token2'], ... };
const headers = {
'Accept': 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
'Authorization': `Bearer ${EXPO_ACCESS_TOKEN}`, // Usar el token de acceso
};
try {
const response = await axios.post(EXPO_PUSH_URL, message, { headers });
const ticket = response.data.data;
// El ticket contiene un ID. Para notificaciones, los tickets pueden ser de tipo 'error'.
// Es buena práctica verificar el estado de los tickets más tarde.
console.log('Ticket de envío:', ticket);
// Verificación rápida de errores en el ticket
if (ticket && ticket.status === 'error') {
console.error('Error en el ticket:', ticket.details?.error);
// Si el token es inválido, deberías eliminarlo de tu base de datos.
if (ticket.details?.error === 'DeviceNotRegistered') {
await removeInvalidTokenFromDatabase(to);
}
}
res.json({ success: true, ticket });
} catch (error) {
console.error('Error al enviar notificación:', error.response?.data || error.message);
res.status(500).json({ error: 'Fallo al enviar la notificación' });
}
});
// Función de ejemplo para limpiar tokens inválidos
async function removeInvalidTokenFromDatabase(token) {
// Lógica para eliminar el token de tu base de datos
console.log(`Eliminando token inválido: ${token}`);
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Servidor listo en puerto ${PORT}`));
Errores Comunes y Cómo Evitarlos
El camino hacia las notificaciones push estables está pavimentado con errores comunes. Identificarlos temprano te ahorrará horas de depuración.
1. Solicitar permisos en el momento equivocado: Pedir permiso para notificaciones apenas el usuario abre la app por primera vez suele resultar en un "No". Los usuarios necesitan entender el valor. Solución: Implementa un flujo contextual. Por ejemplo, solicita permisos después de que el usuario complete una acción positiva (como una primera compra) o en la configuración de la app, con una explicación clara de los beneficios ("Te avisaremos cuando tu pedido esté listo").
2. No manejar la revocación de permisos o tokens inválidos: Los usuarios pueden desactivar las notificaciones en la configuración del sistema, y los tokens pueden volverse inválidos (app desinstalada). Si sigues enviando mensajes, desperdicias recursos y afectas tus métricas de entrega. Solución: Implementa un chequeo periódico en el cliente (ej. al abrir la app) para verificar el estado del permiso y el token. En el servidor, procesa los "tickets" de error que devuelve Expo, especialmente el error DeviceNotRegistered, y elimina esos tokens de tu base de datos.
3. Configuración incorrecta de credenciales para builds de producción: Las notificaciones funcionan en el cliente de desarrollo (Expo Go) gracias a credenciales genéricas de Expo. Pero para una app en producción (build standalone), necesitas tus propias credenciales de APNs (iOS) y FCM (Android) subidas al dashboard de Expo o configuradas con EAS. Solución: Sigue la guía de "Push Notifications" en la documentación de Expo para generar y subir las credenciales. Usa eas build para crear tus binaries de producción.
4. Olvidar configurar el canal de notificaciones en Android 8+ (Oreo): Android Oreo introdujo los "canales de notificación". Si no defines al menos uno, tus notificaciones podrían no mostrarse. Solución: Siempre llama a Notifications.setNotificationChannelAsync para Android, como se muestra en el código de ejemplo. Puedes crear múltiples canales para diferentes tipos de alertas (promociones, mensajes, recordatorios).
5. Enviar el token de acceso de Expo desde el cliente: Nunca incrustes tu Expo Access Token en el código de tu aplicación móvil. Este token otorga permisos para enviar notificaciones a todos los usuarios de tu app. Si se expone, cualquiera podría enviar spam a tus usuarios. Solución: El envío de notificaciones debe ser siempre una operación del lado del servidor. El token de acceso debe estar almacenado de forma segura en variables de entorno de tu backend.
Tip Profesional: Utiliza las "Notification Channels" de Android a tu favor. No crees solo uno llamado "default". Crea canales específicos como "Transacciones", "Social" o "Noticias". Esto permite a los usuarios afinar qué tipos de notificaciones quieren recibir desde la configuración del sistema, mejorando la experiencia y reduciendo las tasas de desactivación.
Checklist de Dominio
Antes de considerar que dominas el manejo de notificaciones push con Expo, verifica que puedes realizar o comprender cada uno de los siguientes puntos:
- Configurar un proyecto Expo con el paquete `expo-notifications` y los permisos correctos en app.json/app.config.js para iOS y Android.
- Solicitar permisos al usuario de manera estratégica y no intrusiva, manejando elegantemente los casos de denegación.
- Obtener, almacenar y gestionar el ciclo de vida del Expo Push Token en el cliente y sincronizarlo de forma segura con tu backend.
- Configurar y manejar listeners para notificaciones recibidas en primer plano y respuestas a notificaciones (cuando el usuario las toca).
- Implementar un endpoint de backend seguro (en Node.js, Python, etc.) capaz de enviar notificaciones a uno o múltiples tokens usando el EPNS de Expo.
- Generar y subir las credenciales nativas necesarias (APNs Key y configuración de FCM) para que las notificaciones funcionen en builds de producción standalone.
- Manejar errores comunes del servidor, como tokens inválidos (`DeviceNotRegistered`), y limpiar tu base de datos en consecuencia.
- Probar exhaustivamente el flujo completo: desde el registro en un dispositivo físico, el envío desde el servidor, la recepción en segundo plano y la navegación al tocar la notificación.
Dominar las notificaciones push es un salto cualitativo para cualquier aplicación. Transforma tu producto de una herramienta reactiva, a la que el usuario debe abrir, en un servicio proactivo que lleva valor directamente a su pantalla de bloqueo. Expo abstrae la mayor parte de la complejidad nativa, pero la responsabilidad de diseñar una experiencia de notificaciones respetuosa, útil y confiable recae completamente en ti, el desarrollador. Usa este poder con criterio, priorizando la relevancia sobre la frecuencia, y construirás una relación de confianza duradera con tus usuarios.
Falar no WhatsApp