Geolocalización en IONIC sin perder la cabeza
Lo que aprendí al unir Capacitor, Edge Functions y una base de datos espacial en Supabase.
Geolocalización en IONIC sin perder la cabeza

Lo que aprendí al unir Capacitor, Edge Functions y una base de datos espacial en Supabase.
Si trabajas con Ionic y Capacitor todos los días, ya sabes que los plugins son solo una parte de la historia. La otra parte está en el sistema operativo, el backend y, en muchas ocasiones, en Postgres con detalles que no aparecen en el CREATE TABLE por defecto.
En los últimos meses implementé geolocalización de punta a punta en una app de alquileres: mostrar propiedades cercanas al usuario, geocodificar direcciones al publicar y guardar una coordenada en la base de datos. No fue simplemente instalar @capacitor/geolocation y continuar. El verdadero trabajo estuvo en alinear permisos y UX para que la infraestructura y la experiencia de usuario hablaran el mismo idioma.
El problema no es solo el plugin
En el cliente, el flujo natural es @capacitor/geolocation: pedir permiso, leer latitud y longitud, y en web caer a navigator.geolocation. Eso es correcto, pero incompleto.
En Android necesitas permisos en el manifiesto; en iOS, un NSLocationWhenInUseUsageDescription que explique por qué solicitas ubicación. Si algo falta, el usuario ve un fallo silencioso o un rechazo, y tú ves otro bug en la consola.
Ahí apareció la primera lección: la ubicación es producto, no solo API. Decidimos no fragmentar la interfaz con una vista exclusiva para resultados por proximidad. El dashboard sigue siendo el dashboard: un carrusel principal que, si hay coordenadas y resultados cercanos, muestra primero lo cercano y luego el resto del catálogo. Si el usuario no concede permiso o no hay coincidencias, se mantiene el mismo listado de propiedades que ya veía antes. Así la navegación no se rompe y el usuario no siente que la app cambió de contexto.
Otro detalle importante fue no mostrar un spinner de “buscando cerca de ti” mientras el sistema aún está resolviendo el permiso. Primero debe resolverse la ubicación, o no, y solo después tiene sentido cargar la RPC de cercanías.
RPC significa Remote Procedure Call: una función del backend que la app invoca como si fuera una operación remota.
Del texto en el formulario a una coordenada
Las direcciones llegan como texto: calle, ciudad, zona. El móvil no adivina coordenadas mágicamente; hay que geocodificar. En nuestro caso eso vive en una Supabase Edge Function con Deno: recibe un JSON, llama a un proveedor externo y devuelve { latitude, longitude }. La app no necesita cambiar si mañana cambia el proveedor; el contrato sigue siendo el mismo.
Aquí hay dos aprendizajes que vale la pena dejar claros.
Primero, un 404 en la URL de la función no siempre significa que la función no existe. A veces la función responde 404 porque el proveedor de mapas devolvió “sin resultados” y así lo modelamos. Antes de culpar al routing, conviene revisar el cuerpo de la respuesta y el código de error del proveedor.
Segundo, pasamos de Google Geocoding a Nominatim (OpenStreetMap). Google exige API habilitada, claves, cuotas y facturación. Nominatim exige cumplir su política de uso, por ejemplo un User-Agent identificable y respeto por los límites de la instancia pública. Ninguno es “gratis sin condiciones”; solo tienen condiciones distintas.
Incorrecto:
CREATE OR REPLACE FUNCTION public.set_vivienda_geography(...)
RETURNS void
LANGUAGE plpgsql
SET search_path = public
AS $$
BEGIN
UPDATE vivienda
SET geography = ST_SetSRID(ST_MakePoint(lng, lat), 4326)::geography;
END;
$$;
Correcto:
CREATE OR REPLACE FUNCTION public.set_vivienda_geography(...)
RETURNS void
LANGUAGE plpgsql
SET search_path = public, extensions
AS $$
BEGIN
UPDATE vivienda
SET geography = ST_SetSRID(ST_MakePoint(lng, lat), 4326)::extensions.geography;
END;
$$;
La idea es simple: la geocodificación es una dependencia de producción, no un fetch casual.
mapa mental geocodificación supabase
Donde Ionic no te ayuda: Postgres y geography
Cuando por fin tienes latitud y longitud, quieres guardarlas de forma que las consultas espaciales sean correctas. En Postgres eso suele implicar PostGIS y el tipo geography. Activar la extensión es el primer paso. El segundo, especialmente en Supabase, es entender dónde vive ese tipo.
En muchos proyectos, PostGIS queda en el schema extensions, no en public. Una función almacenada con SET search_path = public puede ejecutar un UPDATE que haga cast a ::geography, y Postgres responde con algo que en el cliente se ve como 400 Bad Request y un mensaje del estilo type “geography” does not exist (42704).
Eso confunde bastante: “¿no habíamos instalado PostGIS?”. Sí, pero el tipo no se estaba resolviendo dentro del search_path de la función. La solución fue alinear la función con la realidad del servidor: incluir extensions en el search_path y usar cast explícito a extensions.geography.
➡ El flujo completo, en una línea mental:
Mapa mental
- App → geocodificación (Edge) → RPC que escribe el punto → PostGIS para consultas “cerca de mí”.
El móvil manda números y tokens; el modelo espacial vive en la base.
¿Qué aprendí de todo esto?
En Ionic, la geolocalización es sobre todo UX y permisos: cuándo pedirlos, qué mostrar mientras el sistema decide y cómo evitar que una funcionalidad secundaria rompa la pantalla principal.
En el backend, todo es cuestión de contratos y tipos: qué devuelve la Edge Function, dónde vive la extensión, cómo resuelve Postgres los schemas y cómo modelas los errores para que no parezcan otra cosa.
La integración real consiste en alinear esas capas sin exponer al usuario la complejidad técnica. Una pantalla coherente, un listado que tiene sentido con o sin GPS y errores que enseñan al equipo a mirar Network y SQL, no solo el plugin de Capacitor.
Referencias
메타데이터
- post_id
- a6d8d62ea62e
- slug
- geolocalización-en-ionic-sin-perder-la-cabeza-a6d8d62ea62e
- url
- https://medium.com/somos-pragma/geolocalizaci%C3%B3n-en-ionic-sin-perder-la-cabeza-a6d8d62ea62e
- canonical_url
- https://medium.com/somos-pragma/geolocalizaci%C3%B3n-en-ionic-sin-perder-la-cabeza-a6d8d62ea62e
- author_url
- https://medium.com/@diegocaceres1997garcia
- status
- ok
- fetched_at
- 2026-06-09 15:37:30