Mapas en aplicaciones móviles: el panorama actual en React Native
Pocas funciones resultan tan útiles en una aplicación móvil como un mapa. Las apps de reparto muestran dónde está el repartidor, las inmobiliarias sitúan cada propiedad en su barrio, las aplicaciones de deporte dibujan la ruta recorrida y los directorios de comercios ayudan a encontrar la sucursal más cercana. En todos esos casos el mapa no es un adorno: es la interfaz principal sobre la que el usuario toma decisiones.
En el ecosistema de React Native, la biblioteca de referencia para esta tarea sigue siendo react-native-maps. Nació dentro de Airbnb, hoy la mantiene la comunidad bajo su propia organización en GitHub y ofrece un componente MapView que utiliza los mapas nativos de cada plataforma: Apple Maps en iOS y Google Maps en Android, con la opción de usar Google Maps también en iOS. Al tratarse de vistas nativas, el desplazamiento, el zoom y la rotación se sienten tan fluidos como en cualquier aplicación escrita en Swift o Kotlin.
Hace unos años, integrar mapas en React Native implicaba enlazar bibliotecas a mano, abrir Xcode para arrastrar carpetas al proyecto y ajustar dependencias de Google Play Services en Gradle hasta que la compilación dejaba de fallar. Ese proceso ha quedado atrás. El enlazado automático, CocoaPods con subespecificaciones y, sobre todo, los config plugins de Expo han reducido la configuración a unos pocos pasos declarativos.
En esta guía construiremos una pantalla de mapa completa con las versiones actuales del ecosistema. Veremos cómo crear el proyecto, cómo activar Google Maps en ambas plataformas, cómo gestionar las claves de API de forma segura, cómo pedir permisos de ubicación, cómo añadir marcadores, rutas y animaciones de cámara, y cómo evitar los problemas que más tiempo hacen perder. También compararemos react-native-maps con otras alternativas para que sepas cuándo conviene elegir cada una.
Qué es react-native-maps y qué proveedores admite
react-native-maps expone un conjunto de componentes de React que envuelven los SDK de mapas nativos. El componente central es MapView, y a su alrededor se organizan piezas como Marker, Callout, Polyline, Polygon, Circle, Overlay o Heatmap. Cada una se traduce en un objeto nativo del SDK correspondiente.
El concepto clave es el proveedor. Por defecto, el mapa usa el proveedor nativo de la plataforma:
- En iOS, Apple Maps, que funciona sin clave de API ni configuración adicional.
- En Android, Google Maps, que siempre requiere una clave de API.
Si quieres que ambas plataformas muestren exactamente el mismo mapa, puedes forzar Google Maps también en iOS mediante la constante PROVIDER_GOOGLE:
import MapView, { PROVIDER_GOOGLE } from "react-native-maps";
export function MapaGoogle() {
return <MapView provider={PROVIDER_GOOGLE} style={{ flex: 1 }} />;
}
La elección tiene consecuencias. Usar Apple Maps en iOS ahorra configuración y no consume cuota de Google, pero algunas funciones exclusivas de Google, como las capas KML, los mapas de calor o la agrupación nativa de marcadores, no están disponibles con ese proveedor y generarán errores si intentas usarlas. Usar Google Maps en ambas plataformas da una experiencia homogénea y acceso a esas funciones, a cambio de configurar el SDK de Google para iOS y gestionar la facturación en Google Cloud.
Versiones y requisitos actuales
Antes de escribir código conviene fijar el punto de partida. A finales de octubre de 2026, estas son las versiones estables que usaremos en la guía:
- React Native 0.88, publicada el 19 de octubre de 2026, que incluye React 19.3 y no introduce cambios incompatibles respecto a la 0.87.
- Expo SDK 58, que actualiza React Native de la 0.86 a la 0.88.
- react-native-maps 1.29.11, publicada a finales de septiembre de 2026 y recomendada por la documentación de Expo.
En iOS, el SDK de Google Maps exige un objetivo de despliegue mínimo de iOS 14. En Android, react-native-maps necesita play-services-maps en la versión 18.0.0 o superior, aunque normalmente no tendrás que declararlo a mano porque la biblioteca ya lo resuelve.
Un detalle importante si vienes de tutoriales antiguos: la Nueva Arquitectura de React Native es la opción por defecto desde hace varias versiones, y las versiones recientes de react-native-maps incluyen correcciones específicas para Fabric, el nuevo sistema de renderizado. Si encuentras guías que fijan versiones como la 0.22 de la biblioteca o que piden ejecutar react-native link, trátalas como históricas: ese comando ya no existe y el enlazado es automático.
Para comprobar qué versión tienes instalada en un proyecto existente:
npm ls react-native react-native-maps
Crear el proyecto con Expo
La forma más rápida y recomendada de empezar es Expo. Su CLI genera el proyecto, el config plugin de react-native-maps escribe por ti la configuración nativa y, para pruebas iniciales, la biblioteca ya viene incluida en Expo Go.
npx create-expo-app@latest mapa-sucursales
cd mapa-sucursales
npx expo install react-native-maps
El comando npx expo install es preferible a npm install porque elige la versión de la biblioteca compatible con el SDK de Expo que estás usando. Esto evita los desajustes de versiones nativas que en el pasado provocaban errores difíciles de diagnosticar.
La estructura resultante, simplificada, se parece a esta:
mapa-sucursales/
├── app/
│ ├── _layout.tsx
│ └── index.tsx
├── assets/
├── app.json
├── package.json
└── tsconfig.json
Si necesitas integrar mapas en una aplicación React Native que no usa Expo, más adelante veremos la configuración manual para iOS y Android. Gran parte del código de componentes es idéntico en ambos casos.
El primer mapa con MapView
Un MapView necesita como mínimo un tamaño. Lo más habitual es que ocupe toda la pantalla con flex: 1 o StyleSheet.absoluteFillObject. Para decidir qué zona del mundo se muestra al abrirlo se usa initialRegion, un objeto con latitud, longitud y dos deltas que determinan cuánto terreno abarca la vista.
Vamos a mostrar el centro de Guadalajara, en México:
import { StyleSheet, View } from "react-native";
import MapView, { Region } from "react-native-maps";
const CENTRO_GUADALAJARA: Region = {
latitude: 20.6767,
longitude: -103.3475,
latitudeDelta: 0.04,
longitudeDelta: 0.04,
};
export default function PantallaMapa() {
return (
<View style={styles.contenedor}>
<MapView style={styles.mapa} initialRegion={CENTRO_GUADALAJARA} />
</View>
);
}
const styles = StyleSheet.create({
contenedor: { flex: 1 },
mapa: { ...StyleSheet.absoluteFillObject },
});
Los deltas funcionan como un nivel de zoom expresado en grados. Un valor de 0.04 muestra unos pocos kilómetros, adecuado para una ciudad; un valor de 0.005 se acerca al nivel de calle; un valor de 10 muestra una región entera del país.
Hay dos formas de controlar la región. Con initialRegion el mapa arranca en ese punto y después el usuario lo mueve libremente: es un componente no controlado. Con region el mapa queda controlado por el estado de React y deberás actualizarlo en onRegionChangeComplete:
import { useState } from "react";
import MapView, { Region } from "react-native-maps";
export function MapaControlado({ inicial }: { inicial: Region }) {
const [region, setRegion] = useState<Region>(inicial);
return (
<MapView
style={{ flex: 1 }}
region={region}
onRegionChangeComplete={(nueva) => setRegion(nueva)}
/>
);
}
Usa la versión controlada solo cuando realmente necesites la región en el estado, por ejemplo para lanzar una búsqueda de resultados en la zona visible. Actualizar el estado en onRegionChange, que se dispara continuamente durante el gesto, provoca renderizados constantes y tirones en el desplazamiento.
Activar Google Maps en Expo con el config plugin
En Android, el mapa necesita una clave de API de Google para mostrar algo más que un fondo gris. En iOS la necesitarás si decides usar PROVIDER_GOOGLE. Con Expo, ambas claves se declaran en el config plugin de react-native-maps dentro de app.json o app.config.ts, sin tocar archivos nativos.
La práctica recomendada es no escribir las claves directamente en el repositorio, sino leerlas de variables de entorno desde app.config.ts:
import type { ExpoConfig } from "expo/config";
const config: ExpoConfig = {
name: "Mapa de sucursales",
slug: "mapa-sucursales",
ios: {
bundleIdentifier: "dev.ejemplo.mapasucursales",
},
android: {
package: "dev.ejemplo.mapasucursales",
},
plugins: [
[
"react-native-maps",
{
iosGoogleMapsApiKey: process.env.GOOGLE_MAPS_IOS_KEY,
androidGoogleMapsApiKey: process.env.GOOGLE_MAPS_ANDROID_KEY,
},
],
],
};
export default config;
Y en un archivo .env que no debe subirse al control de versiones:
GOOGLE_MAPS_IOS_KEY=AIzaSy-clave-ios-de-ejemplo
GOOGLE_MAPS_ANDROID_KEY=AIzaSy-clave-android-de-ejemplo
Este enfoque requiere react-native-maps 1.22 o posterior y Expo SDK 53 o posterior, condiciones que cumplen sobradamente las versiones actuales. Recuerda que el config plugin actúa en la fase de compilación nativa: después de cambiar las claves tienes que generar de nuevo el binario, ya sea con una compilación de desarrollo local o con EAS Build.
npx expo prebuild --clean
npx expo run:android
npx expo run:ios
Si compilas con EAS Build y la clave no llega a la aplicación, revisa que tu archivo .easignore no excluya el .env, o define las variables como secretos del proyecto en EAS.
Instalación en un proyecto React Native sin Expo
Si tu aplicación usa React Native sin Expo, el código de los componentes es el mismo, pero tendrás que configurar a mano cada plataforma. Primero crea el proyecto con la CLI de la comunidad, que sustituye al antiguo comando react-native init, e instala la biblioteca:
npx @react-native-community/cli@latest init MapaSucursales
cd MapaSucursales
npm install react-native-maps
Gracias al enlazado automático no hace falta ningún paso de enlace. Lo que sí necesitas es preparar iOS y Android para Google Maps, como veremos en las dos secciones siguientes. Si en iOS te basta con Apple Maps, puedes saltarte la configuración de Google en esa plataforma y limitarte a ejecutar pod install.
Configuración de iOS: Podfile, AppDelegate e Info.plist
Configurar los mapas de Google en iOS requiere tres cambios. El primero está en ios/Podfile. Asegúrate de que la plataforma mínima sea iOS 14 o superior y añade la subespecificación de Google de react-native-maps por encima de la línea use_native_modules!:
platform :ios, '15.1'
target 'MapaSucursales' do
rn_maps_path = '../node_modules/react-native-maps'
pod 'react-native-maps/Google', :path => rn_maps_path
config = use_native_modules!
# resto del Podfile generado
end
Si necesitas fijar versiones concretas del SDK de Google, la biblioteca admite variables globales que se declaran antes de esa línea, como $RNMapsGoogleMapsVersion. En la mayoría de los proyectos no hace falta tocarlas. Después instala los pods:
cd ios && pod install && cd ..
El segundo cambio es registrar la clave en el AppDelegate. Los proyectos creados con versiones recientes de React Native usan Swift, y la llamada debe ser la primera instrucción del método de arranque:
import UIKit
import GoogleMaps
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
GMSServices.provideAPIKey("AIzaSy-clave-ios-de-ejemplo")
// resto de la inicialización generada por la plantilla
return true
}
}
Si tu proyecto es antiguo y todavía usa Objective-C, el equivalente es importar <GoogleMaps/GoogleMaps.h> y llamar a [GMSServices provideAPIKey:@"..."] al comienzo de didFinishLaunchingWithOptions.
El tercer cambio es declarar en Info.plist el motivo de uso de la ubicación. El SDK de Google Maps para iOS incluye código de localización, por lo que Apple exige la descripción aunque tu app no muestre la posición del usuario:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Usamos tu ubicación para mostrarte las sucursales más cercanas.</string>
Escribe un texto claro y específico. Las descripciones genéricas son un motivo frecuente de rechazo en la revisión de la App Store.
Configuración de Android: manifiesto y dependencias
En Android basta con añadir la clave de API como metadato dentro de la etiqueta <application> de android/app/src/main/AndroidManifest.xml:
<application
android:name=".MainApplication"
android:label="@string/app_name">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<!-- actividades y resto de la configuración -->
</application>
En lugar de pegar la clave en el manifiesto, el ejemplo la toma de un recurso de cadena. Puedes generarlo desde Gradle a partir de una propiedad local que no se sube al repositorio. En android/app/build.gradle:
android {
defaultConfig {
resValue "string", "google_maps_key", (project.findProperty("GOOGLE_MAPS_ANDROID_KEY") ?: "")
}
}
Y en ~/.gradle/gradle.properties o en las variables de tu sistema de integración continua:
GOOGLE_MAPS_ANDROID_KEY=AIzaSy-clave-android-de-ejemplo
Las guías antiguas solían pedir que se definieran variables como playServicesVersion o supportLibVersion en el build.gradle raíz. Desde hace muchas versiones ya no son necesarias. Si tu proyecto las conserva y no las usa ningún otro módulo, puedes eliminarlas; si las mantienes, playServicesVersion debe ser al menos 18.0.0.
Por último, el emulador o dispositivo debe tener Google Play Services instalado y actualizado. Las imágenes de emulador sin servicios de Google no mostrarán el mapa.
Obtener y proteger las claves de API
Las claves se generan en la consola de Google Cloud. El proceso general es crear o elegir un proyecto, habilitar la API “Maps SDK for Android” y la API “Maps SDK for iOS”, y crear una clave para cada plataforma. Google Maps Platform exige además una cuenta de facturación activa, aunque existe un nivel de uso gratuito.
La parte que muchos equipos omiten, y que más problemas causa, es restringir las claves. Una clave de mapas incluida en una aplicación móvil puede extraerse del binario, así que su seguridad no depende de esconderla, sino de limitar quién puede usarla:
- La clave de Android se restringe a aplicaciones Android indicando el nombre del paquete y la huella SHA-1 del certificado de firma.
- La clave de iOS se restringe a aplicaciones iOS indicando el identificador del paquete.
- Cada clave se limita además a las API concretas que necesita.
Para obtener la huella SHA-1 del certificado de depuración en Android:
keytool -list -v -keystore android/app/debug.keystore \
-alias androiddebugkey -storepass android -keypass android
Certificate fingerprints:
SHA1: 5E:8F:16:06:2E:A3:CD:2C:4A:0D:54:78:76:BA:A6:F3:8C:AB:F6:25
SHA256: FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C
Ten en cuenta que, si publicas en Google Play con la firma gestionada por la tienda, la huella del certificado de producción es distinta de la de tu certificado de subida. Debes añadir la que aparece en Play Console; de lo contrario, el mapa funcionará en desarrollo y se verá gris en producción.
Mostrar la ubicación del usuario y gestionar permisos
Una de las funciones más demandadas es mostrar la ubicación del usuario con el clásico punto azul. MapView lo hace con la propiedad showsUserLocation, pero solo si la aplicación tiene permiso de localización. Pedir ese permiso es responsabilidad tuya.
En proyectos con Expo, la forma más sencilla es el paquete expo-location:
npx expo install expo-location
El siguiente componente solicita el permiso, obtiene la posición actual y centra el mapa en ella:
import { useEffect, useRef, useState } from "react";
import { Text, View } from "react-native";
import * as Location from "expo-location";
import MapView from "react-native-maps";
export function MapaConUbicacion() {
const mapaRef = useRef<MapView>(null);
const [permitido, setPermitido] = useState<boolean | null>(null);
useEffect(() => {
(async () => {
const { status } =
await Location.requestForegroundPermissionsAsync();
const concedido = status === "granted";
setPermitido(concedido);
if (!concedido) return;
const posicion = await Location.getCurrentPositionAsync({});
mapaRef.current?.animateToRegion(
{
latitude: posicion.coords.latitude,
longitude: posicion.coords.longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
},
600
);
})();
}, []);
if (permitido === false) {
return (
<View style={{ flex: 1, justifyContent: "center", padding: 24 }}>
<Text>
Activa la ubicación en los ajustes para ver las sucursales
cercanas.
</Text>
</View>
);
}
return (
<MapView
ref={mapaRef}
style={{ flex: 1 }}
showsUserLocation
showsMyLocationButton
/>
);
}
En un proyecto sin Expo, en Android tendrás que declarar los permisos en el manifiesto y pedirlos en tiempo de ejecución con PermissionsAndroid:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
import { PermissionsAndroid, Platform } from "react-native";
export async function pedirPermisoUbicacion(): Promise<boolean> {
if (Platform.OS !== "android") return true;
const resultado = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
);
return resultado === PermissionsAndroid.RESULTS.GRANTED;
}
Desde el punto de vista de la privacidad, pide el permiso en el momento en que el usuario entiende para qué lo necesitas, por ejemplo al pulsar “Buscar cerca de mí”, y no nada más abrir la aplicación. Además, solicita solo la ubicación en primer plano salvo que tu caso de uso requiera seguimiento en segundo plano.
Marcadores, callouts y rutas
Con el mapa listo, el siguiente paso es dibujar información encima. Los marcadores personalizados en el mapa son la pieza más utilizada. Cada Marker necesita una coordenada y puede llevar título, descripción, un color o una vista personalizada como hijo.
Supongamos una lista de sucursales:
import MapView, { Callout, Marker } from "react-native-maps";
import { Text, View } from "react-native";
type Sucursal = {
id: string;
nombre: string;
horario: string;
latitud: number;
longitud: number;
};
const SUCURSALES: Sucursal[] = [
{
id: "s1",
nombre: "Centro",
horario: "9:00 a 20:00",
latitud: 20.6748,
longitud: -103.3443,
},
{
id: "s2",
nombre: "Chapultepec",
horario: "10:00 a 21:00",
latitud: 20.6738,
longitud: -103.3698,
},
{
id: "s3",
nombre: "Tlaquepaque",
horario: "9:00 a 19:00",
latitud: 20.6409,
longitud: -103.3125,
},
];
export function MapaSucursales() {
return (
<MapView
style={{ flex: 1 }}
initialRegion={{
latitude: 20.66,
longitude: -103.34,
latitudeDelta: 0.08,
longitudeDelta: 0.08,
}}
>
{SUCURSALES.map((s) => (
<Marker
key={s.id}
coordinate={{ latitude: s.latitud, longitude: s.longitud }}
pinColor="#0f766e"
>
<Callout>
<View style={{ padding: 4 }}>
<Text style={{ fontWeight: "600" }}>
{s.nombre}
</Text>
<Text>{s.horario}</Text>
</View>
</Callout>
</Marker>
))}
</MapView>
);
}
El Callout es la burbuja que aparece al tocar el marcador. Para respuestas a toques, Marker acepta onPress y Callout acepta onPress, útil para navegar a la ficha de la sucursal.
Para dibujar un recorrido, por ejemplo la ruta de un repartidor, se usa Polyline con una lista ordenada de coordenadas:
import { Polyline } from "react-native-maps";
const RUTA_REPARTO = [
{ latitude: 20.6748, longitude: -103.3443 },
{ latitude: 20.6712, longitude: -103.3521 },
{ latitude: 20.6738, longitude: -103.3698 },
];
export function RutaReparto() {
return (
<Polyline
coordinates={RUTA_REPARTO}
strokeColor="#2563eb"
strokeWidth={5}
lineCap="round"
/>
);
}
Polyline se coloca como hijo de MapView, igual que los marcadores. Ten en cuenta que react-native-maps no calcula rutas por calles; si necesitas un trazado real entre dos puntos, debes obtener las coordenadas de un servicio de rutas desde tu backend y pasarlas al componente. Polygon y Circle funcionan de forma análoga para delimitar zonas de cobertura o radios de entrega.
Controlar la cámara y animar el mapa
Muchas interacciones requieren mover el mapa desde código: centrarlo en un resultado de búsqueda, ajustar el zoom para que se vean todos los marcadores o seguir a un vehículo. Para ello se usa una referencia al MapView y sus métodos imperativos.
Para encuadrar automáticamente un conjunto de puntos, fitToCoordinates calcula el zoom necesario y deja un margen configurable:
import { useRef } from "react";
import { Button, View } from "react-native";
import MapView, { LatLng } from "react-native-maps";
export function MapaEncuadrado({ puntos }: { puntos: LatLng[] }) {
const mapaRef = useRef<MapView>(null);
const encuadrar = () => {
mapaRef.current?.fitToCoordinates(puntos, {
edgePadding: { top: 80, right: 40, bottom: 80, left: 40 },
animated: true,
});
};
return (
<View style={{ flex: 1 }}>
<MapView ref={mapaRef} style={{ flex: 1 }} onMapReady={encuadrar} />
<Button title="Ver todas las sucursales" onPress={encuadrar} />
</View>
);
}
Llamar a fitToCoordinates dentro de onMapReady garantiza que el mapa ya tiene dimensiones cuando se calcula el encuadre. Si lo llamas antes, el resultado puede ser incorrecto, sobre todo en Android.
Para un control más fino de la cámara, como inclinación o rumbo, existe animateCamera:
mapaRef.current?.animateCamera(
{
center: { latitude: 20.6767, longitude: -103.3475 },
pitch: 45,
heading: 90,
zoom: 16,
},
{ duration: 800 }
);
La propiedad zoom se aplica con Google Maps; con Apple Maps la cámara usa la altitud. Si tu app usa ambos proveedores, prueba estas animaciones en las dos plataformas.
Rendimiento con muchos marcadores
Un mapa con diez marcadores funciona sin problemas. Uno con dos mil, no. Cada marcador es una vista nativa, y si además contiene una vista personalizada de React, el coste se multiplica.
La primera optimización es desactivar el seguimiento de cambios de los marcadores con vistas personalizadas una vez que se han dibujado. Por defecto, react-native-maps vuelve a capturar la vista del marcador en cada actualización, lo que consume CPU:
<Marker coordinate={coordenada} tracksViewChanges={false}>
<IconoSucursal />
</Marker>
Si el icono depende de una imagen que tarda en cargar, activa el seguimiento hasta que la imagen esté lista y desactívalo después; de lo contrario, el marcador puede aparecer vacío.
La segunda optimización es no renderizar lo que no se ve. Con la región visible que entrega onRegionChangeComplete, puedes filtrar los puntos antes de dibujarlos:
import type { Region } from "react-native-maps";
export function dentroDeRegion(
punto: { latitud: number; longitud: number },
region: Region
): boolean {
const latMin = region.latitude - region.latitudeDelta / 2;
const latMax = region.latitude + region.latitudeDelta / 2;
const lngMin = region.longitude - region.longitudeDelta / 2;
const lngMax = region.longitude + region.longitudeDelta / 2;
return (
punto.latitud >= latMin &&
punto.latitud <= latMax &&
punto.longitud >= lngMin &&
punto.longitud <= lngMax
);
}
La tercera es agrupar marcadores cercanos en clústeres cuando el zoom es bajo. Puedes hacerlo en JavaScript con una biblioteca de clustering geoespacial o, si usas Google Maps, aprovechar la agrupación nativa del proveedor de Google. Para conjuntos realmente grandes, lo más eficiente es pedir al backend solo los puntos de la zona visible y ya agrupados.
Por último, memoriza los componentes de marcador con React.memo y evita crear objetos de coordenadas nuevos en cada renderizado, porque cualquier cambio de referencia obliga a actualizar la vista nativa.
Errores comunes y solución de problemas
Casi todos los problemas con mapas en React Native se concentran en unos pocos síntomas.
El más frecuente es el mapa gris o en blanco con el logotipo de Google en una esquina. Significa que el SDK se carga, pero la clave no es válida para esa aplicación. Revisa que la API correspondiente esté habilitada, que la cuenta de facturación esté activa y que las restricciones coincidan con el paquete y la huella SHA-1 reales. En Android, Logcat suele mostrar el motivo:
adb logcat | grep -i "Google Maps"
E Google Android Maps SDK: Authorization failure.
E Google Android Maps SDK: Ensure that the "Maps SDK for Android" is enabled.
E Google Android Maps SDK: Android Application (<cert_fingerprint>;<package_name>): 5E:8F:...;dev.ejemplo.mapasucursales
La última línea indica exactamente qué huella y qué paquete debes registrar en la restricción de la clave.
El segundo síntoma es que el mapa no aparece en absoluto, sin fondo gris. Casi siempre se debe a un problema de estilo: el MapView no tiene altura porque su contenedor no usa flex: 1 o porque está dentro de un ScrollView sin dimensiones explícitas.
El tercero aparece en iOS al usar PROVIDER_GOOGLE sin haber añadido la subespecificación de Google en el Podfile. La aplicación puede fallar al arrancar o mostrar un error indicando que el proveedor de Google no está disponible. Comprueba el Podfile, vuelve a ejecutar pod install y recompila desde cero.
El cuarto es que los cambios de configuración no surten efecto. Si modificaste el config plugin, Info.plist o el manifiesto, una recarga de JavaScript no basta: necesitas recompilar el binario. En Expo, usa npx expo prebuild --clean y compila de nuevo.
El quinto es el punto azul que nunca aparece. Revisa que hayas solicitado el permiso en tiempo de ejecución, que el simulador tenga una ubicación simulada configurada y que la propiedad showsUserLocation esté activa.
Finalmente, si la compilación de Android falla con conflictos de Google Play Services entre bibliotecas, la solución recomendada es excluir com.google.android.gms de los módulos en conflicto y declarar las dependencias de Play Services en el build.gradle de la aplicación. Antes de llegar a eso, asegúrate de que todas tus bibliotecas nativas están actualizadas, porque la mayoría de estos conflictos desaparecen con versiones recientes.
Comparativa con otras opciones de mapas
react-native-maps no es la única forma de mostrar mapas en React Native. Conviene conocer las alternativas para elegir con criterio.
react-native-maps es la opción más madura y documentada. Usa los mapas nativos de cada plataforma, tiene una API declarativa basada en componentes y funciona tanto con Expo como sin él. Es la elección por defecto para la mayoría de las aplicaciones que necesitan mapas estándar, marcadores y formas.
expo-maps es el módulo de mapas desarrollado por el equipo de Expo. Ofrece acceso a Apple Maps en iOS y Google Maps en Android con una API más cercana a los SDK modernos de cada plataforma. Puede ser interesante si tu proyecto ya está totalmente dentro del ecosistema Expo y quieres aprovechar funciones recientes de los SDK nativos, pero su recorrido es más corto y su API difiere entre plataformas.
Las soluciones basadas en Mapbox o MapLibre usan teselas vectoriales y permiten estilos de mapa completamente personalizados, mapas sin conexión y control fino del renderizado. Son la mejor opción para aplicaciones donde el mapa es el producto, como navegación o cartografía especializada, a cambio de más complejidad y, en el caso de Mapbox, de otro modelo de precios.
Una vista web con una biblioteca de mapas para navegador es la alternativa más sencilla técnicamente, pero ofrece peor rendimiento y una experiencia de gestos menos nativa. Solo tiene sentido para mapas muy simples o contenido que ya existe en la web.
Como regla general: empieza con react-native-maps, considera expo-maps si trabajas solo con Expo y quieres seguir su hoja de ruta, y pasa a Mapbox o MapLibre cuando necesites estilos propios o mapas sin conexión.
Casos de uso reales
Para aterrizar todo lo anterior, veamos cómo se combinan estas piezas en aplicaciones típicas.
En un localizador de sucursales, la pantalla arranca centrada en la ciudad, pide permiso de ubicación cuando el usuario pulsa un botón y encuadra con fitToCoordinates las tres tiendas más cercanas. Cada marcador abre un callout con el horario y un botón que lleva a la ficha completa.
En una aplicación de reparto, el backend envía periódicamente la posición del repartidor. El cliente actualiza la coordenada de un único marcador y anima la cámara con animateCamera para seguirlo, mientras una Polyline muestra la ruta prevista. Para que el movimiento sea suave, conviene interpolar entre posiciones en lugar de saltar de una a otra.
En una plataforma inmobiliaria, el usuario desplaza el mapa y la app consulta al backend las propiedades dentro de la región visible al terminar el gesto, usando onRegionChangeComplete con un pequeño retardo para no saturar la API. Los resultados se muestran agrupados en clústeres cuando hay muchos en la misma zona.
En una aplicación de actividad deportiva, la ubicación se registra mientras dura el entrenamiento y la ruta se dibuja con una Polyline que crece en tiempo real. Aquí sí es necesario el permiso de ubicación en segundo plano, con la justificación correspondiente en las tiendas de aplicaciones.
Conclusiones
Integrar mapas en React Native es hoy mucho más sencillo que hace unos años. Con react-native-maps 1.29, React Native 0.88 y Expo SDK 58, el proceso se reduce a instalar la biblioteca, declarar las claves de API y escribir componentes declarativos para el mapa, los marcadores y las rutas. Los pasos manuales de enlazado y las carpetas arrastradas en Xcode pertenecen al pasado.
Los puntos que más impacto tienen en el resultado final son los que no se ven en la pantalla: elegir con criterio el proveedor de mapas en cada plataforma, restringir las claves por paquete y certificado, pedir los permisos de ubicación en el momento adecuado y cuidar el rendimiento cuando el número de marcadores crece. Si dominas esos aspectos, el resto es componer interfaces con React.
Empieza con un MapView sencillo, añade la configuración de Google cuando la necesites y evoluciona hacia clústeres, carga por región y animaciones de cámara a medida que la aplicación lo requiera. Y si algún día el mapa se convierte en el núcleo de tu producto, sabrás cuándo dar el salto a soluciones basadas en teselas vectoriales.
