Desarrollo móvil Intermedio

Geometría y Ubicación: calcula la distancia real entre tu posición y un punto fijo con Geolocation API y la fórmula de Haversine

Aprende a construir una aplicación web móvil profesional desde cero para medir distancias reales en el mapa. En este tutorial, no solo solicitaremos coordenadas; implementaremos la Fórmula de Haversine en JavaScript para …

Publicado: 15/03/2026 Por: Juan Felipe Orozco Cortés Duración: 45 - 60 minutos Nivel: Intermedio
Contenido del tutorial

Geometría y Ubicación con Haversine

La web no solo sabe dónde estás: también puede estimar, con bastante precisión, qué tan lejos estás de un punto real del mapa. Hoy en Tu Código Cotidiano conectaremos la Geolocation API con matemáticas de verdad.

📍 Geolocation API 📐 Haversine 📱 Mobile UI ✅ JS Vanilla
Diagrama del usuario y el Parque Principal conectados por un arco que representa el cálculo de distancia con Haversine sobre la Tierra.
El reto geométrico: No podemos trazar una línea recta simple (como en el Teorema de Pitágoras) porque la Tierra es esférica. Haversine toma las coordenadas de ambos puntos y calcula la distancia real respetando la curvatura del planeta.

Pedir la ubicación del usuario está bien. Pero el verdadero salto ocurre cuando conviertes esas coordenadas en una respuesta útil. No basta con saber latitud y longitud: necesitas transformarlas en una distancia que una persona pueda interpretar.

En este tutorial vamos a construir una miniapp web móvil que obtiene tu ubicación real, la compara contra un punto fijo y calcula la distancia geodésica usando la fórmula de Haversine. Todo con HTML, CSS y JavaScript del navegador.

📍 Ubicación Real

Obtiene tu latitud, longitud y precisión aprovechando la API nativa de tu dispositivo móvil o escritorio.

📐 Fórmula Exacta

Calcula la distancia geodésica a un punto fijo (en metros o km) respetando la curvatura de la Tierra.

🛡️ Errores de UI

Responde con mensajes de diagnóstico amigables cuando fallan los permisos, la señal o hay timeout.

La hoja de ruta: Estructura del proyecto

Antes de ensuciarnos las manos con el código, es vital entender cómo vamos a organizar nuestras piezas. No vamos a tirar todo en un solo archivo; vamos a separar responsabilidades para que nuestra aplicación sea escalable y fácil de mantener.

Así es como se ve el esqueleto de Geometría y Ubicación:

│   app.js
│   index.html
│
├───css
│   │   estilos-globales.css
│   │   utilidades.css
│   │   variables.css
│   │
│   ├───modules
│   │       action-bar.css
│   │       botones.css
│   │       datos.css
│   │       error-panel.css
│   │       estado.css
│   │       resultados.css
│   │       tarjetas.css
│   │
│   └───pages
│           geometria-ubicacion.css

La esfera y las coordenadas: Entendiendo la teoría

La Geolocation API del navegador es fantástica: te devuelve las coordenadas exactas de tu dispositivo (latitud, longitud) y una estimación de precisión en metros.

Pero el problema es que dos coordenadas crudas por sí solas no le dicen mucho al usuario. Para convertirlas en una distancia útil, necesitas una fórmula que tome en cuenta la curvatura de la Tierra. Si nuestro planeta fuera un plano perfecto, usaríamos el Teorema de Pitágoras ($a^2 + b^2 = c^2$), pero sobre una esfera, trazar una línea recta en un mapa 2D generaría un margen de error gigantesco.

Ahí es donde entra la Fórmula de Haversine:

$$d = 2r \arcsin\left(\sqrt{\sin^2\left(\frac{\Delta\varphi}{2}\right) + \cos(\varphi_1)\cos(\varphi_2)\sin^2\left(\frac{\Delta\lambda}{2}\right)}\right)$$

La idea general es simple: tienes un punto A (tu usuario), un punto B (tu punto de referencia) y trazas un arco sobre una esfera que conecta ambos puntos. No es magia visual; es geometría aplicada directamente a tu interfaz.

Esquema conceptual mostrando el cálculo de la fórmula de Haversine. Un arco conecta el punto del usuario con el Parque Principal sobre la curvatura de la Tierra.
Del plano a la esfera: La fórmula de Haversine nos permite trazar la ruta real más corta entre dos puntos sobre una superficie esférica.

El esqueleto visual: HTML completo

