Push: registrar el dispositivo y enrutar el aviso
El push tiene dos mitades independientes, las dos arrancadas desde el layout del área privada: registrar el dispositivo y reaccionar a un toque.
Registro: token nativo, no el de Expo
return Notifications.getDevicePushTokenAsync();
Y no getExpoPushTokenAsync(). El motivo está comentado: el backend envía por FCM y APNs
directamente, sin pasar por el servicio de push de Expo. Pedir el token de Expo daría una cadena que
el backend no sabe usar.
El token se manda a la api (registerDevice) y se guarda en SecureStore, que es lo que permite
darlo de baja al cerrar sesión — ver sesión.
La importación diferida no es opcional
if (Platform.OS === 'web' || isRunningInExpoGo()) return;
const Notifications = await import('expo-notifications');
expo-notifications lanza en Android en el momento en que se importa si la app corre en Expo
Go. Por eso no hay ningún import estático del paquete en todo el fichero: solo import() dinámico,
y solo fuera de Expo Go.
Si añades un import normal arriba del fichero, rompes la app en Expo Go aunque nunca llames a la función.
Consecuencia práctica: el push solo se puede probar en una build de desarrollo o EAS, nunca en Expo Go. Y en Android hace falta un dispositivo o emulador con Play Store; uno sin él no genera token real.
Enrutar un toque
Un aviso trae un data con un type, y ese type decide la pantalla:
function handleNotificationResponse(response) {
const data = response.notification.request.content.data;
if (isContactRequestData(data)) {
router.push({ pathname: '/access-request', params: { ... } });
return;
}
if (isAppointmentReminderData(data)) {
router.push({ pathname: '/pet/[id]', params: { id: data.petId, tab: 'historial' } });
}
}
Fíjate en que cada tipo tiene su type guard (isContactRequestData) que comprueba campo a
campo. No es ceremonia: ese data viene de fuera y llega como unknown. Sin validar, un aviso mal
formado —o de una versión antigua de la app— reventaría al navegar.
Un tipo desconocido simplemente no hace nada: la app abre donde estuviera.
Añadir un tipo nuevo
- Declara su tipo de datos y su type guard.
- Añade la rama en
handleNotificationResponse. - Comprueba que el backend manda ese mismo
typeen eldatadel push.
Ese tercer punto es donde suele estar el fallo: el data lo compone el productor, y su plantilla
vive en otro servicio.
Los dos caminos de un toque
Se cubren ambos, y es fácil olvidar el primero:
const lastResponse = await Notifications.getLastNotificationResponseAsync();
if (lastResponse) handleNotificationResponse(lastResponse);
const subscription = Notifications.addNotificationResponseReceivedListener(
handleNotificationResponse,
);
return () => subscription.remove();
- Arranque en frío: la app estaba cerrada, el toque la abrió. Solo se detecta consultando la última respuesta.
- App viva: el listener.
La función devuelve su propia baja para que el efecto la limpie. Y el efecto gestiona el caso de que
el await resuelva después de desmontarse —dándose de baja al momento—, que es el bug clásico de
suscribirse en un efecto asíncrono.