Bloquear el acceso según la ubicación IP

Autor:Lisa Farrell · 2026-01-18

Una solución de control de acceso en el frontend basada en la ubicación IP: cuando la página se carga, solicita https://my.ipin.io/info para obtener el código de país del visitante (country). Cuando country se identifica como TW (u otras regiones que configures), la página deja de proporcionar contenido normal y termina el flujo de acceso directamente en el frontend.

Muchas necesidades de “restringir el acceso por región” en realidad no buscan construir una seguridad fuerte (del tipo anti-hackers), sino que son escenarios comunes del frontend:

  • El servicio no está disponible temporalmente en ciertos países/regiones (por cumplimiento, derechos de autor, pagos, logística, etc.)
  • Realizar una verificación de región cuando el usuario entra a la página y luego decidir si mostrar contenido/botones/enlaces de descarga
  • Las campañas de marketing solo se dirigen a regiones específicas; otras regiones necesitan un aviso directo de “fuera del área de servicio” o “no disponible”

Este enfoque es especialmente adecuado para el frontend: el navegador solicita directamente GET https://my.ipin.io/info, y la API devuelve información de geolocalización según la “IP que inicia la solicitud”, por ejemplo:

{"ip":"185.220.236.7","country":"TW","region":"Taiwan","city":"Taipei"}

Luego, solo necesitas comprobar `country === "TW"` para ejecutar la acción de “bloquear el acceso” (por ejemplo: mostrar un aviso de acceso denegado, mostrar texto de 404, o sobrescribir la página original con tu propia estructura de página de error). El punto clave aquí es: no estamos hablando de “redirecciones”; en su lugar, presentamos claramente el resultado de acceso como un error o una denegación, para que el usuario vea directamente el estado final “no accesible”.

1) Lógica y principio de funcionamiento (2 párrafos)

Párrafo 1: Flujo
El usuario abre la página web → el navegador solicita inmediatamente https://my.ipin.io/info → obtiene `country` → si coincide con la lista negra (por ejemplo, `TW`), bloquea de inmediato la visualización posterior: deja de renderizar contenido normal y muestra directamente un estado final como “acceso denegado / 404 / error personalizado”; de lo contrario, continúa cargando normalmente las funciones y el contenido.

Párrafo 2: Por qué el frontend encaja con esta API
Como el navegador solicita directamente una API de terceros, el tercero ve la “IP pública” del visitante, y el país/región devuelto corresponde al propio usuario—exactamente el efecto que buscas.

2) Implementación de código (lo más simple posible, enfocada en el frontend)

2.1 Lo más simple: si coincide, “acceso denegado” (recomendado)

Coloca este código en el <head> (cuanto antes, mejor). Si coincide, sobrescribe el contenido y muestra un mensaje claro de “Acceso denegado”. Puedes reemplazar el texto por tu aviso de cumplimiento o alcance del servicio.

Ejemplo A: bloquear si country === "TW"

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Acceso denegado</h1>" +
        "<p>El servicio no está disponible en tu región.</p>" +
        "</body>";
    }
  } catch (e) {
    // Si falla, permitir por defecto (puedes cambiar a bloquear por defecto)
  }
})();
</script>

Ejemplo B: bloquear si NO es TW (solo permitir TW)

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country !== "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Acceso denegado</h1>" +
        "<p>Esta página solo está disponible en regiones seleccionadas.</p>" +
        "</body>";
    }
  } catch (e) {
    // Si falla, permitir por defecto (puedes cambiar a bloquear por defecto)
  }
})();
</script>

Ejemplo C: bloquear si TW, US, etc. (lista de bloqueo)

