Use Cases (Actualizado: 2/6/2026)

Geolocation API con Claude Code: permisos, privacidad, fallback y tests

Implementa Geolocation API con Claude Code: permisos, HTTPS, fallback manual, privacidad y pruebas.

Geolocation API con Claude Code: permisos, privacidad, fallback y tests

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ónValor recomendadoFallo frecuente
Momento del permisoTras un clic del usuarioPedir ubicación al cargar la página
PrecisiónAlta precisión apagada por defectoUsar GPS para cualquier lista cercana
FallbackDirección o código postalBloquear el flujo si el usuario niega
LogsEventos y rangos gruesosEnviar 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.

#Claude Code #Geolocation #geolocalizacion #mapa #Web API
Gratis

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.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.