Enlaces, URIs y rutas: la etiqueta a de punta a punta

Por: Artiko
htmlcssjavascriptdomfrontendenlacesurlurimailtonavegacionrutas

Enlaces, URIs y rutas: la etiqueta a de punta a punta

En el capítulo 5 trabajaste con <img>, con background-image y, sobre todo, con la idea de ruta: ese texto que va dentro de src o dentro de url() y que el navegador convierte en una petición concreta a un servidor. Ahí las rutas apuntaban a recursos que el navegador incrustaba dentro de la página sin cambiar de documento.

Este capítulo toma esa misma idea de ruta y la lleva al otro extremo: en vez de traer un recurso hacia el documento actual, vamos a mover al usuario hacia otro documento. Eso es un hipervínculo, y es literalmente la razón por la que la web se llama web. Sin la etiqueta <a> tendrías una colección de archivos HTML sueltos; con ella tienes una red navegable.

Vamos a cubrir el hipervínculo en profundidad: la etiqueta y todos sus atributos útiles, los estados CSS de un enlace y el orden en que deben escribirse, la anatomía completa de una URI según su especificación, la diferencia real entre URI, URL y URN, cómo el navegador resuelve una ruta relativa, por qué index.html es especial, cómo funcionan los enlaces con # y cómo mailto: y tel: salen del navegador para hablarle al sistema operativo.

La etiqueta a: anatomía del hipervínculo

La etiqueta <a> (de anchor, ancla) crea un hipervínculo. Su forma mínima es:

<a href="pagina.html">Texto visible del enlace</a>

Tiene tres partes que conviene nombrar por separado, porque cada una cumple una función distinta:

  • La etiqueta de apertura con el atributo href: define el destino. href significa hypertext reference.
  • El contenido: lo que el usuario ve y sobre lo que hace clic. Puede ser texto, una imagen, o incluso varios elementos.
  • La etiqueta de cierre: </a>.

Un documento completo con enlaces:

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <title>Super Club Net - Enlaces</title>
  </head>
  <body>
    <h1>Super Club Net</h1>
    <p>
      Visita nuestra <a href="nosotros.html">página de nosotros</a>
      o revisa la <a href="https://developer.mozilla.org/es/">documentación de MDN</a>.
    </p>
    <p>También puedes ver el <a href="documentos/reglamento.pdf">reglamento del club</a>.</p>
  </body>
</html>

Si no le das estilos, el navegador aplica su hoja de estilos por defecto: el texto aparece subrayado y en azul, y el cursor cambia a una mano al pasar por encima. Esos dos indicadores no son decoración, son el contrato visual que le dice al usuario “esto se puede clickear”.

El contenido del enlace no tiene que ser texto

Un <a> puede envolver cualquier contenido en línea y, en HTML5, también bloques completos. Esto es perfectamente válido:

<!-- Una imagen que funciona como enlace -->
<a href="galeria.html">
  <img src="imagenes/miniatura-galeria.jpg" alt="Ver la galería del club">
</a>
<!-- Una tarjeta completa como enlace -->
<a href="torneos/2026-verano.html" class="tarjeta">
  <h2>Torneo de Verano 2026</h2>
  <p>Inscripciones abiertas hasta el 15 de enero.</p>
</a>

Cuando envuelves una imagen, el alt de la imagen pasa a ser el texto accesible del enlace. Por eso ese alt debe describir el destino, no la imagen: “Ver la galería del club” es mejor que “foto de una cancha”.

Lo que no puedes hacer es anidar un <a> dentro de otro <a>, ni meter un <button> dentro de un <a>. El navegador corrige el HTML inválido de formas impredecibles y el resultado casi nunca es el que esperabas.

Cuándo usar a y cuándo usar button

Esta distinción confunde a mucha gente y vale la pena fijarla ahora:

ElementoSe usa paraQué hace el navegador
<a href="...">Navegar a otro recurso o a otra parte del documentoCambia la URL, agrega una entrada al historial, permite abrir en pestaña nueva, permite copiar la dirección
<button>Ejecutar una acción en la página actualNo cambia la URL ni el historial; dispara JavaScript o envía un formulario

Reglas prácticas:

  • Si el usuario debería poder hacer clic derecho y elegir “Abrir en una pestaña nueva”, es un <a>.
  • Si al hacer clic se abre un modal, se agrega un producto al carrito o se despliega un menú, es un <button>.
  • Nunca uses <a href="#"> con JavaScript para simular un botón: rompe el historial, ensucia la URL con un # y confunde a los lectores de pantalla, que anunciarán “enlace” cuando en realidad es un control.
<!-- Incorrecto: un botón disfrazado de enlace -->
<a href="#" onclick="abrirMenu()">Menú</a>
<!-- Correcto -->
<button type="button" onclick="abrirMenu()">Menú</button>

Los estados de un enlace en CSS

Un enlace no es un elemento estático: pasa por distintos estados según lo que haga el usuario y según el historial del navegador. CSS expone esos estados como pseudoclases.

stateDiagram-v2
    [*] --> SinVisitar: el usuario nunca abrio este href
    SinVisitar --> Hover: el puntero entra al area del enlace
    Hover --> SinVisitar: el puntero sale
    Hover --> Active: el usuario presiona el boton del mouse
    Active --> Visitado: se suelta el boton y ocurre la navegacion
    SinVisitar --> Focus: el usuario llega con la tecla Tab
    Focus --> Active: el usuario presiona Enter o Espacio
    Visitado --> Hover: vuelve a pasar el puntero
    Visitado --> [*]
    note right of SinVisitar
        a:link
        aplica solo si el href existe
    end note
    note right of Visitado
        a:visited
        el navegador limita
        que propiedades se pueden
        cambiar aqui por privacidad
    end note
    note right of Focus
        a:focus y a:focus-visible
        imprescindibles para teclado
    end note

Las cinco pseudoclases relevantes:

PseudoclaseCuándo aplica
a:linkEl enlace tiene href y el usuario no lo ha visitado
a:visitedEl usuario ya visitó ese destino según el historial del navegador
a:hoverEl puntero está encima del enlace
a:focusEl enlace tiene el foco del teclado (llegaste con Tab)
a:activeEl enlace está siendo presionado en este instante

El orden LVHA no es opcional

Estas pseudoclases tienen la misma especificidad, así que si dos coinciden, gana la que se declaró después. Eso obliga a escribirlas en un orden concreto: :link, :visited, :focus, :hover, :active. La regla mnemotécnica clásica es LVHA (“LoVe HAte”), con :focus insertado antes de :hover.

