Geolocation API con Claude Code: permisos, privacidad, fallback y tests
Implementa Geolocation API con Claude Code: permisos, HTTPS, fallback manual, privacidad y pruebas.
La geolocalización parece una función pequeña: un botón, una coordenada y un mapa. En producción es más delicada. El navegador muestra un permiso sensible, el sitio debe ejecutarse en un contexto seguro, el usuario puede negar el acceso, el dispositivo puede tardar demasiado y los logs pueden terminar guardando latitud y longitud sin que nadie lo haya decidido.
Claude Code ayuda mucho si recibe una especificación concreta. Si solo pides “añade ubicación actual”, puede generar un snippet que llama a getCurrentPosition y muestra el resultado. Una implementación publicable necesita explicar el motivo antes del prompt del navegador, ofrecer entrada manual de dirección o código postal, manejar timeout, separar la capa de mapas y probar con ubicación simulada.
Las referencias primarias usadas son MDN Geolocation API, getCurrentPosition, watchPosition, Permissions API, la especificación W3C de Geolocation, el requisito de secure origins en Chrome, Chrome DevTools Sensors, Playwright emulation y Claude Code permissions. Para continuar dentro del sitio, revisa integración de mapas, auditoría de seguridad y diseño responsivo.
Define el límite antes de programar
Geolocation API no es un proveedor de mapas. Solo pide al navegador una posición del dispositivo. El navegador puede usar GPS, Wi-Fi, torres celulares, IP, servicios del sistema operativo o datos en caché. La aplicación no controla la fuente exacta y tampoco debe prometer precisión perfecta.
Antes de abrir Claude Code, toma cuatro decisiones. La primera es cuándo pedir permiso: hazlo después de una acción clara del usuario. La segunda es la precisión: empieza con enableHighAccuracy: false salvo que el producto necesite seguimiento fino. La tercera es el fallback: dirección, ciudad, código postal o punto de recogida. La cuarta es retención: si puedes calcular resultados sin guardar coordenadas exactas, no las guardes.
| Decisión | Valor recomendado | Fallo frecuente |
|---|---|---|
| Momento del permiso | Tras un clic del usuario | Pedir ubicación al cargar la página |
| Precisión | Alta precisión apagada por defecto | Usar GPS para cualquier lista cercana |
| Fallback | Dirección o código postal | Bloquear el flujo si el usuario niega |
| Logs | Eventos y rangos gruesos | Enviar position.coords a analytics |
También separa responsabilidades. Geolocation entrega coordenadas. Google Maps, Mapbox, OpenStreetMap, geocoding, rutas y búsqueda de tiendas son otra capa, con claves, cuota y facturación propias.
Casos de uso reales
El primer caso es un buscador de tiendas. El usuario pulsa “usar mi ubicación” y la app ordena sucursales por distancia. Debe existir un campo de código postal junto al botón, porque negar el permiso no significa que el usuario no quiera comprar.
El segundo caso es disponibilidad de entrega o servicio a domicilio. Una tienda, reparación técnica o app de alquiler puede calcular si el usuario está dentro de zona y mostrar horarios. Normalmente basta guardar inside_zone, distance_bucket o la tienda asignada, no coordenadas exactas.
El tercer caso es check-in de personal de campo. Limpieza, mantenimiento, eventos o ventas pueden confirmar que una persona está cerca del punto esperado. El flujo debe tener alternativa: foto, aprobación del supervisor o nota manual cuando el dispositivo no consiga ubicación.
El cuarto caso es contenido local: clima, eventos, inventario cercano o avisos municipales. Si solo necesitas ciudad o región, un selector manual puede ser mejor que pedir ubicación precisa.
Ejemplo ejecutable con getCurrentPosition
Guarda este archivo como geo-demo.html y ejecútalo desde localhost o HTTPS. Incluye explicación de permiso, timeout, maximumAge, resultado redondeado y fallback manual.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Geolocation demo</title>
<style>
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
margin: 2rem;
}
button,
input {
font: inherit;
padding: 0.7rem 0.9rem;
}
.panel {
border: 1px solid #ddd;
max-width: 36rem;
padding: 1rem;
}
</style>
</head>
<body>
<main class="panel">
<h1>Buscar tiendas cercanas</h1>
<p>
Usamos tu ubicación solo para ordenar tiendas.
No guardamos tu dirección exacta ni historial.
</p>
<button id="useLocation" type="button">Usar mi ubicación</button>
<p id="status" role="status" aria-live="polite"></p>
<pre id="result"></pre>
<form id="manualForm">
<label for="postcode">Código postal o ciudad</label>
<input id="postcode" name="postcode" autocomplete="postal-code" />
<button type="submit">Buscar manualmente</button>
</form>
</main>
<script type="module">
const status = document.querySelector("#status");
const result = document.querySelector("#result");
const button = document.querySelector("#useLocation");
const form = document.querySelector("#manualForm");
function showManual(reason) {
status.textContent =
`${reason}. También puedes buscar por código postal.`;
}
function onSuccess(position) {
const { latitude, longitude, accuracy } = position.coords;
status.textContent = "Ubicación recibida.";
result.textContent = JSON.stringify(
{
lat: Number(latitude.toFixed(5)),
lng: Number(longitude.toFixed(5)),
accuracyMeters: Math.round(accuracy),
},
null,
2,
);
}
function onError(error) {
const messages = {
1: "El permiso de ubicación fue denegado",
2: "La posición del dispositivo no está disponible",
3: "La solicitud de ubicación agotó el tiempo",
};
showManual(messages[error.code] ?? "Ubicación no disponible");
}
button.addEventListener("click", () => {
if (!("geolocation" in navigator)) {
showManual("Este navegador no soporta Geolocation");
return;
}
status.textContent = "Comprobando permiso de ubicación...";
navigator.geolocation.getCurrentPosition(onSuccess, onError, {
enableHighAccuracy: false,
timeout: 8000,
maximumAge: 60000,
});
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
status.textContent =
`Buscando cerca de "${data.get("postcode")}".`;
});
</script>
</body>
</html>
timeout limita la espera. maximumAge permite reutilizar una posición reciente. enableHighAccuracy pide mayor precisión cuando el navegador pueda ofrecerla, pero puede tardar más y consumir más batería.
watchPosition sin olvidarse de parar
watchPosition sirve para recibir cambios continuos. Úsalo en rutas, entregas o check-in activo, no en una búsqueda puntual. El ID devuelto debe limpiarse con clearWatch.
import { useEffect, useRef, useState } from "react";
type LocationPoint = {
lat: number;
lng: number;
accuracy: number;
at: string;
};
export function TrackingPanel() {
const watchId = useRef<number | null>(null);
const [points, setPoints] = useState<LocationPoint[]>([]);
const [error, setError] = useState<string | null>(null);
function start() {
if (!navigator.geolocation || watchId.current !== null) return;
watchId.current = navigator.geolocation.watchPosition(
(position) => {
const { latitude, longitude, accuracy } = position.coords;
setPoints((current) => [
{
lat: Number(latitude.toFixed(5)),
lng: Number(longitude.toFixed(5)),
accuracy: Math.round(accuracy),
at: new Date(position.timestamp).toISOString(),
},
...current.slice(0, 9),
]);
},
(err) => setError(`No se pudo seguir: ${err.code}`),
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 5000,
},
);
}
function stop() {
if (watchId.current === null) return;
navigator.geolocation.clearWatch(watchId.current);
watchId.current = null;
}
useEffect(() => stop, []);
return (
<section>
<button type="button" onClick={start}>Iniciar seguimiento</button>
<button type="button" onClick={stop}>Detener</button>
{error && <p role="alert">{error}</p>}
<ol>
{points.map((point) => (
<li key={point.at}>
{point.lat}, {point.lng}
{" / "}
{point.accuracy}m
</li>
))}
</ol>
</section>
);
}
En un producto real, el seguimiento debe ser visible. “Iniciar ruta”, “pausar” y “finalizar” son estados de producto, no detalles técnicos.
Privacidad, permisos y logs
No envíes latitud y longitud completas a logs. Pueden aparecer en herramientas de errores, eventos de analytics, grabaciones de sesión y tickets de soporte. Registra el resultado del flujo o una versión muy gruesa.
type GeoLogInput = {
lat: number;
lng: number;
accuracy: number;
permission: "granted" | "prompt" | "denied" | "unknown";
};
export function toPrivacySafeGeoLog(input: GeoLogInput) {
return {
permission: input.permission,
accuracyBucket:
input.accuracy <= 50 ? "high" :
input.accuracy <= 500 ? "medium" : "low",
latBucket: Number(input.lat.toFixed(2)),
lngBucket: Number(input.lng.toFixed(2)),
};
}
La Permissions API puede mostrar si el estado es granted, prompt o denied, pero el prompt real aparece al llamar a Geolocation. Úsala para mejorar el texto de la interfaz, no para eliminar el flujo de fallback.
export async function readGeoPermission() {
if (!("permissions" in navigator)) return "unknown";
try {
const status = await navigator.permissions.query({
name: "geolocation",
});
return status.state;
} catch {
return "unknown";
}
}
Fallos concretos que debes probar
Prueba HTTP, HTTPS e iframe. Geolocation requiere contexto seguro y puede estar bloqueada por Permissions-Policy. Si tu widget se incrusta en un portal externo, el permiso del iframe importa.
Prueba permiso denegado. La interfaz debe ofrecer dirección o código postal. No debe repetir el prompt ni mostrar un error técnico.
Prueba timeout y ubicación no disponible. Escritorios, interiores, VPN, sistemas operativos sin ubicación y navegadores gestionados por empresa fallan con frecuencia.
Prueba caché antigua. maximumAge acelera, pero puede devolver una ubicación anterior. Para check-in usa un valor más estricto que para una lista de tiendas.
import { expect, test } from "@playwright/test";
test.use({
geolocation: {
latitude: 40.416775,
longitude: -3.70379,
accuracy: 50,
},
permissions: ["geolocation"],
});
test("shows nearby stores from mocked location", async ({ page }) => {
await page.goto("/stores");
await page.getByRole("button", { name: "Usar mi ubicación" }).click();
await expect(page.getByText("Ubicación recibida")).toBeVisible();
});
Prompt seguro para Claude Code
El prompt debe especificar alcance, límites y verificación. Así evitas que el agente toque claves de mapas, billing o eventos de analytics.
claude <<'PROMPT'
Implement a beginner-friendly Geolocation feature.
Scope:
- Edit only src/features/location and related tests.
- Do not change billing, analytics, or map provider config.
- Preserve existing API keys and environment variable names.
Requirements:
- Request location only after the user clicks a button.
- Explain why location is needed before the browser prompt.
- Use getCurrentPosition with timeout and maximumAge.
- Add manual postcode/address fallback for denied or timeout cases.
- Do not log raw latitude or longitude.
- Add a Playwright test with mocked geolocation.
- Return a short verification checklist.
PROMPT
Para equipos, añade reglas de permisos y mantén los secretos fuera del alcance del agente.
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Bash(git push *)"
],
"allow": [
"Bash(npm test *)",
"Bash(npm run lint)"
]
}
}
Si necesitas convertir esto en una checklist de equipo, revisar un flujo real o preparar prompts seguros para tu repositorio, la página de Claude Code training and consultation es el siguiente paso práctico.
Nota de verificación práctica
Probé los ejemplos en Chrome con localhost, DevTools Sensors con Madrid y “Location unavailable”, y Playwright con permiso de geolocalización concedido. Antes de publicar, añade pruebas para permiso denegado, ubicación del sistema desactivada, iframe de terceros, cuota del proveedor de mapas y búsqueda de logs que contengan coordenadas completas.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.