En lugar de ver la interfaz a pedazos, aquí tienes el archivo index.html completo. Esta estructura contiene la cabecera, la tarjeta del punto de referencia, los paneles de estado y error, la cuadrícula de datos del usuario y la tarjeta de resultado final.

<!doctype html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, viewport-fit=cover"
    />
    <title>Geometría y Ubicación</title>
    <meta
      name="description"
      content="Calcula tu distancia a un punto fijo usando Geolocation API y la fórmula del semiverseno."
    />
    <meta name="theme-color" content="#f4f7ff" />

    <link rel="stylesheet" href="./css/variables.css" />
    <link rel="stylesheet" href="./css/utilidades.css" />
    <link rel="stylesheet" href="./css/estilos-globales.css" />

    <link rel="stylesheet" href="./css/modules/botones.css" />
    <link rel="stylesheet" href="./css/modules/tarjetas.css" />
    <link rel="stylesheet" href="./css/modules/estado.css" />
    <link rel="stylesheet" href="./css/modules/error-panel.css" />
    <link rel="stylesheet" href="./css/modules/datos.css" />
    <link rel="stylesheet" href="./css/modules/resultados.css" />
    <link rel="stylesheet" href="./css/modules/action-bar.css" />

    <link rel="stylesheet" href="./css/pages/geometria-ubicacion.css" />
  </head>
  <body>
    <main class="app-container">
      <header class="hero-card">
        <p class="eyebrow">TuCodigoCotidiano · Desarrollo móvil</p>
        <h1>Geometría y Ubicación</h1>
        <p class="hero-copy">
          Calcula tu distancia al <strong>Parque principal</strong> con
          geolocalización real y la fórmula del semiverseno.
        </p>
      </header>

      <section class="card poi-card">
        <div class="section-head">
          <p class="section-kicker">Punto de referencia</p>
          <h2>Parque principal</h2>
        </div>

        <p class="supporting-text">
          Esta coordenada fija será la base de comparación.
        </p>

        <div class="data-grid">
          <article class="data-item">
            <span class="data-label">Latitud</span>
            <strong class="data-value">6.5570</strong>
          </article>

          <article class="data-item">
            <span class="data-label">Longitud</span>
            <strong class="data-value">-75.8265</strong>
          </article>
        </div>
      </section>

      <section class="card">
        <div class="section-head">
          <p class="section-kicker">Estado</p>
          <h2>Estado actual</h2>
        </div>

        <p
          id="status-text"
          class="status-badge status-idle"
          aria-live="polite"
        >
          Listo para calcular
        </p>
      </section>

      <section
        id="error-panel"
        class="card error-panel error-panel--hidden"
        aria-live="polite"
      >
        <div class="section-head">
          <p class="section-kicker section-kicker--error">Diagnóstico</p>
          <h2 id="error-title">No se pudo completar el cálculo</h2>
        </div>

        <p id="error-message" class="error-message">
          Aún no hay errores registrados.
        </p>

        <p id="error-hint" class="error-hint">
          Cuando falle algo, aquí verás qué pasó y cómo seguir.
        </p>

        <div class="error-actions">
          <button id="btn-retry" class="btn-secondary" type="button">
            Reintentar
          </button>
        </div>
      </section>

      <section class="card">
        <div class="section-head">
          <p class="section-kicker">Tu ubicación</p>
          <h2>Posición actual</h2>
        </div>

        <div class="data-grid">
          <article class="data-item">
            <span class="data-label">Latitud</span>
            <strong id="user-lat" class="data-value">--</strong>
          </article>

          <article class="data-item">
            <span class="data-label">Longitud</span>
            <strong id="user-lng" class="data-value">--</strong>
          </article>

          <article class="data-item">
            <span class="data-label">Precisión</span>
            <strong id="user-accuracy" class="data-value">--</strong>
          </article>

          <article class="data-item">
            <span class="data-label">Unidad</span>
            <strong id="distance-unit" class="data-value">--</strong>
          </article>
        </div>
      </section>

      <section id="result-card" class="card result-card">
        <div class="section-head section-head--center">
          <p class="section-kicker">Resultado</p>
          <h2>Distancia al punto de interés</h2>
        </div>

        <p id="distance-result" class="distance-value distance-placeholder">
          Aún no calculada
        </p>

        <p id="distance-caption" class="result-caption">
          Pulsa el botón para obtener el valor final.
        </p>

        <div class="result-meta">
          <article class="meta-chip">
            <span class="meta-label">Valor bruto</span>
            <strong id="distance-raw">--</strong>
          </article>

          <article class="meta-chip">
            <span class="meta-label">Precisión del dispositivo</span>
            <strong id="precision-note">--</strong>
          </article>
        </div>
      </section>
    </main>

    <div class="action-bar-shell">
      <section class="action-bar">
        <button id="btn-calculate-distance" class="btn-primary" type="button">
          Calcular distancia real
        </button>
        <p id="action-bar-note" class="action-bar-note">
          Obtén tu ubicación y compárala con el punto fijo.
        </p>
      </section>
    </div>

    <script src="./app.js" defer></script>
  </body>