<script>
(async function () {
  const blocked = ["TW", "US"]; // Bloquear si coincide con cualquiera
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (blocked.includes(info.country)) {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Acceso denegado</h1>" +
        "<p>El servicio no está disponible en tu región.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.2 Mostrar 404 Not Found (presentación de “no disponible/no existe”)

Algunos productos prefieren que, en ciertas regiones, parezca que “la página no existe” en lugar de indicar explícitamente “restringido”. En ese caso, puedes sobrescribir el contenido con una estructura de página de error tipo 404. Nota: sigue siendo una presentación en el frontend y no cambia realmente el código de estado HTTP devuelto por el servidor, pero es lo suficientemente clara para la experiencia de acceso.

<script>
(async function () {
  try {
    const { country } = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>404 Not Found</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>404 Not Found</h1>" +
        "<p>The requested URL was not found on this server.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.3 Lista negra como array (más cercano a un uso real)

En escenarios reales, normalmente no se bloquea solo una región, sino que se mantiene una lista negra. El ejemplo de abajo sigue siendo minimalista: si coincide cualquiera, muestra directamente “acceso denegado”.

<script>
(async function () {
  const blocked = ["TW"]; // Códigos de país/región a bloquear
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (blocked.includes(info.country)) {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Acceso denegado</h1>" +
        "<p>El servicio no está disponible en tu región.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.4 React (la versión más corta utilizable: mostrar una página de error tras coincidir)

import { useEffect, useState } from "react";

export default function App() {
  const [blocked, setBlocked] = useState(false);

  useEffect(() => {
    (async () => {
      try {
        const info = await fetch("https://my.ipin.io/info").then(r => r.json());
        if (info.country === "TW") setBlocked(true);
      } catch (e) {}
    })();
  }, []);

  if (blocked) {
    return (
      <div style={{ fontFamily: "system-ui", padding: 40 }}>
        <h1>403 Acceso denegado</h1>
        <p>El servicio no está disponible en tu región.</p>
      </div>
    );
  }

  return <div>...</div>;
}

2.5 Vue (también la versión más corta: mostrar contenido de error tras coincidir)

<script>
export default {
  data() {
    return { blocked: false };
  },
  async mounted() {
    try {
      const info = await fetch("https://my.ipin.io/info").then(r => r.json());
      if (info.country === "TW") this.blocked = true;
    } catch (e) {}
  }
}
</script>

<template>
  <div v-if="blocked" style="font-family:system-ui;padding:40px">
    <h1>403 Acceso denegado</h1>
    <p>El servicio no está disponible en tu región.</p>
  </div>

  <div v-else>...</div>
</template>

3) Implementación en WordPress / Joomla / Magento / Shopify (enfoque frontend + código)

La idea es la misma en todas partes: insertar un “snippet JS súper simple” en el head global del sitio (o en el archivo de layout). Dado que tu objetivo es “control frontend + presentación de error”, mientras el script se ejecute lo suficientemente pronto, puede completar la verificación regional antes de que se renderice el contenido principal y, si coincide, presentar directamente el estado final “acceso denegado / 404 / mensaje de error”.

Recomiendo usar este snippet de forma unificada (la versión más simple de “acceso denegado”):

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Acceso denegado</h1>" +
        "<p>El servicio no está disponible en tu región.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

3.1 WordPress

  • Opción A (la más fácil): usar un plugin de inyección de scripts en header/footer y pegar el script en el área Header (en todo el sitio)
  • Opción B: insertar el script antes de </head> en el archivo del tema `header.php`

3.2 Joomla! / Joomla

  • Normalmente, en el template actual, encontrar <head> en `index.php` y pegar el script antes de </head>
  • O usar una opción del backend/template que permita añadir código personalizado/HTML personalizado en el head (según el template)

3.3 Magento (enfoque de inyección en frontend)

  • Si solo quieres “bloqueo a nivel de presentación”, puedes añadir el script al head global del tema (zona head en theme/layout)
  • En el backend de Magento suele existir “HTML Head” o configuración similar (según versión/tema) cuyo objetivo es inyectar contenido en <head> en todo el sitio

> Como tu objetivo es “evitar código del lado del servidor tanto como sea posible”, aquí no ampliamos enfoques XML/módulos—si puedes inyectar el script en el head, funcionará.

3.4 Shopify

  • Online Store → Themes → Edit code
  • Encontrar `theme.liquid` y pegar el script antes de </head> (en todo el sitio)

3.5 Otros sistemas comunes (patrón general)

  • Cualquier sistema que permita editar el <head> global: colocar el script lo más pronto posible en el head
  • Si solo quieres restringir ciertas páginas: añadir el script solo en los templates de esas páginas

4) Resumen

El núcleo de este artículo se resume en una frase:

> El navegador solicita https://my.ipin.io/info y obtiene { country }. Si country === "TW", termina el acceso a la página mostrando directamente “acceso denegado / 404 / error personalizado”, implementando así una solución frontend para bloquear el acceso por región IP.

Es de bajo costo y rápida de implementar, y encaja con “avisos de cumplimiento / avisos de indisponibilidad regional / filtrado en frontend”. En muchos negocios, una “presentación de error” en la capa frontend ya satisface los objetivos de producto y operaciones: el usuario entiende claramente que la página no está disponible en su región, y el contenido principal y los puntos de entrada no se exponen más. Si necesitas un control más estricto “no evadible”, puedes añadir otra capa de bloqueo en el servidor o en el CDN.

5) Preguntas frecuentes (FAQ)

Q1: ¿El bloqueo en frontend realmente puede “denegar el acceso”?

Más precisamente, permite que la “capa de presentación” muestre un resultado de rechazo claro (por ejemplo, una página 403/404/mensaje de error), impidiendo que usuarios “normales” sigan usando las funciones de la página. Sin embargo, personas con conocimientos pueden desactivar JavaScript o modificar scripts, por lo que no es una barrera de seguridad no evadible. Si necesitas un “bloqueo absoluto”, añade una capa adicional en el servidor o en el CDN. Si tu objetivo es un mensaje de cumplimiento y de indisponibilidad, el bloqueo en frontend suele ser suficiente.

Q2: ¿Por qué poner el script en el <head> y lo más pronto posible?

Porque cuanto antes se verifique, antes se puede terminar el flujo de acceso antes de que se renderice el contenido principal, reduciendo el “parpadeo” donde el usuario ve la página brevemente antes del bloqueo. También evita cargar recursos innecesarios (scripts, imágenes, componentes). Es la ubicación más razonable para experiencia de usuario y rendimiento.

Q3: ¿Qué pasa si falla la solicitud a https://my.ipin.io/info?

La estrategia más común es “permitir por defecto” para que una falla de una API externa no bloquee por error a usuarios normales. Otra estrategia es “denegar por defecto”, adecuada para escenarios de cumplimiento estricto. Puedes elegir según el riesgo del negocio. En cualquier caso: mantener la lógica simple — si se obtiene country, se evalúa; si no, se aplica la política por defecto.

Q4: ¿Habrá problemas de CORS?

Es posible. Si la API no permite acceso cross-origin desde el navegador, fetch será bloqueado. En ese caso, debes asegurarte de que la API habilite CORS (por ejemplo, permitiendo tu dominio), de lo contrario el navegador no podrá leer el JSON devuelto. Para un enfoque “frontend-first”, CORS es el punto principal a confirmar con antelación.

Q5: ¿Qué estándar sigue el valor country?

Normalmente es un código de país/región de dos letras (comúnmente ISO 3166-1 alpha-2). TW es un ejemplo típico. En la práctica, se recomienda usar códigos en mayúsculas de forma consistente y mantener la lista negra en el mismo formato para evitar fallos por diferencias de mayúsculas/minúsculas.

Q6: ¿Cómo pasar de “bloquear un país” a “solo permitir ciertos países”?

Solo invierte la lógica:
- allowlist: permitir solo ["US","JP"], y para el resto mostrar “acceso denegado/mensaje de error”
- blocklist: bloquear solo ["TW"], y permitir el resto
Ambas opciones consisten en verificar si country está en una lista; la diferencia está en el comportamiento por defecto.