Saltar al contenido

Lección 12 de 20 · Lectura y práctica

Integración de Geolocalización y Mapas

Tipo de lección: Video Integración de Geolocalización y Mapas - React Native & Expo Integración de Geolocalización y Mapas En esta lección, profundizaremos en una de las capacidades más poderosas y contextuales de los.

Del concepto a la práctica

Taller de esta lección

La ubicación tiene tres datos distintos: permiso, coordenadas y precisión. Una coordenada es una pareja numérica, no una dirección ni una prueba de que el usuario desea compartirla. La interfaz debe pedir autorización con un propósito comprensible y mostrar qué ocurrió.

Ejemplo resuelto

Un resultado local puede ser { latitude: 40.4168, longitude: -3.7038, accuracy: 25 }. El mapa centra la vista con esos números y una etiqueta informa Precisión aproximada: 25 m. No se inventa una calle a partir de la coordenada.

Pruébalo

  1. Consulta el permiso y detén el flujo si fue rechazado.

  2. Solicita una lectura puntual de ubicación.

  3. Valida que latitud y longitud sean números finitos.

  4. Renderiza el mapa y un estado de error separado.

Error frecuente: guardar coordenadas antiguas como si fueran la posición actual. Corrección: registra la hora de lectura y distingue última ubicación conocida de ubicación actual.

Comprueba tu resultado

Ejercicio: usa las coordenadas anteriores y después simula un error de permiso. Resultado esperado: el primer caso centra el mapa en latitud 40.4168 y longitud -3.7038; el segundo no muestra un marcador inventado y explica que falta autorización.

Lectura y efecto asíncrono válidos

Valida latitud finita entre−90/90 y longitud entre−180/180, aceptando0. coords.accuracy puede ser null; muestra Precisión no disponible en ese caso y no0 m. Conserva timestamp y distingue la pareja simulada40.4168/−3.7038 del GPS realmente observado. Si falla permiso/lectura, no uses la posición del fixture como si fuera actual.

El ejemplo histórico tiene dos errores sintácticos: = = useState y await dentro de un efecto no async. Un efecto debe devolver limpieza, no una promesa. Este patrón reemplaza la suscripción y evita fuga si la promesa resuelve después de desmontar:

jsx
import {useEffect} from 'react';
import * as Location from 'expo-location';
export function usePosicionActiva(activo,onPosicion,onError){
  useEffect(()=>{
    if(!activo) return;
    let cancelado=false,suscripcion;
    (async()=>{
      try{
        const permiso=await Location.requestForegroundPermissionsAsync();
        if(!permiso.granted) throw new Error('Permiso denegado');
        const creada=await Location.watchPositionAsync({accuracy:Location.Accuracy.Balanced},p=>{
          if(!cancelado) onPosicion(p);
        });
        if(cancelado) creada.remove(); else suscripcion=creada;
      }catch(e){if(!cancelado) onError(e);}
    })();
    return ()=>{cancelado=true;suscripcion?.remove();};
  },[activo,onPosicion,onError]);
}

El consumidor define callbacks estables y estado para ubicación/error; detener activo elimina la escucha. Configura proveedor y claves del mapa según plataforma/build; no requiere siempre habilitar una capacidad Maps especial para cualquier MapKit. Para una foto puntual basta lectura en primer plano y no se solicita permiso de fondo.

Solicitud de permiso lleva a acceso autorizado o denegado, con alternativa y acceso a ajustes del sistema.
CursaloMapa de práctica
Pide permiso cuando hace falta

Solicita el permiso cuando la tarea lo necesita y explica su propósito. Si se deniega, ofrece una alternativa y respeta la decisión; si el sistema ya no permite volver a solicitarlo, orienta hacia ajustes sin prometer acceso.

Ver la infografía en tamaño completo ↗

Tipo de lección: Video

Integración de Geolocalización y Mapas