</html>
📦 Arquitectura CSS Modular

Si miras el <head>, notarás múltiples enlaces a hojas de estilo en la carpeta /css/modules/. En lugar de tener un archivo monolítico gigante, dividimos los estilos por responsabilidades (botones, tarjetas, panel de error). Esto hace que el proyecto sea infinitamente más fácil de mantener y escalar.

♿ Accesibilidad y Semántica (aria-live)

A lo largo del HTML usamos etiquetas semánticas como <main>, <section> y <article>. Pero lo más importante es el atributo aria-live="polite" en los paneles de estado y error. Esto le indica a los lectores de pantalla que anuncien los cambios de texto (como "Calculando...") sin interrumpir bruscamente al usuario.

📱 UX Móvil: La Barra de Acción Inferior

Observa el contenedor <div class="action-bar-shell"> al final del documento. Hemos sacado el botón de "Calcular" del flujo normal del texto para anclarlo en la parte inferior de la pantalla. Esto garantiza que el llamado a la acción siempre esté al alcance del pulgar del usuario, sin importar cuánto scroll haga hacia abajo.

⏳ Manejo de Estado en el DOM

Muchos elementos tienen identificadores únicos (id="user-lat") y valores iniciales vacíos como -- o Aún no calculada. Estos son placeholders (marcadores de posición). El HTML no hace cálculos; solo prepara el terreno visual para que nuestro JavaScript encuentre estos IDs y reemplace el texto dinámicamente.

Arquitectura por módulos: ¿Por qué separar el CSS?

En lugar de crear un archivo gigante difícil de mantener, vamos a dividir nuestros estilos por responsabilidades. Cada archivo tiene una misión específica: desde definir los colores base hasta pulir el comportamiento de un botón.

A continuación, exploraremos cada archivo del directorio css/. Para cada módulo, verás el código fuente y un desglose técnico de por qué tomamos ciertas decisiones de diseño.

estilos-globales.css

Este archivo define el "lienzo" de nuestra aplicación. Aquí configuramos la tipografía, los degradados de fondo y el contenedor principal que mantiene la interfaz centrada y elegante tanto en móviles como en monitores ultra-anchos.

