Volver al Blog
AppsDesarrolloSoftware

Notificaciones Push en React Native y Expo con Firebase Cloud Messaging (FCM): Guía Integral

Brayan Developer
5 min de lectura
Notificaciones Push en React Native y Expo con Firebase Cloud Messaging (FCM): Guía Integral
Aprende a implementar notificaciones push profesionales en iOS y Android con React Native, Expo Notifications y Firebase Cloud Messaging. Incluye deep linking y backend Node.js.

Las notificaciones push son el canal de comunicación con mayor tasa de apertura en aplicaciones móviles, alcanzando hasta un 40% más de retención y re-engagement que el correo electrónico. Sin embargo, configurar notificaciones fiables que funcionen tanto en iOS (Apple Push Notification service - APNs) como en Android con React Native y Expo suele ser un reto técnico. En esta guía detallamos la arquitectura paso a paso.

Notificaciones Push en React Native y Expo

Arquitectura de Notificaciones Push Multiplataforma#

El flujo de entrega de una notificación push involucra tres actores fundamentales: la aplicación móvil (cliente), tu servidor backend y los servidores de mensajería en la nube de Google y Apple.

graph TD
    App["App Móvil (React Native / Expo)"] -->|1. Solicita permiso y obtiene Push Token| PushServer["Apple APNs / Google FCM"]
    App -->|2. Envía Token del Dispositivo| Backend["Backend API (Node.js / Supabase)"]
    Backend -->|3. Almacena Token vinculado al Usuario| DB[("Base de Datos PostgreSQL")]

    Trigger["Evento de Negocio (Nueva Venta / Mensaje / Alerta)"] --> Backend
    Backend -->|4. Despacha Payload con Firebase Admin SDK| PushServer
    PushServer -->|5. Entrega Notificación Push en pantalla| App

1. Configuración del Cliente en React Native (Expo Notifications)#

Utilizando el paquete oficial expo-notifications, podemos gestionar permisos, canales en Android y escuchar eventos de llegada y toque de la notificación.

Hook personalizado para registro de Push Token:#

// hooks/usePushNotifications.ts
import { useState, useEffect, useRef } from 'react';
import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
import { useRouter } from 'expo-router';

// Configurar cómo se comportan las notificaciones cuando la app está abierta en primer plano
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: true,
  }),
});

export function usePushNotifications() {
  const [expoPushToken, setExpoPushToken] = useState<string | null>(null);
  const notificationListener = useRef<Notifications.EventSubscription | null>(null);
  const responseListener = useRef<Notifications.EventSubscription | null>(null);
  const router = useRouter();

  useEffect(() => {
    registerForPushNotificationsAsync().then((token) => setExpoPushToken(token));

    // Escuchar cuando llega una notificación con la app abierta
    notificationListener.current = Notifications.addNotificationReceivedListener((notification) => {
      console.log('Notificación recibida en foreground:', notification);
    });

    // Escuchar cuando el usuario TOCA la notificación (Deep Linking)
    responseListener.current = Notifications.addNotificationResponseReceivedListener((response) => {
      const data = response.notification.request.content.data;
      if (data?.url) {
        router.push(data.url); // Redirige a la pantalla interna con Expo Router
      }
    });

    return () => {
      if (notificationListener.current) notificationListener.current.remove();
      if (responseListener.current) responseListener.current.remove();
    };
  }, []);

  return { expoPushToken };
}

async function registerForPushNotificationsAsync(): Promise<string | null> {
  if (!Device.isDevice) {
    console.warn('Las notificaciones push requieren un dispositivo físico.');
    return null;
  }

  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  let finalStatus = existingStatus;

  if (existingStatus !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }

  if (finalStatus !== 'granted') {
    alert('¡No se otorgaron permisos para notificaciones push!');
    return null;
  }

  const projectId = Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId;
  const tokenData = await Notifications.getExpoPushTokenAsync({ projectId });

  // Canal de notificación obligatorio para Android 8.0+
  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'Notificaciones Generales',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#2563EB',
    });
  }

  return tokenData.data;
}

2. Enrutamiento Profundo (Deep Linking) con Expo Router#

Cuando el usuario recibe una notificación (ej. "Tu pedido #4028 ha sido enviado"), tocarla debe abrir directamente el detalle del pedido, no la pantalla inicial.

Al despachar la notificación desde tu backend, incluye la propiedad data.url:

{
  "to": "ExponentPushToken[xxxxxxxxxxxxxx]",
  "title": "¡Pedido Despachado! 🚚",
  "body": "Tu paquete está en camino. Toca para ver el mapa de seguimiento.",
  "data": {
    "url": "/orders/4028",
    "orderId": "4028"
  },
  "sound": "default",
  "badge": 1
}

3. Despacho Masivo y Segmentado desde el Backend (Node.js)#

Para enviar notificaciones push a miles de usuarios sin bloquear tu API, implementamos un servicio de despacho por lotes (batching de 100 en 100) utilizando la API de Expo Push o Firebase Admin SDK:

// server/pushService.ts
import { Expo, ExpoPushMessage } from 'expo-server-sdk';

const expo = new Expo();

interface SendPushOptions {
  pushTokens: string[];
  title: string;
  body: string;
  data?: Record<string, unknown>;
}

export async function sendBulkPushNotifications({
  pushTokens,
  title,
  body,
  data,
}: SendPushOptions) {
  const messages: ExpoPushMessage[] = [];

  for (const token of pushTokens) {
    if (!Expo.isExpoPushToken(token)) {
      console.error(`Token inválido: ${token}`);
      continue;
    }

    messages.push({
      to: token,
      sound: 'default',
      title,
      body,
      data,
      priority: 'high',
      channelId: 'default',
    });
  }

  // Agrupar mensajes en lotes de 100 para entrega masiva eficiente
  const chunks = expo.chunkPushNotifications(messages);

  for (const chunk of chunks) {
    try {
      const ticketChunk = await expo.sendPushNotificationsAsync(chunk);
      console.log('Lote enviado con éxito:', ticketChunk);
    } catch (error) {
      console.error('Error enviando lote de notificaciones push:', error);
    }
  }
}

Buenas Prácticas para Evitar Desinstalaciones#

  1. Segmentación Precisa: No envíes la misma notificación a toda tu base de usuarios. Segmenta por geolocalización, intereses o historial de compras.
  2. Respetar Horarios Locales: Evita notificaciones push comerciales entre las 10:00 PM y las 8:00 AM de la zona horaria del usuario.
  3. Limpieza Automática de Tokens: Si la API devuelve un error DeviceNotRegistered, elimina inmediatamente el token de la base de datos para no degradar la reputación de tu app.

Conclusión y Desarrollo de Apps Móviles#

Una arquitectura de notificaciones push bien implementada transforma una app estática en una herramienta de fidelización y ventas automatizadas en tiempo real.

¿Necesitas desarrollar una aplicación móvil para iOS y Android con notificaciones en tiempo real, chat y pagos integrados? Contáctame hoy o descubre nuestros servicios de desarrollo móvil.

Etiquetas
React NativeExpoFirebaseNotificaciones PushiOSAndroidMobile AppsTypeScript
Compartir:XLinkedInWhatsApp

¿Te gustaría profundizar en estos temas?

Aprende sobre desarrollo de software, apps a medida, automatizaciones con N8N, Next.js y Cloud con casos reales.

Hablemos