En esta lección, profundizaremos en una de las capacidades más poderosas y contextuales de los dispositivos móviles: la geolocalización y su representación visual en mapas. Aprenderás a solicitar y gestionar permisos, obtener la ubicación del usuario en tiempo real, y mostrar esa información de manera interactiva y útil utilizando mapas nativos. Esta funcionalidad es la base para aplicaciones de delivery, fitness, redes sociales basadas en ubicación, y herramientas de navegación. Dominarás tanto las APIs de Expo como las configuraciones específicas para un manejo robusto y profesional.

Concepto Clave: El Triángulo de la Ubicación Contextual

Piensa en la integración de geolocalización y mapas no como dos características separadas, sino como un sistema interconectado que le da contexto espacial a tu aplicación. Este sistema se compone de tres vértices fundamentales. El primero es el hardware y los sensores del dispositivo (GPS, red celular, Wi-Fi), que recopilan señales crudas. El segundo es el software del sistema operativo (iOS Location Services, Android Fused Location Provider), que fusiona estas señales, gestiona el consumo de batería y proporciona una API unificada. El tercer vértice es tu aplicación React Native, que, a través de puentes como Expo Location y react-native-maps, solicita, interpreta y visualiza estos datos para el usuario final.

Una analogía útil es la de un restaurante con repartidores. Los sensores son los ojos y oídos del repartidor en la calle. El sistema operativo es el manager del local que recibe la información del repartidor, decide cuál es la ruta más eficiente (bajo consumo) y la comunica al cocinero. Tu aplicación es el cocinero, que toma la ubicación del cliente (proporcionada por el manager) y la usa para preparar el pedido y mostrar en una pantalla de cocina un mapa con el trayecto estimado. Sin una comunicación clara entre estas tres partes, la comida llegará fría o se perderá.

Cómo Funciona en la Práctica: Flujo de Permisos y Precisión

El primer paso, y el más crítico, es la gestión de permisos. Tanto iOS como Android tienen modelos de permisos estrictos y diferentes. Expo Location abstrae gran parte de esta complejidad, pero es vital entender lo que sucede bajo el capó. En iOS, debes definir las claves NSLocationWhenInUseUsageDescription o NSLocationAlwaysUsageDescription en tu `app.json` o `info.plist`, con una descripción clara para el usuario de por qué necesitas su ubicación. En Android, necesitas los permisos ACCESS_FINE_LOCATION y/o ACCESS_COARSE_LOCATION en tu `app.json`. La elección entre "cuando está en uso" y "siempre" tiene implicaciones importantes para la revisión de App Store y la experiencia del usuario.

Una vez concedidos los permisos, el proceso de obtención de ubicación comienza. No es un simple "obtener coordenadas". Debes decidir entre precisión alta (para aplicaciones de navegación) o baja (para check-ins sociales), lo que afecta directamente al consumo de batería. El Fused Location Provider de Android es inteligente y combina GPS, redes y sensores para equilibrar precisión y eficiencia. En iOS, el Core Location hace un trabajo similar. Tu código debe estar preparado para manejar escenarios donde la ubicación no esté disponible inmediatamente (el dispositivo está en interiores), o la precisión sea pobre inicialmente y mejore con el tiempo.

El siguiente paso práctico es la integración del mapa. La biblioteca `react-native-maps` proporciona componentes nativos (`MapView`, `Marker`, `Polyline`) que se renderizan utilizando `MapKit` en iOS y `Google Maps` en Android. Esto requiere configuración adicional: en iOS, necesitas habilitar la capacidad de Maps en Xcode (manejado por Expo prebuild), y en Android, necesitas una clave de API de Google Maps. Sin esta clave, verás un mapa en blanco con una marca de agua en Android. La configuración se realiza a través del archivo `app.json` en un proyecto de Expo, centralizando así la gestión de claves y permisos.

Código en Acción: Seguimiento de Ubicación en Tiempo Real con Mapa