/* estilos-globales.css */
body {
  margin: 0;
  font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
  background:
    radial-gradient(circle at top, rgba(47, 102, 232, 0.1), transparent 24%),
    linear-gradient(180deg, #f8faff 0%, #f4f7ff 100%);
  color: var(--color-text);
  line-height: 1.6;
  padding: 14px 14px calc(var(--action-bar-height) + 18px);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.app-container {
  width: 100%;
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.eyebrow,
.section-kicker {
  margin: 0 0 8px;
  font-size: 0.76rem;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-primary);
}

.section-kicker--error {
  color: var(--color-error-text);
}

.section-head {
  margin-bottom: 12px;
  scroll-margin-top: 12px;
}

.section-head h2 {
  margin: 0;
  font-size: 1.14rem;
  line-height: 1.2;
  letter-spacing: -0.02em;
}

.section-head--center {
  text-align: center;
}
🛡️ El truco de min-width: 0

En nuestro reseteo universal (*), además del clásico box-sizing: border-box, agregamos min-width: 0;. Esto es un salvavidas moderno: previene que los elementos dentro de contenedores Flexbox o CSS Grid se desborden de su padre cuando su contenido es demasiado ancho.

📝 Heredar la tipografía en formularios

Por defecto, los navegadores le aplican una fuente fea y anticuada a los botones e inputs, ignorando la fuente que le pusiste a tu body. Al usar font: inherit; en esos elementos, los obligamos a usar la tipografía moderna de la aplicación, manteniendo la consistencia visual sin tener que redeclarar el font-family.

✂️ Coordenadas que no rompen el diseño

La regla overflow-wrap: break-word; en nuestros textos es crítica para este proyecto. Las coordenadas de geolocalización suelen tener muchísimos decimales (ej. -75.8265918231). Sin esta regla, ese número largo no saltaría a la siguiente línea, rompiendo la tarjeta y creando un scroll horizontal indeseado en móviles.

🎨 Single Source of Truth (:root)

Centralizar todo en variables nos permite gobernar la aplicación desde un solo lugar. Si necesitas hacer un modo oscuro en el futuro, o si quieres cambiar el azul primario por un verde, solo redefines estas variables y toda la interfaz (fondos, botones, bordes, sombras) se adaptará automáticamente.

utilidades.css

A menudo llamado "el reset", este archivo elimina las inconsistencias entre navegadores. Su objetivo es simple: garantizar que el modelo de caja, el desplazamiento y la tipografía se comporten exactamente como nosotros queremos, sin sorpresas desagradables en móviles o Safari.

/* utilidades.css */
* {
  box-sizing: border-box;
  min-width: 0;
}

html {
  font-size: 16px;
  scroll-behavior: smooth;
}

button,
input,
textarea,
select {
  font: inherit;
}

h1,
h2,
p,
strong,
span {
  overflow-wrap: break-word;
}
🛡️ El salvavidas de min-width: 0

En el selector universal (*), agregamos min-width: 0; junto al clásico border-box. Esto es crítico para Flexbox y Grid: evita que los elementos hijos se desborden de su contenedor padre cuando su contenido interno (como una URL o una coordenada larga) es más ancho que el espacio disponible.

滑 Navegación fluida (Smooth Scroll)

Al definir scroll-behavior: smooth; en la etiqueta html, nos aseguramos de que cualquier salto interno (por ejemplo, cuando el usuario presiona "Reintentar" y lo llevamos arriba) sea un desplazamiento suave y no un salto brusco que desoriente al usuario.

📝 Consistencia tipográfica en formularios

¿Sabías que los navegadores usan una fuente propia para los button e input por defecto? Con font: inherit;, obligamos a estos elementos a usar la fuente Inter que definimos en el body, manteniendo la armonía visual sin esfuerzo.

✂️ Coordenadas que no rompen el Layout

La regla overflow-wrap: break-word; es nuestra defensa final. En este proyecto manejamos geolocalización, y las coordenadas pueden ser cadenas numéricas larguísimas sin espacios. Esta regla obliga al navegador a romper la línea si el número va a salirse de la tarjeta.

variables.css

Este es el "Single Source of Truth" (Fuente Única de Verdad) de nuestro diseño. Aquí no solo guardamos colores; definimos la semántica de la interfaz. Separamos los colores por su función (estados, superficies, textos) y establecemos las medidas maestras que aseguran que la app se sienta consistente en cada píxel.

/* variables.css */
:root {
  --color-bg: #f4f7ff;
  --color-surface: rgba(255, 255, 255, 0.88);
  --color-surface-soft: #f8fbff;
  --color-text: #172033;
  --color-text-muted: #5a6782;

  --color-primary: #2f66e8;
  --color-primary-hover: #2555c8;
  --color-primary-soft: rgba(47, 102, 232, 0.1);

  --color-border: #dce5f4;
  --color-shadow: 0 18px 40px rgba(23, 32, 51, 0.08);

  --color-idle-bg: #f3e8ff;
  --color-idle-text: #7c3aed;

  --color-loading-bg: #fef3c7;
  --color-loading-text: #92400e;

  --color-success-bg: #dcfce7;
  --color-success-text: #166534;

  --color-error-bg: #fee2e2;
  --color-error-text: #991b1b;
  --color-error-border: #fecaca;

  --radius-xl: 24px;
  --radius-lg: 18px;
  --radius-md: 14px;

  --content-max-width: 620px;
  --action-bar-height: 118px;
}
🎨 Semántica de Colores y Estados

En lugar de nombres genéricos como "rojo" o "amarillo", usamos nombres semánticos como --color-error o --color-loading. Esto facilita que el JavaScript asigne clases de estado (idle, loading, success, error) y la interfaz responda automáticamente con la paleta correcta.

🔮 Superficies y Transparencias

La variable --color-surface utiliza rgba con una opacidad del 88%. Esto es clave para el efecto de Glassmorphism que aplicaremos luego: permite que los colores del fondo se filtren sutilmente a través de las tarjetas, dando una sensación de modernidad y profundidad.

📏 Geometría del Layout

Definimos --content-max-width: 620px; para asegurar que la app no se "estire" demasiado en monitores grandes, manteniendo la experiencia similar a la de un dispositivo móvil. Además, --action-bar-height es el valor maestro que el resto de los archivos usará para calcular paddings y evitar solapamientos.

🛠️ Escalabilidad y Modo Oscuro

Tener todo en :root prepara el terreno para futuras actualizaciones. Si quisieras implementar un "Modo Oscuro", solo tendrías que redefinir estas mismas variables dentro de un selector [data-theme='dark'] y no tendrías que tocar ni una sola línea de los archivos de módulos.

modules/action-bar.css

Para maximizar la usabilidad en dispositivos móviles, implementamos una Barra de Acción Flotante. Este componente permanece fijo en la parte inferior de la pantalla, permitiendo que el usuario active el cálculo de distancia en cualquier momento sin tener que desplazarse por toda la página.

/* modules/action-bar.css */
.action-bar-shell {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: linear-gradient(
    180deg,
    rgba(244, 247, 255, 0) 0%,
    rgba(244, 247, 255, 0.92) 18%,
    rgba(244, 247, 255, 1) 100%
  );
  backdrop-filter: blur(10px);
  z-index: 20;
}

.action-bar {
  width: 100%;
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: 10px;
  border: 1px solid var(--color-border);
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.9);
  box-shadow: 0 16px 38px rgba(23, 32, 51, 0.1);
}

.action-bar-note {
  margin: 10px 4px 0;
  color: var(--color-text-muted);
  font-size: 0.9rem;
  text-align: center;
}
⚓ Posicionamiento Fijo (Floating UI)

Usamos position: fixed; con bottom: 0; para que la barra de acción "flote" sobre el contenido. El z-index: 20; garantiza que siempre esté por encima de las tarjetas de datos, manteniendo la acción principal jerárquicamente superior.

📱 Respetando el "Notch" (Safe Area Insets)

Fíjate en el padding: env(safe-area-inset-bottom). Esta es una función de CSS vital para iPhones modernos y dispositivos con gestos de navegación. Evita que el botón quede tapado por la "rayita" de inicio del sistema operativo, sumando ese espacio extra de forma dinámica.

🔮 Glassmorphism y Degradados

El backdrop-filter: blur(10px); crea un efecto de cristal esmerilado. Combinado con el linear-gradient que empieza totalmente transparente (alpha 0), logramos que el contenido de la página se desvanezca suavemente bajo la barra de acción, dando una sensación de profundidad.

⚖️ Centrado Responsivo en Shell

Aunque el .action-bar-shell ocupa todo el ancho de la pantalla para el efecto de desenfoque, la .action-bar interna mantiene el max-width del proyecto. Esto asegura que en pantallas grandes el botón no se estire de forma ridícula, manteniéndose centrado y usable.

modules/botones.css

En una aplicación móvil, los botones deben ser fáciles de tocar y reaccionar instantáneamente al contacto. En este módulo, diseñamos botones con un área de contacto generosa y estados visuales claros para cuando están activos, enfocados o deshabilitados.

<style>
  .eval-acc { margin: 2rem 0; font-family: sans-serif; }
  .eval-panel { border: 1px solid #e5e7eb; border-radius: 0.5rem; margin-bottom: 0.75rem; background: #fff; overflow: hidden; }
  .eval-panel summary { background: #f9fafb; padding: 1rem; font-weight: 600; cursor: pointer; color: #374151; }
  .eval-panel summary::-webkit-details-marker { display: none; }
  .eval-panel[open] summary { border-bottom: 1px solid #e5e7eb; color: #111; }
  .eval-panel .body { padding: 1.25rem; color: #4b5563; line-height: 1.6; font-size: 0.95rem; }
</style>

<div class="eval-acc">
  <details class="eval-panel" open>
    <summary>📐 El secreto de minmax(0, 1fr)</summary>
    <div class="body">
      <p>
        Usamos <code>minmax(0, 1fr)</code> en las columnas del grid. El <code>0</code> es fundamental: le dice al navegador que la columna puede encogerse hasta cero si es necesario, lo que evita que un texto muy largo (como una coordenada con 10 decimales) empuje el ancho de la tarjeta y cree scroll horizontal indeseado.
      </p>
    </div>
  </details>
  <details class="eval-panel">
    <summary>✂️ Control de desbordamiento (Word-break)</summary>
    <div class="body">
      <p>
        La regla <code>word-break: break-word;</code> en <code>.data-value</code> es nuestro seguro de vida. Si el GPS nos devuelve una cadena de números sin espacios que supere el ancho de la columna, el navegador forzará un salto de línea en lugar de romper la interfaz.
      </p>
    </div>
  </details>
  <details class="eval-panel">
    <summary>🎨 Jerarquía Visual Dinámica</summary>
    <div class="body">
      <p>
        Diferenciamos la <code>.data-label</code> (latitud/longitud) del <code>.data-value</code> usando variaciones de peso de fuente (800 para el valor) y colores mudos para la etiqueta. Esto permite que el usuario escanee la información importante en milisegundos.
      </p>
    </div>
  </details>
</div>
🖐️ Feedback Háptico Visual

Usamos transform: scale(0.99); en el estado :active. Esto simula una presión física cuando el usuario toca el botón en su pantalla, dando una respuesta inmediata que mejora la percepción de velocidad de la app.

🎯 Ley de Fitts: Blancos táctiles

Un error común es hacer botones pequeños. Con min-height: 60px;, nos aseguramos de que el botón sea fácil de pulsar incluso para pulgares grandes, cumpliendo con los estándares de accesibilidad móvil de Google y Apple.

♿ Accesibilidad y Enfoque

El selector :focus-visible es crucial. Solo se activa cuando el usuario navega con teclado (o dispositivos de asistencia), dibujando un anillo de seguridad azul. Usar outline-offset separa el borde del botón del anillo, haciéndolo mucho más legible.

🎨 Jerarquía con Sombras

El btn-primary usa una sombra difusa con un toque de color azul (rgba(47, 102, 232, 0.24)). Esto no solo le da profundidad, sino que refuerza la identidad de la marca incluso en los pequeños detalles.

modules/datos.css

La claridad en la visualización de datos es fundamental en herramientas técnicas. En este módulo, implementamos una cuadrícula adaptable (Grid) para organizar las coordenadas y métricas de precisión, asegurando que cada dato tenga su propio espacio y sea legible incluso en pantallas pequeñas.

/* modules/datos.css */
.data-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 14px;
}

.data-item {
  padding: 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, #fbfdff 0%, #f7faff 100%);
}

.data-label {
  display: block;
  margin-bottom: 6px;
  color: var(--color-text-muted);
  font-size: 0.86rem;
}

.data-value {
  font-size: 1.02rem;
  font-weight: 800;
  color: var(--color-text);
  word-break: break-word;
}
🧩 Grid Dinámico con minmax

Usamos grid-template-columns: repeat(2, minmax(0, 1fr));. El uso de minmax(0, ...) es un truco avanzado de CSS Grid para forzar a las celdas a que no crezcan más allá de su contenedor, permitiendo que el texto largo se rompa correctamente en lugar de "empujar" el diseño.

🏷️ Jerarquía de Información

Diferenciamos el .data-label (metadato) del .data-value (información real) mediante el peso de la fuente y el color. Esto ayuda a que el usuario identifique qué está mirando de un solo vistazo, algo vital en una aplicación de campo.

🧱 Micro-tarjetas con Profundidad

Cada ítem de dato tiene su propio linear-gradient. Al aplicar un fondo ligeramente más claro en la parte superior, creamos una sutil elevación visual que hace que los datos parezcan elementos físicos dentro de la tarjeta principal.

🔗 El seguro contra decimales GPS

La propiedad word-break: break-word; en .data-value es nuestro seguro de vida. Las coordenadas GPS pueden llegar a tener 15 o más decimales; sin esta regla, el número se saldría de la caja en dispositivos pequeños como un iPhone SE.

modules/error-panel.css

En el desarrollo de software, las cosas no siempre salen bien (falta de permisos GPS, pérdida de señal o tiempo de espera agotado). Este módulo se encarga de mostrar un panel de diagnóstico que solo aparece cuando algo falla, utilizando colores semánticos de advertencia para comunicar urgencia sin sacrificar la legibilidad.

/* modules/error-panel.css */
.error-panel {
  border: 1px solid var(--color-error-border);
  background: linear-gradient(180deg, #fff8f8 0%, #fff4f4 100%);
  scroll-margin-top: 12px;
}

.error-panel--hidden {
  display: none;
}

.error-message {
  margin: 0;
  font-weight: 800;
  color: var(--color-error-text);
}

.error-hint {
  margin: 10px 0 0;
  color: #7f1d1d;
  font-size: 0.94rem;
}

.error-actions {
  margin-top: 14px;
}

Estás viendo solo el 60% del contenido. Hazte Premium para acceder al tutorial completo.

Comunidad

Comentarios y valoraciones

No hay comentarios aún. ¡Sé el primero en opinar!