Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/danitocsc/transporte-unrc-web-public/llms.txt

Use this file to discover all available pages before exploring further.

PublicStopsMap es el componente de mapa interactivo de la vista pública. Utiliza MapLibre GL JS (no React-Leaflet) para renderizar tiles vectoriales de CartoDB Voyager, las polilíneas de cada ruta piloto, marcadores de paradas con estilos diferenciados y la posición GPS del usuario con una línea punteada hacia la parada más cercana. Al ser un componente 'use client' que accede a window y al Canvas de WebGL, siempre debe importarse con { ssr: false }.
El mapa se inicializa una sola vez en el useEffect de montado. Los marcadores de paradas se vuelven a crear completos cada vez que cambian stops, nearestStopId o selectedStopId, mediante el callback drawStops memoizado con useCallback.

Interfaces exportadas

Estas dos interfaces son exportadas desde PublicStopsMap.tsx y reutilizadas en PublicMapPage.tsx y los datos del servidor.

PublicStop

export interface PublicStop {
  id: string;
  name: string;
  lat: number;
  lng: number;
  route_id: string;
  turno: string;
  dias_raw: string;
}
id
string
Identificador único de la parada. Usado como clave React, para comparaciones de cercanía (nearestStopId) y para el lookup de la parada seleccionada.
name
string
Nombre legible de la parada que se muestra en el panel lateral y en el popup del mapa.
lat
number
Latitud decimal de la parada (WGS 84). Combinada con lng como [lat, lng] en los cálculos Haversine.
lng
number
Longitud decimal de la parada (WGS 84). MapLibre GL espera coordenadas en formato [lng, lat], por lo que el componente invierte el orden al crear los marcadores.
route_id
string
ID de la ruta a la que pertenece esta parada. Usado para colorear el ícono SVG del marcador con el color de la ruta correspondiente.
turno
string
Turno de servicio: 'Matutino', 'Intermedio' o 'Vespertino'. Se muestra en el popup del marcador.
dias_raw
string
Cadena de texto legible con los días de operación de la parada (por ejemplo, "Lunes a Viernes"). Se muestra en el popup y en el panel lateral.

PublicRoute

export interface PublicRoute {
  id: string;
  name: string;
  description?: string;
  color: string;
  points: [number, number][];
}
id
string
Identificador único de la ruta. Ejemplos reales: 'route-la-mesa-unrc-001', 'route-centro-unrc-001'.
name
string
Nombre legible de la ruta para el popup al hacer clic en la polilínea.
description
string (opcional)
Descripción corta de la ruta. Si se proporciona, los primeros 80 caracteres se muestran en el popup de la polilínea.
color
string
Color hexadecimal de la polilínea y de los marcadores de paradas de esta ruta (p.ej. '#e11d48').
points
[number, number][]
Arreglo de coordenadas [lat, lng] que definen la trayectoria de la ruta. El componente las invierte a [lng, lat] al construir el LineString GeoJSON requerido por MapLibre GL.

Props del componente

interface Props {
  stops: PublicStop[];
  routes: PublicRoute[];
  userLocation?: [number, number] | null;
  nearestStopId?: string | null;
  onStopClick?: (stop: PublicStop) => void;
  selectedStopId?: string | null;
}
stops
PublicStop[]
required
Paradas a renderizar. Puede ser la lista completa o un subconjunto ya filtrado por ruta (el filtrado lo hace PublicMapPage antes de pasar esta prop).
routes
PublicRoute[]
required
Rutas cuyas polilíneas se dibujan en el mapa. Se procesan una sola vez en el evento load del mapa.
userLocation
[number, number] | null
Coordenadas GPS del usuario [lat, lng]. Si se proporciona, se crea un marcador azul y, si también hay una parada más cercana, se traza una línea punteada hasta ella y el mapa hace zoom para encuadrar ambos puntos.
nearestStopId
string | null
ID de la parada más cercana. Determina cuál marcador recibe el estilo azul #0ea5e9 con tamaño 36 px.
onStopClick
(stop: PublicStop) => void
Callback invocado cuando el usuario hace clic en el elemento HTML del marcador. Recibe el objeto PublicStop completo. En PublicMapPage está conectado a setSelectedStop.
selectedStopId
string | null
ID de la parada actualmente seleccionada. Determina cuál marcador recibe el estilo granate #7A003C con tamaño 36 px.

Tile provider y estilo del mapa

El mapa usa un estilo MapLibre GL con tiles raster de CartoDB Voyager para una apariencia limpia y legible en el contexto universitario:
const MAP_STYLE: maplibregl.StyleSpecification = {
  version: 8,
  sources: {
    osm: {
      type: 'raster',
      tiles: [
        'https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
        'https://b.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
        'https://c.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
      ],
      tileSize: 256,
      attribution: '© OpenStreetMap contributors © CARTO',
    },
  },
  layers: [{ id: 'osm', type: 'raster', source: 'osm' }],
};
El mapa se inicializa centrado en Tijuana con las coordenadas hardcodeadas [-116.94, 32.47] y zoom 12.