Vamos a construir un componente funcional que solicite permisos, obtenga la ubicación actual, la muestre en un mapa, y luego inicie un seguimiento en tiempo real para dibujar el recorrido del usuario. Este es un patrón común en aplicaciones de actividad física. Asegúrate de tener instaladas las dependencias: `expo-location` y `react-native-maps`.

import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text, Alert, Button } from 'react-native';
import MapView, { Marker, Polyline } from 'react-native-maps';
import * as Location from 'expo-location';

const LocationTrackerMap = () => {
  const [location, setLocation] = = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);
  const [tracking, setTracking] = useState(false);
  const [pathCoordinates, setPathCoordinates] = useState([]);

  // Solicitar permisos y obtener ubicación inicial
  useEffect(() => {
    (async () => {
      let { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        setErrorMsg('Permiso para acceder a la ubicación denegado.');
        Alert.alert('Permiso necesario', 'Esta app necesita acceso a tu ubicación para funcionar.');
        return;
      }

      let currentLocation = await Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.Balanced, // Equilibrio entre precisión y batería
      });
      setLocation(currentLocation);
      // Inicializar el camino con la primera ubicación
      setPathCoordinates([{
        latitude: currentLocation.coords.latitude,
        longitude: currentLocation.coords.longitude,
      }]);
    })();
  }, []);

  // Efecto para manejar el seguimiento en tiempo real
  useEffect(() => {
    let locationSubscription = null;
    if (tracking) {
      locationSubscription = await Location.watchPositionAsync(
        {
          accuracy: Location.Accuracy.High, // Alta precisión para tracking
          timeInterval: 1000, // Actualizar cada segundo
          distanceInterval: 2, // O cada 2 metros
        },
        (newLocation) => {
          const newCoords = {
            latitude: newLocation.coords.latitude,
            longitude: newLocation.coords.longitude,
          };
          setLocation(newLocation);
          // Añadir nuevas coordenadas al camino (inmutabilidad)
          setPathCoordinates(prevPath => [...prevPath, newCoords]);
        }
      );
    }

    // Cleanup: Detener la suscripción cuando el componente se desmonte o se detenga el tracking
    return () => {
      if (locationSubscription) {
        locationSubscription.remove();
      }
    };
  }, [tracking]);

  const startTracking = () => setTracking(true);
  const stopTracking = () => setTracking(false);

  let initialRegion = {
    latitude: 37.78825,
    longitude: -122.4324,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  };

  if (location) {
    initialRegion = {
      latitude: location.coords.latitude,
      longitude: location.coords.longitude,
      latitudeDelta: 0.005, // Zoom más cercano
      longitudeDelta: 0.005,
    };
  }

  return (
    
      {errorMsg ? (
        {errorMsg}
      ) : location ? (
        <>
          
            {/* Marcador personalizado en la ubicación actual */}
            
            {/* Línea que dibuja el recorrido */}
            
          
          
            
            
            Puntos en ruta: {pathCoordinates.length}
            Estado: {tracking ? 'Activo' : 'Inactivo'}
          
        
      ) : (
        Obteniendo ubicación...
      )}
    
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    width: '100%',
    height: '70%',
  },
  controls: {
    padding: 20,
    height: '30%',
    justifyContent: 'space-around',
  },
});

export default LocationTrackerMap;

Este código es un ejemplo robusto. El primer `useEffect` maneja la inicialización: permisos y primera ubicación. El segundo `useEffect` es el corazón del seguimiento en tiempo real, suscribiéndose y desuscribiéndose correctamente para evitar fugas de memoria. El uso de `Polyline` toma el array de coordenadas acumuladas y dibuja una línea roja sobre el mapa, mostrando visualmente el recorrido. La propiedad `showsUserLocation={true}` activa el punto azul nativo del sistema, que suele tener comportamientos optimizados de precisión y batería.