Si pones :hover antes que :visited, un enlace ya visitado nunca cambiará de color al pasar el mouse, porque :visited lo sobrescribe.

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <title>Estados de un enlace</title>
    <style>
      body { font-family: system-ui, sans-serif; max-width: 40rem; margin: 2rem auto; }
      /* 1 */ a:link { color: #1c5d99; text-decoration: underline; }
      /* 2 */ a:visited { color: #6b3fa0; }
      /* 3 */ a:focus-visible { outline: 3px solid #ffb703; outline-offset: 2px; }
      /* 4 */ a:hover { color: #d62828; text-decoration-thickness: 2px; }
      /* 5 */ a:active { color: #9d0208; }
    </style>
  </head>
  <body>
    <h1>Prueba los estados</h1>
    <ul>
      <li><a href="https://example.com/uno">Enlace probablemente no visitado</a></li>
      <li><a href="https://developer.mozilla.org/">Enlace probablemente visitado</a></li>
      <li><a href="#final">Enlace interno</a></li>
    </ul>
    <p id="final">Presiona Tab para ver el estado de foco.</p>
  </body>
</html>

Dos advertencias importantes

Sobre :visited. Por privacidad, los navegadores limitan severamente qué propiedades puedes cambiar en :visited. Básicamente solo colores (color, background-color, border-color, outline-color). Además, getComputedStyle() desde JavaScript miente sobre esos valores. Esto existe porque, sin la restricción, una página podría medir el estilo de cientos de enlaces y deducir tu historial de navegación completo.

Sobre text-decoration: none. Quitar el subrayado es tentador y muy común, pero deja al color como único indicador de que algo es un enlace. Alrededor del 8 % de los hombres tiene alguna forma de daltonismo, y para muchos de ellos un enlace azul dentro de texto negro es indistinguible. Si quitas el subrayado dentro de un párrafo, agrega otro indicador: negrita, un borde inferior, un fondo sutil. En una barra de navegación, donde el contexto ya deja claro que todo es navegable, quitar el subrayado sí es razonable.

/* Aceptable: navegación, el contexto ya comunica */
nav a {
  text-decoration: none;
}

/* Riesgoso: enlaces dentro de un párrafo sin ningún otro indicador */
p a {
  text-decoration: none;
}

/* Mejor: sin subrayado pero con otro indicador */
p a {
  text-decoration: none;
  border-bottom: 2px solid currentColor;
}

URI, URL y URN: los nombres correctos

El valor de href es una URI. Vale la pena aclarar la terminología porque se usa mal todo el tiempo.

  • URI (Uniform Resource Identifier, identificador uniforme de recursos): una cadena de texto que identifica un recurso. Es el término general.
  • URL (Uniform Resource Locator, localizador uniforme de recursos): una URI que además dice cómo y dónde encontrar el recurso. https://ninjas.cl/libros/ es una URL: indica el protocolo y la ubicación.
  • URN (Uniform Resource Name, nombre uniforme de recursos): una URI que nombra un recurso de forma persistente sin decir dónde está. urn:isbn:9780134685991 identifica un libro concreto, pero no te dice en qué servidor descargarlo.
flowchart TD
    URI["URI<br/>Identificador uniforme de recursos<br/>RFC 3986"]
    URL["URL<br/>Dice DONDE y COMO<br/>obtener el recurso"]
    URN["URN<br/>Dice COMO SE LLAMA<br/>el recurso, no donde esta"]
    URI --> URL
    URI --> URN
    URL --> E1["https://ninjas.cl/libros/"]
    URL --> E2["ftp://ftp.is.co.za/rfc/rfc1808.txt"]
    URL --> E3["mailto:[email protected]"]
    URN --> E4["urn:isbn:0451450523"]
    URN --> E5["urn:ietf:rfc:3986"]
    style URI fill:#1c5d99,color:#ffffff
    style URL fill:#2a9d8f,color:#ffffff
    style URN fill:#e76f51,color:#ffffff

En la práctica diaria casi todo lo que escribes en un href es una URL, y el mundo usa “URL” y “URI” como sinónimos. No es grave, pero saber la diferencia te ayuda a leer especificaciones sin perderte.

La sintaxis genérica de una URI

El documento que define esto es el RFC 3986. Su sintaxis genérica es:

esquema:[//autoridad]ruta[?consulta][#fragmento]

Y la autoridad, cuando existe, se descompone a su vez:

autoridad = [usuario[:contraseña]@]host[:puerto]

Veamos una URL completa desmenuzada:

https://blog.ejemplo.cl:8443/articulos/2026/enlaces?orden=fecha&pagina=2#comentarios
flowchart LR
    subgraph URL["Una URL completa, componente por componente"]
        direction LR
        A["esquema<br/>https"]
        B["host<br/>blog.ejemplo.cl"]
        C["puerto<br/>8443"]
        D["ruta<br/>/articulos/2026/enlaces"]
        E["consulta<br/>orden=fecha y pagina=2"]
        F["fragmento<br/>comentarios"]
    end
    A --> B --> C --> D --> E --> F
    A -.-> A1["Que protocolo usar"]
    B -.-> B1["A que servidor conectarse<br/>se resuelve por DNS"]
    C -.-> C1["En que puerto TCP<br/>se omite si es el estandar"]
    D -.-> D1["Que recurso pedir<br/>dentro de ese servidor"]
    E -.-> E1["Parametros para el servidor<br/>SI viaja en la peticion"]
    F -.-> F1["A que parte del documento ir<br/>NO viaja al servidor"]
    style A fill:#1c5d99,color:#ffffff
    style B fill:#2a9d8f,color:#ffffff
    style C fill:#8ab17d,color:#000000
    style D fill:#e9c46a,color:#000000
    style E fill:#f4a261,color:#000000
    style F fill:#e76f51,color:#ffffff

Tabla de referencia de componentes

ComponenteDelimitadorObligatorioEjemploNotas
Esquemaseguido de :httpsSiempre en minúsculas por convención; define el protocolo
Autoridadprecedida por //Noblog.ejemplo.cl:8443mailto: y urn: no la tienen
Usuario e infoseguido de @Noadmin@Desaconsejado en HTTP; los navegadores lo bloquean o advierten
HostSi hay autoridadblog.ejemplo.clPuede ser un nombre de dominio o una IP
Puertoprecedido por :No:8443Se omite cuando coincide con el puerto por defecto del esquema
Rutaempieza con / si hay autoridadSí (puede ser vacía)/articulos/2026/enlacesSensible a mayúsculas en la mayoría de los servidores
Consultaprecedida por ?Noorden=fecha&pagina=2Pares clave=valor separados por &
Fragmentoprecedido por #NocomentariosLo procesa solo el navegador, nunca se envía al servidor

Ese último punto es de los más importantes del capítulo y volveremos a él: el fragmento no viaja en la petición HTTP. El servidor jamás se entera de qué había después del #.

Ejemplos de distintos esquemas

https://ninjas.cl/books/superclubnet/
http://localhost:4321/tecnologias/
ftp://ftp.is.co.za/rfc/rfc1808.txt
ldap://[2001:db8::7]/c=GB?objectClass?one
mailto:[email protected]
tel:+56912345678
file:///home/usuario/proyectos/index.html
data:text/plain;charset=utf-8,Hola%20mundo

Fíjate en tres detalles:

  1. La dirección IPv6 va entre corchetes ([2001:db8::7]) para que los : de la IP no se confundan con el : del puerto.
  2. mailto: y tel: no llevan // porque no tienen autoridad: no hay un servidor al cual conectarse.
  3. file:/// tiene tres barras porque la autoridad está vacía: es file:// + /home/....

Anatomía del nombre de dominio

El host de una URL suele ser un nombre de dominio, y ese nombre también tiene estructura. Se lee de derecha a izquierda, de lo más general a lo más específico:

flowchart RL
    TLD["cl<br/>TLD<br/>dominio de primer nivel"]
    SLD["ejemplo<br/>SLD<br/>dominio de segundo nivel"]
    SUB1["tienda<br/>subdominio"]
    SUB2["blog<br/>subdominio"]
    TLD --> SLD --> SUB1 --> SUB2
    TLD -.-> N1["Lo administra un registro<br/>NIC Chile en este caso"]
    SLD -.-> N2["Es lo que se registra y se paga"]
    SUB1 -.-> N3["Los crea libremente<br/>el dueno del dominio"]
    SUB2 -.-> N3
    style TLD fill:#1c5d99,color:#ffffff
    style SLD fill:#2a9d8f,color:#ffffff
    style SUB1 fill:#e9c46a,color:#000000
    style SUB2 fill:#f4a261,color:#000000

El nombre completo blog.tienda.ejemplo.cl se lee de derecha a izquierda: cl es el dominio de primer nivel, ejemplo el de segundo nivel, y tienda y blog son subdominios encadenados.

ParteNombreEjemplosQuién lo controla
.cl, .com, .net, .orgTLD, dominio de primer nivel.cl para Chile, .com genéricoRegistros globales o nacionales (NIC Chile para .cl)
ejemploSLD, dominio de segundo nivelgoogle, ninjas, wikipediaQuien lo registra y paga por él
blog, www, apiSubdominiowww.ejemplo.cl, api.ejemplo.clEl dueño del dominio, libremente

www es un subdominio como cualquier otro; no tiene nada de mágico. Es una convención heredada de los noventa, cuando una organización tenía un servidor para web (www), otro para correo (mail) y otro para archivos (ftp).

Sobre los puertos

Un servidor puede tener muchos servicios escuchando al mismo tiempo. El puerto es el número que dice a cuál te quieres conectar.

EsquemaPuerto por defectoSe escribe en la URL
http80Solo si es distinto de 80
https443Solo si es distinto de 443
ftp21Solo si es distinto de 21

Por eso https://ejemplo.cl y https://ejemplo.cl:443 son exactamente la misma dirección. En desarrollo local sí verás puertos explícitos todo el tiempo, porque los servidores de desarrollo escuchan en puertos altos: http://localhost:4321, http://localhost:3000, http://localhost:8080.

http contra https

http transmite todo en texto plano: cualquiera en la misma red puede leer lo que envías. https es el mismo protocolo pero dentro de un túnel cifrado con TLS. Hoy no hay razón para servir un sitio público por http: los certificados son gratuitos y los navegadores marcan como “no seguro” cualquier sitio que no use https.

Rutas absolutas y relativas en href

En el capítulo anterior viste rutas para imágenes. Aquí aplican exactamente las mismas reglas, así que este es un buen momento para formalizarlas.

Supongamos esta estructura de archivos:

sitio/
  index.html
  nosotros.html
  css/
    estilos.css
  imagenes/
    logo.png
  torneos/
    index.html
    2026-verano.html
    resultados/
      index.html
      final.html

Si estás editando torneos/resultados/final.html, estas son las formas de enlazar:

Forma escritaTipoResuelve aCuándo conviene
final.htmlRelativa a la carpeta actual/torneos/resultados/final.htmlArchivos hermanos
./final.htmlRelativa explícita/torneos/resultados/final.htmlIgual a la anterior, más explícita
../2026-verano.htmlSube un nivel/torneos/2026-verano.htmlSubir en la jerarquía
../../nosotros.htmlSube dos niveles/nosotros.htmlVolver a la raíz desde profundo
/nosotros.htmlAbsoluta de raíz/nosotros.htmlSitios servidos desde la raíz de un dominio
https://ejemplo.cl/a.htmlAbsoluta completaEse dominio exactoSitios externos
//ejemplo.cl/a.htmlRelativa de protocoloHereda http o https de la página actualPrácticamente obsoleta, usa https://
flowchart TD
    START["href encontrado en el HTML"] --> Q1{"Empieza con<br/>esquema seguido de dos puntos?"}
    Q1 -->|"Si: https:, mailto:, tel:"| ABS["URL absoluta<br/>se usa tal cual"]
    Q1 -->|No| Q2{"Empieza con //?"}
    Q2 -->|Si| PROTO["Hereda el esquema<br/>de la pagina actual"]
    Q2 -->|No| Q3{"Empieza con /?"}
    Q3 -->|Si| ROOT["Se pega despues de<br/>esquema mas host mas puerto<br/>de la pagina actual"]
    Q3 -->|No| Q4{"Empieza con numeral?"}
    Q4 -->|Si| FRAG["Misma pagina<br/>solo cambia el fragmento<br/>no hay peticion HTTP"]
    Q4 -->|No| Q5{"Empieza con signo de interrogacion?"}
    Q5 -->|Si| QRY["Misma ruta<br/>reemplaza la consulta"]
    Q5 -->|No| REL["Ruta relativa<br/>se resuelve contra la carpeta<br/>del documento actual<br/>o contra la etiqueta base"]
    ABS --> FINAL["URL final resuelta"]
    PROTO --> FINAL
    ROOT --> FINAL
    FRAG --> FINAL
    QRY --> FINAL
    REL --> FINAL
    style START fill:#1c5d99,color:#ffffff
    style FINAL fill:#2a9d8f,color:#ffffff
    style FRAG fill:#e76f51,color:#ffffff

Ejemplo ejecutable de resolución de rutas

Crea esta estructura y ábrela con un servidor local para ver la resolución en acción.

torneos/resultados/final.html:

<head>
  <meta charset="utf-8">
  <title>Final del torneo</title>
  <link rel="stylesheet" href="../../css/estilos.css">
</head>
<body>
  <h1>Final del Torneo 2026</h1>
  <nav>
    <ul>
      <li><a href="index.html">Todos los resultados</a></li>
      <li><a href="../2026-verano.html">Ficha del torneo</a></li>
      <li><a href="../../nosotros.html">Sobre el club</a></li>
      <li><a href="/">Portada</a></li>
      <li><a href="https://ninjas.cl/">Sitio externo</a></li>
    </ul>
  </nav>
  <p>Consulta el <a href="../../documentos/acta-final.pdf">acta oficial en PDF</a>.</p>
</body>

Una precisión sobre la ruta absoluta de raíz (/nosotros.html): la barra inicial significa “desde la raíz del dominio”, no “desde la raíz de mi proyecto”. Si publicas el sitio en https://ejemplo.cl/superclubnet/, entonces /nosotros.html apuntará a https://ejemplo.cl/nosotros.html y dará 404. En ese escenario las rutas relativas son más portables, o bien necesitas la etiqueta <base>.

La etiqueta base

<base> va en el <head> y cambia el punto de referencia para todas las rutas relativas del documento.

<head>
  <meta charset="utf-8">
  <base href="https://ejemplo.cl/superclubnet/">
  <title>Con base</title>
</head>
<body>
  <!-- Esto resuelve a https://ejemplo.cl/superclubnet/nosotros.html -->
  <a href="nosotros.html">Nosotros</a>
</body>

Solo puede haber una etiqueta <base> por documento y debe aparecer antes de cualquier otro elemento que use una ruta. Es una herramienta poderosa y peligrosa: afecta a imágenes, hojas de estilo, formularios y enlaces al mismo tiempo, incluidos los enlaces con #. Úsala con cuidado.

index.html: el documento por defecto

Cuando escribes https://ejemplo.cl/torneos/ estás pidiendo un directorio, no un archivo. El servidor tiene tres opciones: devolver un error, listar el contenido del directorio, o devolver un archivo por defecto. Casi todos los servidores están configurados para lo tercero, y ese archivo por defecto es index.html.

sequenceDiagram
    autonumber
    participant U as Usuario
    participant N as Navegador
    participant D as DNS
    participant S as Servidor web
    U->>N: Escribe https://ejemplo.cl/torneos/
    N->>D: Que IP tiene ejemplo.cl?
    D-->>N: 203.0.113.42
    N->>S: GET /torneos/ HTTP/1.1<br/>Host: ejemplo.cl
    Note over S: La ruta termina en /<br/>se pide un directorio
    S->>S: Busca el documento por defecto<br/>configurado: index.html
    alt Existe /torneos/index.html
        S-->>N: 200 OK con el contenido de index.html
        N-->>U: Renderiza la pagina
    else No existe y el listado esta activado
        S-->>N: 200 OK con el listado del directorio
        N-->>U: Muestra la lista de archivos
    else No existe y el listado esta desactivado
        S-->>N: 403 Forbidden o 404 Not Found
        N-->>U: Muestra la pagina de error
    end

Consecuencias prácticas:

  • https://ejemplo.cl/ y https://ejemplo.cl/index.html sirven el mismo contenido, pero son URLs distintas para efectos de caché, historial y buscadores. Elige una forma y sé consistente; lo habitual es enlazar siempre a la versión sin index.html.
  • Escribe el nombre todo en minúsculas. En Linux, que es donde corre la mayoría de los servidores, Index.html e index.html son archivos diferentes. En Windows y macOS suelen no distinguirse, y ese es justamente el problema: funciona en tu máquina y falla al publicar.
  • Cada carpeta puede tener su propio index.html. Es la forma clásica de construir URLs limpias sin ninguna configuración especial.
sitio/
  index.html              ->  /
  torneos/
    index.html            ->  /torneos/
    2026-verano.html      ->  /torneos/2026-verano.html
  contacto/
    index.html            ->  /contacto/

Algunos servidores aceptan otros nombres (index.htm, default.html, index.php) y el orden de preferencia se configura en el servidor. Si trabajas con un servidor propio, index.html es la apuesta segura.

La barra final importa

/torneos y /torneos/ no son lo mismo, y la diferencia se nota al resolver rutas relativas:

Página actualhref="ficha.html" resuelve a
https://ejemplo.cl/torneos/https://ejemplo.cl/torneos/ficha.html
https://ejemplo.cl/torneoshttps://ejemplo.cl/ficha.html

En el segundo caso el navegador considera que torneos es un archivo, no una carpeta, así que lo descarta y sube un nivel. La mayoría de los servidores emiten una redirección 301 de /torneos a /torneos/ justamente para evitar este problema, pero no todos.

Enlaces con hashtag: fragmentos dentro del documento

Un href que empieza con # no pide nada al servidor. El navegador busca dentro del documento actual un elemento cuyo atributo id coincida con el texto que sigue al #, y hace scroll hasta él.

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <title>Reglamento del Super Club Net</title>
    <style>
      body { font-family: system-ui, sans-serif; max-width: 45rem; margin: 2rem auto; }
      section { min-height: 70vh; padding-top: 1rem; }
    </style>
  </head>
  <body>
    <h1 id="inicio">Reglamento del Super Club Net</h1>
    <nav aria-label="Índice del reglamento">
      <ol>
        <li><a href="#socios">De los socios</a></li>
        <li><a href="#cuotas">De las cuotas</a></li>
        <li><a href="#torneos">De los torneos</a></li>
      </ol>
    </nav>
    <section id="socios">
      <h2>1. De los socios</h2>
      <p>Puede ser socio cualquier persona mayor de 14 años.</p>
      <p><a href="#inicio">Volver al índice</a></p>
    </section>
    <section id="cuotas">
      <h2>2. De las cuotas</h2>
      <p>La cuota mensual se paga dentro de los primeros cinco días.</p>
      <p><a href="#inicio">Volver al índice</a></p>
    </section>
    <section id="torneos">
      <h2>3. De los torneos</h2>
      <p>Los torneos oficiales se organizan cada temporada.</p>
      <p><a href="#inicio">Volver al índice</a></p>
    </section>
  </body>
</html>

Cada <section> tiene un id y cada enlace del índice apunta a ese id con un # delante. El min-height: 70vh está solo para que las secciones sean altas y el salto se note al probar el ejemplo. Ningún clic dentro de esta página genera una petición HTTP: todo el trabajo lo hace el navegador sobre el documento ya cargado.

Las tres formas del fragmento

hrefComportamiento
#seccionSalta al elemento con id="seccion"
#Salta al inicio del documento y agrega un # a la URL
#topSalta al inicio del documento aunque no exista un id="top" (comportamiento definido en la especificación de HTML)

Un enlace href="#" funciona como “volver arriba”, pero ensucia la URL y agrega una entrada al historial. Si de verdad quieres un botón de “volver arriba”, enlaza a un id real del encabezado principal.

Reglas para los id

  • Un id debe ser único en todo el documento. Si hay dos elementos con el mismo id, el navegador salta al primero y el resto es HTML inválido.
  • No puede contener espacios.
  • Distingue mayúsculas de minúsculas: #Socios no encuentra id="socios".
  • Evita acentos y caracteres especiales aunque técnicamente HTML5 los permita, porque tendrás que codificarlos en la URL y se vuelve ilegible.
<!-- Problemático: hay que escribirlo codificado en el href -->
<h2 id="informacion-histórica">Información histórica</h2>
<a href="#informaci%C3%B3n-hist%C3%B3rica">Ir</a>
<!-- Mejor -->
<h2 id="informacion-historica">Información histórica</h2>
<a href="#informacion-historica">Ir</a>

Cómo procesa el navegador un fragmento

sequenceDiagram
    autonumber
    participant U as Usuario
    participant N as Navegador
    participant DOC as Documento en memoria
    participant S as Servidor
    U->>N: Clic en un enlace cuyo href es numeral torneos
    N->>N: Compara origen, host, ruta y consulta<br/>con la URL actual
    alt Solo cambia el fragmento
        Note over N,S: No se envia ninguna peticion HTTP
        N->>DOC: Busca un elemento con id igual a torneos
        alt Lo encuentra
            DOC-->>N: Nodo encontrado
            N->>N: Lo marca como destino del fragmento<br/>y aplica la pseudoclase :target
            N->>N: Hace scroll hasta ese nodo
            N->>N: Mueve el foco del teclado al nodo
            N->>N: Actualiza la barra de direcciones<br/>y agrega una entrada al historial
        else No lo encuentra
            DOC-->>N: Sin coincidencias
            N->>N: No hace scroll, solo cambia la URL
        end
    else Cambia tambien la ruta
        N->>S: GET /reglamento.html
        S-->>N: 200 OK con el documento
        Note over N: El fragmento se aplica<br/>despues de cargar
        N->>N: Hace scroll al id indicado
    end

Fragmentos hacia otro documento

Puedes combinar ruta y fragmento en el mismo href:

<a href="reglamento.html#cuotas">Ver la sección de cuotas del reglamento</a>
<a href="/torneos/2026-verano.html#inscripcion">Cómo inscribirse</a>
<a href="https://ninjas.cl/books/superclubnet/#_enlaces">Sección de enlaces del libro</a>

El navegador primero descarga el documento y luego, cuando termina de construirlo, salta al fragmento. Si el elemento con ese id se inserta más tarde con JavaScript, el salto ya ocurrió y no pasará nada.

Mejorando la experiencia con CSS

Dos propiedades hacen que la navegación por fragmentos se sienta bien:

/* Desplazamiento suave en vez de un salto brusco */
html { scroll-behavior: smooth; }

/* Respeta a quien pidió menos movimiento en su sistema operativo */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

:target {
  /* Deja espacio arriba para que un encabezado fijo no tape el destino */
  scroll-margin-top: 5rem;
  /* Resalta la sección a la que se llegó */
  background-color: #fff3cd;
  transition: background-color 1.2s ease;
}

scroll-margin-top resuelve un problema clásico: si tienes una barra de navegación con position: fixed de 80 píxeles de alto, el scroll deja el título justo debajo de la barra y queda oculto. Esa propiedad le dice al navegador que deje ese margen al hacer scroll.

La pseudoclase :target selecciona el elemento cuyo id coincide con el fragmento actual de la URL. Es CSS puro reaccionando al estado de la URL, sin JavaScript.

Fragmentos de texto

Los navegadores basados en Chromium y algunos otros soportan una extensión llamada text fragments, que permite enlazar a un texto concreto aunque no tenga id. La sintaxis usa el prefijo :~:text= dentro del fragmento:

https://ejemplo.cl/reglamento.html#:~:text=cuota%20mensual

Al abrirlo, el navegador busca “cuota mensual” en la página, hace scroll y lo resalta. Es la función detrás de “Copiar enlace al fragmento resaltado” del menú contextual de Chrome. No está soportado en todos los navegadores, así que sirve para compartir enlaces, no para construir la navegación de tu sitio.

Enlaces con protocolo: mailto, tel y compañía

Hasta aquí todos los href apuntaban a documentos. Pero el esquema de una URI puede indicarle al navegador que no debe navegar, sino delegar la acción al sistema operativo.

flowchart TD
    CLICK["El usuario hace clic en un enlace"] --> SCHEME{"Que esquema tiene el href?"}
    SCHEME -->|"http o https"| WEB["El navegador hace<br/>una peticion HTTP<br/>y renderiza el documento"]
    SCHEME -->|"solo un fragmento"| HASH["El navegador hace scroll<br/>dentro del documento actual"]
    SCHEME -->|"mailto:"| MAIL["El navegador entrega la URI<br/>al sistema operativo"]
    SCHEME -->|"tel:"| TEL["El navegador entrega la URI<br/>al sistema operativo"]
    SCHEME -->|"sms:"| SMS["El navegador entrega la URI<br/>al sistema operativo"]
    MAIL --> OS["El sistema busca la aplicacion<br/>registrada para ese esquema"]
    TEL --> OS
    SMS --> OS
    OS --> OK{"Hay una aplicacion<br/>registrada?"}
    OK -->|Si| APP["Se abre el cliente de correo,<br/>el marcador telefonico<br/>o la app de mensajes"]
    OK -->|No| NADA["No ocurre nada visible<br/>o aparece un dialogo de eleccion"]
    style CLICK fill:#1c5d99,color:#ffffff
    style APP fill:#2a9d8f,color:#ffffff
    style NADA fill:#e76f51,color:#ffffff

mailto

Abre el cliente de correo del usuario con un mensaje nuevo ya preparado.

<!-- Forma mínima -->
<a href="mailto:[email protected]">Escríbenos</a>
<!-- Con asunto -->
<a href="mailto:[email protected]?subject=Consulta%20sobre%20inscripciones">
  Consultar por inscripciones
</a>
<!-- Con asunto, copia y cuerpo -->
<a href="mailto:[email protected]?subject=Inscripci%C3%B3n%20al%20torneo&[email protected]&body=Hola%2C%20quiero%20inscribirme%20en%20el%20torneo%20de%20verano.">
  Inscribirme por correo
</a>
<!-- Varios destinatarios -->
<a href="mailto:[email protected],[email protected]">Escribir a la directiva</a>

Los parámetros que puedes usar después del ?:

ParámetroQué hace
subjectRellena el asunto
bodyRellena el cuerpo del mensaje
ccCopia visible
bccCopia oculta

Todos deben ir codificados en porcentaje: los espacios como %20, los acentos en su forma UTF-8 codificada, los saltos de línea como %0A. Si escribes un espacio literal o un & sin codificar, el enlace se corta en ese punto.

Un detalle importante: mailto: depende por completo de que el usuario tenga un cliente de correo configurado. En un computador con Gmail en el navegador y sin cliente instalado, puede que no pase nada. Por eso, en una página de contacto, muestra siempre la dirección en texto además del enlace:

<p>Escríbenos a <a href="mailto:[email protected]">[email protected]</a></p>

Así, si el enlace no funciona, el usuario todavía puede copiar la dirección.

tel

Abre el marcador telefónico. En un teléfono, deja el número listo para llamar; en un escritorio, puede abrir Skype, Teams o una aplicación de VoIP.

<a href="tel:+56912345678">+56 9 1234 5678</a>

La regla de oro: el href debe usar formato internacional E.164 (signo +, código de país, número sin espacios ni guiones ni paréntesis), mientras que el texto visible puede tener el formato legible que quieras. Así el enlace funciona sin importar desde qué país lo abran.

<!-- Correcto: href limpio, texto legible -->
<a href="tel:+56221234567">(2) 2123 4567</a>
<!-- Incorrecto: caracteres de formato dentro del href -->
<a href="tel:(2) 2123-4567">(2) 2123-4567</a>

También se admiten extensiones con , (pausa) o con el parámetro ext:

<a href="tel:+56221234567,,102">Recepción, anexo 102</a>

Otros esquemas útiles

EsquemaEjemploQué hace
mailto:mailto:[email protected]Redacta un correo
tel:tel:+56912345678Inicia una llamada
sms:sms:+56912345678Abre un mensaje de texto
geo:geo:-33.4489,-70.6693Abre la app de mapas en esas coordenadas
ftp:ftp://ftp.ejemplo.cl/pub/Abre un servidor FTP
file:file:///home/usuario/doc.htmlAbre un archivo local
data:data:text/plain,HolaContiene el recurso dentro de la propia URI
javascript:javascript:alert(1)Ejecuta código; no lo uses, es un antipatrón

Sobre javascript: en un href: rompe la navegación, es bloqueado por políticas de seguridad de contenido y confunde a las tecnologías asistivas. Si necesitas ejecutar código, usa <button> y un manejador de eventos.

Codificación porcentual

Las URIs solo admiten un conjunto limitado de caracteres. Todo lo demás debe representarse como % seguido del valor hexadecimal de cada byte en UTF-8.

CarácterCodificadoPor qué hay que codificarlo
espacio%20Separa tokens en HTTP
?%3FInicia la consulta
#%23Inicia el fragmento
&%26Separa parámetros
=%3DSepara clave de valor
+%2BEn una consulta significa espacio
/%2FSepara segmentos de ruta
á%C3%A1Fuera de ASCII, dos bytes en UTF-8
ñ%C3%B1Fuera de ASCII, dos bytes en UTF-8
salto de línea%0ACarácter de control

Puedes generar la codificación con JavaScript. La API real del lenguaje es encodeURIComponent() para trozos individuales y encodeURI() para una URI completa:

const destinatario = "[email protected]";
const asunto = "Inscripción al torneo de verano";
const cuerpo = "Hola,\nQuiero inscribirme en el torneo.\nGracias.";

const href =
  "mailto:" + destinatario +
  "?subject=" + encodeURIComponent(asunto) +
  "&body=" + encodeURIComponent(cuerpo);

console.log(href);
// mailto:[email protected]?subject=Inscripci%C3%B3n%20al%20torneo%20de%20verano
// &body=Hola%2C%0AQuiero%20inscribirme%20en%20el%20torneo.%0AGracias.

La diferencia entre las dos funciones importa:

  • encodeURIComponent("a/b?c") devuelve "a%2Fb%3Fc": codifica todo lo que sea reservado, porque el resultado va a ser el valor de un parámetro.
  • encodeURI("https://ejemplo.cl/a b") devuelve "https://ejemplo.cl/a%20b": respeta :, /, ? y # porque asume que son parte de la estructura de la URI.

Regla: usa encodeURIComponent para cada valor que insertes en una consulta, y encodeURI solo cuando tengas una URI casi completa a la que le falta escapar espacios.

El atributo target

target indica dónde debe abrirse el destino.

ValorComportamiento
_selfEn el mismo contexto de navegación. Es el valor por defecto
_blankEn una pestaña o ventana nueva
_parentEn el contexto padre; relevante si la página está dentro de un <iframe>
_topEn el contexto más alto de la jerarquía, rompiendo todos los <iframe>
Un nombreEn el <iframe> cuyo atributo name coincida
<a href="https://ninjas.cl/" target="_blank" rel="noopener noreferrer">
  Ninjas.cl (se abre en una pestaña nueva)
</a>

Sobre _blank hay que ser honesto: es una de las decisiones más discutidas del desarrollo web.

Argumentos en contra. Le quita al usuario el control sobre su propia navegación. Rompe el botón “Atrás”, porque en la pestaña nueva no hay historial al cual volver. En móviles, donde la gestión de pestañas es incómoda, acumula pestañas que el usuario no pidió. Y para quien usa un lector de pantalla, el cambio de contexto puede ocurrir sin aviso.

Cuándo se justifica. Cuando el usuario está en medio de un proceso que perdería al salir: un formulario a medio llenar, un reproductor de video, un carrito de compras. Ahí abrir los términos y condiciones en pestaña nueva evita destruir su trabajo.

Si lo usas, avísalo:

<a href="/terminos.pdf" target="_blank" rel="noopener">
  Términos y condiciones
  <span aria-hidden="true">↗</span>
  <span class="solo-lectores">(se abre en una pestaña nueva)</span>
</a>

La clase .solo-lectores es el patrón habitual para texto que solo leen las tecnologías asistivas: se saca de la vista sin usar display: none, que lo ocultaría también a los lectores de pantalla.

.solo-lectores {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

El atributo rel

rel describe la relación entre el documento actual y el destino. Los valores más útiles:

ValorPara qué sirve
noopenerImpide que la página abierta acceda a window.opener y pueda manipular la pestaña original
noreferrerImpide que se envíe la cabecera Referer; implica noopener
nofollowIndica a los buscadores que no transfieran reputación a ese enlace
ugcMarca contenido generado por usuarios, como comentarios
sponsoredMarca enlaces pagados o de afiliación
externalSeñala que el destino está fuera del sitio actual
meIndica que el destino es otro perfil de la misma persona; se usa en la verificación de Mastodon

El caso de noopener merece explicación. Sin él, la página que se abre con target="_blank" recibe una referencia a la ventana original en window.opener y puede cambiar su ubicación. Un sitio malicioso podía reemplazar tu pestaña original por una página falsa de inicio de sesión mientras el usuario miraba la pestaña nueva. Esta técnica se llama tabnabbing.

Los navegadores modernos aplican noopener de forma implícita cuando hay target="_blank", así que hoy es una red de seguridad más que una necesidad. Escribirlo igual no cuesta nada y protege a usuarios con navegadores antiguos.

<a href="https://sitio-externo.cl" target="_blank" rel="noopener noreferrer">Sitio externo</a>

El atributo download

Convierte un enlace en una descarga en lugar de una navegación.

<!-- Descarga con el nombre original del archivo -->
<a href="/documentos/reglamento.pdf" download>Descargar el reglamento</a>
<!-- Descarga renombrando el archivo -->
<a href="/documentos/r-2026-v3-final.pdf" download="reglamento-super-club-net.pdf">
  Descargar el reglamento
</a>

Dos restricciones que hay que conocer:

  1. download solo funciona si el recurso está en el mismo origen que la página (mismo esquema, host y puerto). Para recursos de otro origen el navegador lo ignora y navega normalmente. La excepción son las URIs blob: y data:.
  2. El servidor puede sobrescribir el nombre mediante la cabecera Content-Disposition.

Otros atributos del enlace

<a href="/manual.pdf"
   type="application/pdf"
   hreflang="es"
   title="Manual del socio, PDF de 2 MB"
   referrerpolicy="no-referrer">
  Manual del socio
</a>
AtributoFunción
typeSugiere el tipo MIME del destino; es una pista, no una garantía
hreflangIndica el idioma del documento de destino
titleMuestra un tooltip al pasar el mouse; no lo uses como única fuente de información, porque no aparece en móviles ni al navegar con teclado
referrerpolicyControla cuánta información de origen se envía en la cabecera Referer
pingLista de URLs a las que el navegador enviará una petición POST al hacer clic; se usa para analítica y muchos navegadores lo ignoran o lo bloquean

Accesibilidad de los enlaces

Un enlace bien hecho es un enlace que cualquiera puede usar.

El texto debe tener sentido fuera de contexto. Los lectores de pantalla ofrecen una lista de todos los enlaces de la página. Una lista con quince entradas que dicen “clic aquí” es inservible.

<!-- Malo -->
<p>Para ver el reglamento <a href="/reglamento.pdf">haz clic aquí</a>.</p>
<!-- Bueno -->
<p>Revisa el <a href="/reglamento.pdf">reglamento del club en PDF</a>.</p>

Nunca dejes un enlace sin texto accesible. Un enlace que solo contiene un ícono necesita una etiqueta:

<!-- Malo: no hay nada que anunciar -->
<a href="/carrito"><svg><!-- ícono --></svg></a>
<!-- Bueno -->
<a href="/carrito" aria-label="Ver el carrito de compras">
  <svg aria-hidden="true" focusable="false"><!-- ícono --></svg>
</a>

El foco debe verse. Nunca escribas outline: none sin poner otro indicador en su lugar. Quien navega con teclado depende de ese anillo para saber dónde está.

/* Nunca hagas esto solo */
a:focus { outline: none; }

/* Haz esto */
a:focus-visible { outline: 3px solid #ffb703; outline-offset: 2px; }

El área de clic debe ser suficientemente grande. Las guías de accesibilidad recomiendan un objetivo táctil de al menos 24 por 24 píxeles CSS. En navegación y botones, agrega padding en vez de depender del tamaño de la fuente.

Marca los enlaces del sitio actual. Si tu barra de navegación resalta la página actual, indícalo también semánticamente:

<nav aria-label="Principal">
  <ul>
    <li><a href="/">Inicio</a></li>
    <li><a href="/torneos/" aria-current="page">Torneos</a></li>
    <li><a href="/contacto/">Contacto</a></li>
  </ul>
</nav>

Ofrece un enlace para saltar al contenido. Es el primer elemento enfocable de la página y permite a quien usa teclado saltarse la navegación:

<body>
  <a class="saltar" href="#contenido">Saltar al contenido principal</a>
  <nav><!-- muchos enlaces --></nav>
  <main id="contenido" tabindex="-1"><h1>Super Club Net</h1></main>
</body>
.saltar { position: absolute; left: -9999px; top: 0; background: #1c5d99; color: #ffffff; padding: 0.75rem 1rem; z-index: 100; }
.saltar:focus { left: 0; }

Proyecto integrador: la navegación del Super Club Net

Juntemos todo en una página completa que usa cada tipo de enlace visto en el capítulo.

index.html:

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Super Club Net</title>
    <link rel="stylesheet" href="css/estilos.css">
  </head>
  <body>
    <a class="saltar" href="#contenido">Saltar al contenido principal</a>
    <header>
      <h1><a href="/" class="marca">Super Club Net</a></h1>
      <nav aria-label="Principal">
        <ul class="menu">
          <li><a href="/" aria-current="page">Inicio</a></li>
          <li><a href="/torneos/">Torneos</a></li>
          <li><a href="/socios/">Socios</a></li>
          <li><a href="#contacto">Contacto</a></li>
        </ul>
      </nav>
    </header>
    <main id="contenido" tabindex="-1">
      <section id="bienvenida">
        <h2>Bienvenido</h2>
        <p>
          Somos un club deportivo fundado en 1998. Revisa nuestro
          <a href="documentos/reglamento.pdf" download="reglamento-super-club-net.pdf">reglamento en PDF</a>
          o mira el <a href="/torneos/2026-verano.html#inscripcion">proceso de inscripción del torneo</a>.
        </p>
      </section>
      <section id="enlaces-utiles">
        <h2>Enlaces útiles</h2>
        <ul>
          <li>
            <a href="https://developer.mozilla.org/es/docs/Web/HTML" target="_blank" rel="noopener noreferrer">
              Referencia de HTML en MDN <span aria-hidden="true">↗</span>
              <span class="solo-lectores">(se abre en una pestaña nueva)</span>
            </a>
          </li>
          <li>
            <a href="https://ninjas.cl/books/superclubnet/" target="_blank" rel="noopener noreferrer">
              Guía original del Super Club Net <span aria-hidden="true">↗</span>
              <span class="solo-lectores">(se abre en una pestaña nueva)</span>
            </a>
          </li>
        </ul>
      </section>
      <section id="contacto">
        <h2>Contacto</h2>
        <ul class="contacto">
          <li>Correo: <a href="mailto:[email protected]?subject=Consulta%20desde%20el%20sitio">[email protected]</a></li>
          <li>Teléfono: <a href="tel:+56221234567">(2) 2123 4567</a></li>
          <li>Mensajes: <a href="sms:+56912345678">+56 9 1234 5678</a></li>
        </ul>
        <p><a href="#contenido">Volver arriba</a></p>
      </section>
    </main>
    <footer>
      <p>Super Club Net 2026 &middot; <a href="/legal/privacidad.html">Política de privacidad</a></p>
    </footer>
  </body>
</html>

css/estilos.css:

:root {
  --azul: #1c5d99;
  --morado: #6b3fa0;
  --rojo: #d62828;
  --ambar: #ffb703;
  --texto: #1f2933;
}

* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; line-height: 1.7; color: var(--texto); }

/* ---- Enlace de salto ---- */
.saltar {
  position: absolute;
  left: -9999px; top: 0; z-index: 100;
  background: var(--azul); color: #ffffff;
  padding: 0.75rem 1rem;
  text-decoration: none;
}
.saltar:focus { left: 0; }

/* ---- Estados de enlace en orden LVHA ---- */
a:link { color: var(--azul); text-underline-offset: 0.15em; }
a:visited { color: var(--morado); }
a:focus-visible { outline: 3px solid var(--ambar); outline-offset: 3px; border-radius: 3px; }
a:hover { color: var(--rojo); text-decoration-thickness: 2px; }
a:active { color: #9d0208; }

/* ---- Cabecera y navegación ---- */
header { border-bottom: 2px solid #e4e7eb; padding: 1rem 1.5rem; }
.marca { text-decoration: none; color: var(--texto); }

.menu { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0; margin: 0.5rem 0 0; }
.menu a { display: inline-block; padding: 0.6rem 0.9rem; text-decoration: none; border-radius: 4px; }
.menu a:hover { background: #f0f4f8; }
.menu a[aria-current="page"] { background: var(--azul); color: #ffffff; }

/* ---- Contenido ---- */
main { max-width: 48rem; margin: 0 auto; padding: 2rem 1.5rem; }
main:focus { outline: none; }
section { margin-bottom: 3rem; scroll-margin-top: 2rem; }
.contacto { list-style: none; padding: 0; }
.contacto li { padding: 0.35rem 0; }

footer { border-top: 2px solid #e4e7eb; padding: 1.5rem; text-align: center; font-size: 0.9rem; }

/* ---- Utilidades ---- */
.solo-lectores {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- Desplazamiento suave, respetando preferencias ---- */
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

Leer una URL desde JavaScript

Aunque el DOM lo veremos en detalle más adelante, vale la pena mostrar la API que el navegador ofrece para trabajar con URLs, porque es la mejor forma de comprobar que entendiste sus componentes.

Abre la consola del navegador y pega esto:

const url = new URL(
  "https://blog.ejemplo.cl:8443/articulos/2026/enlaces?orden=fecha&pagina=2#comentarios"
);

console.log(url.protocol); // "https:"
console.log(url.host);     // "blog.ejemplo.cl:8443"
console.log(url.hostname); // "blog.ejemplo.cl"
console.log(url.port);     // "8443"
console.log(url.pathname); // "/articulos/2026/enlaces"
console.log(url.search);   // "?orden=fecha&pagina=2"
console.log(url.hash);     // "#comentarios"
console.log(url.origin);   // "https://blog.ejemplo.cl:8443"

url.searchParams.forEach(function (valor, clave) {
  console.log(clave + " = " + valor);
});
// orden = fecha
// pagina = 2

Un detalle a notar: url.protocol incluye los dos puntos ("https:") y url.hash incluye el numeral ("#comentarios"). Es un tropiezo clásico al comparar valores.

El constructor URL también resuelve rutas relativas, lo que te permite verificar tus propias resoluciones sin subir nada a un servidor:

new URL("../otro.html", "https://ejemplo.cl/a/b/c.html").href;
// "https://ejemplo.cl/a/otro.html"

new URL("/raiz.html", "https://ejemplo.cl/a/b/c.html").href;
// "https://ejemplo.cl/raiz.html"

new URL("#seccion", "https://ejemplo.cl/a/b/c.html").href;
// "https://ejemplo.cl/a/b/c.html#seccion"

Errores comunes

ErrorCausaSolución
El enlace no cambia de estilo al pasar el mouse en páginas ya visitadasa:hover se declaró antes de a:visited y pierde por orden de cascadaEscribe las pseudoclases en orden LVHA: :link, :visited, :focus, :hover, :active
El enlace no se ve como enlace ni cambia el cursorSe escribió <a> sin atributo hrefUn <a> sin href no es un enlace; agrégale href o usa <button>
El #seccion no lleva a ninguna parteNo existe ningún elemento con ese id, o hay diferencia de mayúsculas y minúsculasVerifica que el id exista y coincida exactamente; los id distinguen mayúsculas
El destino del # queda tapado por la barra de navegación fijaEl scroll deja el elemento en el borde superior, debajo del encabezado con position: fixedAplica scroll-margin-top con la altura de la barra al elemento destino
La página funciona local pero da 404 al publicarDiferencias de mayúsculas en nombres de archivo entre tu sistema y el servidor LinuxUsa siempre minúsculas y guiones en nombres de archivo y carpetas
/estilos.css no carga cuando el sitio está en una subcarpetaLa barra inicial apunta a la raíz del dominio, no a la raíz del proyectoUsa rutas relativas o define correctamente la etiqueta <base>
El mailto corta el mensaje a la mitadEl body contenía un &, un # o un espacio sin codificarCodifica cada valor con encodeURIComponent() antes de armar el href
El enlace tel: no llama en algunos paísesEl número está en formato local o tiene guiones y paréntesis dentro del hrefUsa formato E.164 en el href (tel:+56912345678) y deja el formato legible en el texto visible
Se abren pestañas nuevas que el usuario no pidióSe aplicó target="_blank" a todos los enlaces por costumbreReserva _blank para casos donde el usuario perdería trabajo, y anúncialo en el texto
Al hacer clic en un enlace externo el sitio original es reemplazado por una página falsaTabnabbing: la página abierta manipuló window.openerAgrega rel="noopener noreferrer" a los enlaces con target="_blank"
download no descarga y navega a la páginaEl recurso está en otro origen distinto al de la páginadownload solo funciona en el mismo origen; sirve el archivo desde tu propio dominio
El lector de pantalla anuncia “enlace” pero no dice a dónde vaEl enlace solo contiene un ícono o una imagen sin altAgrega alt descriptivo a la imagen o aria-label al enlace
Un enlace relativo sube un nivel de másLa URL actual no termina en barra, así que el último segmento se trata como archivoTermina las URLs de directorio con / o usa rutas absolutas de raíz
El navegador no muestra el foco al navegar con TabAlguien escribió outline: none para quitar el borde azulUsa :focus-visible con un outline propio, visible y con buen contraste
Un href="#" con JavaScript salta al inicio de la páginaEl comportamiento por defecto del fragmento vacío es ir al tope del documentoUsa <button type="button"> en vez de un enlace falso

Ejercicios propuestos

  1. Mapa del sitio. Crea una estructura de carpetas con index.html en la raíz y en tres subcarpetas (torneos/, socios/, contacto/). Cada index.html debe tener una navegación que enlace a las otras tres usando solo rutas relativas. Verifica que todos los enlaces funcionen desde cualquier página.

  2. La misma navegación, dos veces. Duplica el ejercicio anterior pero usando rutas absolutas de raíz (/torneos/). Sirve el sitio desde la raíz de un servidor local y luego desde una subcarpeta. Documenta qué se rompe en cada caso y por qué.

  3. Descomposición manual. Toma estas cinco URLs e identifica cada componente en una tabla (esquema, host, puerto, ruta, consulta, fragmento). Luego verifica tus respuestas con new URL() en la consola del navegador.

    • https://www.ejemplo.cl/tienda/producto?id=42&color=rojo#opiniones
    • http://localhost:8080/api/v1/socios
    • ftp://archivos.ejemplo.org:2121/publico/manual.pdf
    • mailto:[email protected]?subject=Ayuda
    • https://ejemplo.cl:443/
  4. Reglamento navegable. Escribe un documento con seis secciones. Al inicio, un índice con enlaces a cada una; al final de cada sección, un enlace de vuelta al índice. Agrega scroll-behavior: smooth, scroll-margin-top y un estilo :target que resalte la sección activa.

  5. Barra fija. Sobre el ejercicio anterior, agrega un encabezado con position: fixed y altura de 4 rem. Comprueba que los títulos quedan tapados al saltar y corrígelo con scroll-margin-top. Mide la altura real con las herramientas de desarrollo.

  6. Generador de mailto. Construye una página con tres campos de texto (destinatario, asunto, mensaje) y un enlace que se actualice cada vez que escribes. Usa encodeURIComponent() en cada valor. Prueba escribiendo texto con acentos, con & y con saltos de línea.

  7. Página de contacto completa. Crea una página que ofrezca correo (mailto: con asunto prellenado), teléfono (tel: en formato E.164), mensaje (sms:) y ubicación (geo: con las coordenadas de tu ciudad). Cada dato debe estar también visible como texto plano.

  8. Auditoría de accesibilidad. Toma cualquier página que hayas hecho en capítulos anteriores y revisa todos sus enlaces contra esta lista: texto con sentido fuera de contexto, foco visible, área de clic de al menos 24 píxeles, aria-current en la página actual, enlace de salto al contenido, aviso en los que abren pestaña nueva. Corrige lo que falte.

  9. Comparación de resolución. En la consola del navegador, ejecuta new URL(relativa, base).href para al menos ocho combinaciones distintas de rutas relativas y URLs base, incluyendo bases con y sin barra final. Anota los resultados y explica cada uno.

  10. Detector de enlaces externos. Escribe un script que recorra todos los <a> del documento, detecte cuáles apuntan a un dominio distinto del actual comparando url.origin, y les agregue automáticamente rel="noopener noreferrer" más un ícono indicador. Es un buen ejercicio para practicar la API URL antes de entrar al DOM.

Lo que viene

Con este capítulo la navegación de tu sitio ya está resuelta: sabes conectar documentos entre sí, saltar dentro de uno solo, apuntar a recursos externos y sacar al usuario del navegador cuando hace falta. También tienes el vocabulario correcto para hablar de direcciones web sin ambigüedades, algo que te va a servir cuando trabajes con formularios, con peticiones desde JavaScript y con enrutamiento del lado del servidor.

En el capítulo 7 llevaremos el enlace un paso más allá: veremos los mapas de imagen, que permiten definir zonas clickeables con distintas formas dentro de una misma imagen; la etiqueta <iframe>, que incrusta un documento completo dentro de otro y explica por fin para qué existen los valores _parent y _top del atributo target; y los divisores de contenido, esos elementos que estructuran visualmente una página sin agregar significado semántico. Si quieres repasar la ruta completa del curso, el índice está en /tecnologias/superclubnet/00-indice/.