Marcador de la UNRC

Al inicializar el mapa se añade un marcador fijo en las coordenadas de la universidad:
const UNRC_LAT = 32.436451;
const UNRC_LNG = -116.860092;
El marcador muestra un círculo granate con la letra “U” y un popup con el texto "UNRC Unidad Tijuana — Destino del servicio".

Estilos de marcadores de paradas

La función createStopElement construye el elemento HTML del marcador dinámicamente. Los parámetros visuales se calculan en drawStops según el estado de la parada:
EstadoColor de fondo (bg)Borde (border)Tamaño (size)
Parada más cercana#0ea5e9 (azul)#fff36 px
Parada seleccionada#7A003C (granate)#fff36 px
Parada regular#374151 (gris oscuro)#9ca3af28 px
function createStopElement(size: number, bg: string, border: string): HTMLDivElement {
  const el = document.createElement('div');
  const svgSize = Math.round(size * 0.6);
  el.innerHTML = `<div style="background:${bg};border-radius:50%;width:${size}px;height:${size}px;
    border:2.5px solid ${border};box-shadow:0 2px 6px rgba(0,0,0,0.35);cursor:pointer;
    display:flex;align-items:center;justify-content:center;">
    <svg width="${svgSize}" height="${svgSize}" ...><!-- ícono de autobús --></svg>
  </div>`;
  return el;
}
Cada marcador tiene un Popup con el nombre de la parada, turno y días de servicio. El evento click en el elemento HTML llama a onStopClick(stop) y detiene la propagación.

Polilíneas de rutas

Las rutas se dibujan una sola vez en el evento load del mapa como capas de tipo line en MapLibre GL:
map.on('load', () => {
  routes.forEach((route) => {
    if (!route.points?.length) return;
    // Las coordenadas en GeoJSON van en formato [lng, lat]
    const coordinates = route.points.map(([lat, lng]) => [lng, lat] as [number, number]);

    map.addSource(`route-${route.id}`, {
      type: 'geojson',
      data: {
        type: 'Feature',
        properties: {},
        geometry: { type: 'LineString', coordinates },
      },
    });

    map.addLayer({
      id: `route-line-${route.id}`,
      type: 'line',
      source: `route-${route.id}`,
      layout: { 'line-join': 'round', 'line-cap': 'round' },
      paint: {
        'line-color': route.color || '#666',
        'line-width': 5,
        'line-opacity': 0.85,
      },
    });
  });
});
Al hacer clic en una polilínea se abre un popup con el nombre y descripción de la ruta.

Marcador de ubicación del usuario y línea a la parada más cercana

Cuando userLocation cambia, el componente:
  1. Elimina el marcador de usuario anterior (si existe).
  2. Elimina la capa y fuente 'nearest-line' (si existen).
  3. Crea un nuevo marcador con un círculo azul sólido de 18 px con popup "Tu ubicación".
  4. Si existe una parada más cercana, dibuja una línea punteada azul (#0ea5e9, line-dasharray: [6, 4]) entre el usuario y esa parada.
  5. Llama a map.fitBounds para encuadrar ambos puntos con 60 px de padding.
// Línea punteada al montar (o cuando el mapa ya está cargado)
function addNearestLine(map, userLocation, nearest) {
  map.addSource('nearest-line', {
    type: 'geojson',
    data: {
      type: 'Feature',
      properties: {},
      geometry: {
        type: 'LineString',
        coordinates: [
          [userLocation[1], userLocation[0]],
          [nearest.lng, nearest.lat],
        ],
      },
    },
  });
  map.addLayer({
    id: 'nearest-line',
    type: 'line',
    source: 'nearest-line',
    paint: {
      'line-color': '#0ea5e9',
      'line-width': 2,
      'line-dasharray': [6, 4],
      'line-opacity': 0.8,
    },
  });
}
Si el estilo del mapa aún no ha terminado de cargar cuando userLocation cambia, la línea se añade dentro del listener map.on('load', ...) para garantizar que las fuentes y capas estén disponibles.

Importación correcta

Por requerir acceso al DOM y a WebGL, este componente nunca debe importarse directamente en un módulo con ssr: true. La forma canónica usada en PublicMapPage:
import dynamic from 'next/dynamic';

const PublicStopsMap = dynamic(
  () => import('@/components/maps/PublicStopsMap'),
  {
    ssr: false,
    loading: () => <div className="w-full h-full bg-slate-100 animate-pulse" />,
  }
);

Build docs developers (and LLMs) love