Tip Profesional: Para aplicaciones de producción que requieren tracking en segundo plano, necesitarás permisos adicionales (`ACCESS_BACKGROUND_LOCATION` en Android y `NSLocationAlwaysAndWhenInUseUsageDescription` en iOS) y probablemente el uso de `TaskManager` de Expo o un módulo nativo específico. El seguimiento en segundo plano es complejo y debe justificarse claramente ante el usuario y las tiendas de aplicaciones.

Configuración de app.json para Expo

Para que el código anterior funcione completamente, especialmente en Android, necesitas configurar las claves y permisos en tu `app.json`. Aquí está la sección relevante:

{
  "expo": {
    "plugins": [
      [
        "expo-location",
        {
          "locationAlwaysAndWhenInUsePermission": "Esta aplicación utiliza tu ubicación para mostrar tu posición en el mapa y registrar recorridos.",
          "isIosBackgroundLocationEnabled": false, // Cambiar a true si necesitas fondo en iOS
          "isAndroidBackgroundLocationEnabled": false
        }
      ]
    ],
    "ios": {
      "infoPlist": {
        "NSLocationWhenInUseUsageDescription": "Esta app necesita tu ubicación para mostrarte en el mapa y grabar tus rutas.",
        "NSLocationAlwaysAndWhenInUseUsageDescription": "Esta app necesita tu ubicación en segundo plano para continuar grabando tu ruta mientras usas otras apps."
      },
      "config": {
        "googleMapsApiKey": "TU_CLAVE_DE_GOOGLE_MAPS_PARA_IOS_AQUI"
      }
    },
    "android": {
      "permissions": [
        "ACCESS_FINE_LOCATION",
        "ACCESS_COARSE_LOCATION"
        // Añadir "ACCESS_BACKGROUND_LOCATION" solo si es estrictamente necesario
      ],
      "config": {
        "googleMaps": {
          "apiKey": "TU_CLAVE_DE_GOOGLE_MAPS_PARA_ANDROID_AQUI"
        }
      }
    }
  }
}

Errores Comunes y Cómo Evitarlos

1. Mapa en Blanco en Android (Solo Logotipo de Google): Este es el error más frecuente. Ocurre porque falta la clave de API de Google Maps o está incorrectamente configurada. Solución: Asegúrate de haber generado una clave de API RESTRICTIVA en Google Cloud Console, habilitado el SDK de Maps para Android, y haberla colocado en `app.json` bajo `android.config.googleMaps.apiKey`. Luego, ejecuta `expo prebuild` o `eas build` para aplicar los cambios.

2. Permisos Denegados o Comportamiento Inconsistente entre iOS/Android: No verificar el estado del permiso antes de usarlo o no manejar el rechazo del usuario. Solución: Siempre llama a `requestForegroundPermissionsAsync()` y verifica el `status` devuelto. Ten un flujo de UI alternativo para cuando el permiso sea denegado (explicar el valor, redirigir a ajustes). Recuerda que en iOS, "una vez" es una opción, y en Android 11+, los permisos pueden ser "solo esta vez".

3. Fugas de Memoria por no Limpiar Suscripciones: Al usar `watchPositionAsync`, no remover el listener cuando el componente se desmonta o cuando se detiene el tracking. Solución: Siempre guarda la referencia de la suscripción devuelta por `watchPositionAsync` y llame a `.remove()` en la función de cleanup del `useEffect`, o cuando sea apropiado detener el seguimiento.

4. Alto Consumo de Batería: Utilizar una precisión excesivamente alta (`Accuracy.Highest`) o intervalos de actualización muy cortos (ej. 100ms) cuando no es necesario. Solución: Ajusta la `accuracy` y los intervalos (`timeInterval`, `distanceInterval`) al caso de uso mínimo viable. Para una app de check-in, `Accuracy.Lowest` o `Balanced` es suficiente. Para navegación paso a paso, necesitarás `High`.

