ecosystem-wikicómo extender los templates sin romper sus convenciones
‹ todas las recipes

Push: registrar el dispositivo y enrutar el aviso

template-expo
último cambio 2026-09-25

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

  1. Declara su tipo de datos y su type guard.
  2. Añade la rama en handleNotificationResponse.
  3. Comprueba que el backend manda ese mismo type en el data del 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();

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.