5. Coordenadas o Región del Mapa Mal Definidas: Pasar `latitude` y `longitude` invertidos, o usar `latitudeDelta`/`longitudeDelta` con valores que resulten en un zoom excesivo o un mapa mundial. Solución: Recuerda que `latitude` va de -90 a 90 (vertical), y `longitude` de -180 a 180 (horizontal). Valores delta como 0.01 ofrecen un zoom urbano, mientras que 20 muestra un continente. Depura siempre imprimiendo las coordenadas obtenidas.

Checklist de Dominio

Antes de considerar esta lección completa, asegúrate de poder verificar cada uno de los siguientes puntos:

  • Puedo explicar la diferencia entre los permisos de ubicación "en uso" (foreground) y "siempre" (background) y cuándo usar cada uno.
  • Sé configurar correctamente el archivo `app.json` o `Info.plist`/`AndroidManifest.xml` para solicitar permisos de ubicación tanto en iOS como en Android en un proyecto de Expo.
  • He implementado una lógica robusta para solicitar permisos, manejar la denegación del usuario y degradar la funcionalidad de manera elegante.
  • Puedo obtener la ubicación actual del usuario (`getCurrentPositionAsync`) y configurar correctamente las opciones de precisión para equilibrar exactitud y consumo de batería.
  • Sé suscribirme a actualizaciones de ubicación en tiempo real (`watchPositionAsync`) y gestionar correctamente el ciclo de vida de la suscripción para evitar fugas de memoria.
  • He integrado `react-native-maps` en un proyecto, configurado las claves de API necesarias y utilizado componentes como `MapView`, `Marker` y `Polyline` para visualizar datos geográficos.
  • Puedo calcular y mostrar la región correcta en un `MapView` basándome en las coordenadas del usuario o un conjunto de puntos de interés.
  • Comprendo las implicaciones de rendimiento y batería del seguimiento de ubicación y sé ajustar los parámetros (precisión, intervalos) para diferentes casos de uso.

Profundizando: Geocodificación y Optimizaciones

La geolocalización no termina en las coordenadas. La geocodificación (convertir una dirección en coordenadas) y la geocodificación inversa (convertir coordenadas en una dirección) son esenciales para aplicaciones completas. Expo Location ofrece métodos para esto: `Location.geocodeAsync("Dirección")` y `Location.reverseGeocodeAsync({latitude, longitude})`. Sin embargo, ten en cuenta que estos servicios tienen límites de uso y requieren conexión a internet. Para un uso intensivo, considera usar el SDK de Google Maps o un servicio dedicado como Mapbox directamente desde tu backend.

En cuanto a optimizaciones, considera técnicas como la detección de movimiento estacionario (dejar de actualizar la ubicación si el usuario no se mueve significativamente) y el throttling o debouncing de actualizaciones de UI. No es necesario redibujar el marcador en el mapa con cada actualización de milímetros; puedes acumular cambios y actualizar la vista cada 2-5 segundos o varios metros. Además, para recorridos largos (`Polyline` con miles de puntos), considera simplificar la línea o usar técnicas de clúster para marcadores en un mapa estático, mejorando el rendimiento de forma drástica.

Finalmente, siempre prueba en dispositivos reales en diferentes condiciones: exteriores con buen GPS, interiores con solo Wi-Fi, y en movimiento (coche, transporte público). El simulador es útil para el desarrollo inicial, pero el comportamiento real de la ubicación, especialmente la transición entre fuentes de señal y la precisión, solo se puede validar en hardware físico. Implementa un panel de depuración en tu pantalla de desarrollo que muestre en tiempo real la precisión, la fuente de la ubicación (GPS, red, etc.) y la velocidad, te será de gran ayuda para afinar tu implementación.

Consejo Final de Arquitectura: Para aplicaciones complejas, considera centralizar la lógica de geolocalización en un Contexto de React o en un store (Zustand, Redux). Esto evita la duplicación de código, maneja el estado de permisos y ubicación de forma global, y facilita el acceso a estos datos desde cualquier pantalla de tu aplicación, manteniendo una única fuente de verdad y suscripción activa.

Sigue con React Native y Expo: APIs Nativas y Comercio Móvil

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.