Etiquetas

Contenedor de mapas


 se crea ra un componente ContenedorMapas.tsx que tendra toda la logica, a continuacion el codigo:



import { useState } from 'react';
import DraggableMapContainer from './ContenedorMapasAnimado';

// Componente funcional ContenedorMapas
function ContenedorMapas() {
  // Estado para almacenar la lista de mapas. Inicialmente contiene un mapa con ID 1.
  const [maps, setMaps] = useState([{ id: 1 }]);
  // Estado para rastrear el ID del mapa que está actualmente activo o en primer plano.
  const [activeMapId, setActiveMapId] = useState(1);
  // Estado para generar el ID único del próximo mapa que se agregue.
  const [nextId, setNextId] = useState(2);

  // Función para añadir un nuevo mapa a la lista.
  const addMap = () => {
    // Obtiene el próximo ID disponible.
    const newId = nextId;
    // Actualiza el estado de 'maps' añadiendo un nuevo objeto de mapa con el nuevo ID.
    // Se utiliza el spread operator (...) para incluir los mapas existentes y luego añadir el nuevo.
    setMaps([...maps, { id: newId }]);
    // Establece el nuevo mapa como el mapa activo.
    setActiveMapId(newId);
    // Incrementa el 'nextId' para el siguiente mapa que se añada.
    setNextId(newId + 1);
  };

  // Función para eliminar un mapa de la lista basado en su ID.
  const removeMap = (id) => {
    // Crea un nuevo array de mapas que no incluye el mapa con el ID proporcionado.
    const newMaps = maps.filter(map => map.id !== id);
    // Actualiza el estado de 'maps' con el nuevo array filtrado.
    setMaps(newMaps);

    // Si el mapa que se eliminó era el mapa activo,
    if (id === activeMapId && newMaps.length > 0) {
      // y si todavía quedan mapas en la lista, activa el último mapa disponible.
      setActiveMapId(newMaps[newMaps.length - 1].id);
    }
  };

  // Renderiza la estructura del componente.
  return (
    <div style={{
      width: '100vw', // Ancho del 100% del viewport.
      height: '100vh', // Alto del 100% del viewport.
      position: 'relative', // Permite posicionar elementos hijos de forma absoluta o relativa a este contenedor.
      background: '#f0f0f0', // Color de fondo gris claro.
      overflow: 'hidden' // Oculta cualquier contenido que se salga de los límites del contenedor.
    }}>
      {/* Botón para añadir nuevos mapas */}
      <button
        onClick={addMap} // Llama a la función 'addMap' cuando se hace clic.
        style={{
          position: 'fixed', // Se mantiene en una posición fija en la ventana del navegador.
          top: '20px', // A 20 píxeles del borde superior.
          right: '20px', // A 20 píxeles del borde derecho.
          zIndex: 2000, // Asegura que el botón esté por encima de otros elementos (mayor valor de z-index).
          padding: '10px 15px', // Espaciado interno del botón.
          background: '#4CAF50', // Color de fondo verde.
          color: 'white', // Color del texto blanco.
          border: 'none', // Sin borde.
          borderRadius: '4px', // Bordes ligeramente redondeados.
          cursor: 'pointer', // Cambia el cursor al pasar por encima, indicando que es interactivo.
          fontSize: '16px' // Tamaño de la fuente.
        }}
      >
        + Añadir Mapa
      </button>

      {/* Contador de mapas */}
      <div style={{
        position: 'fixed', // Se mantiene en una posición fija en la ventana del navegador.
        top: '20px', // A 20 píxeles del borde superior.
        left: '20px', // A 20 píxeles del borde izquierdo.
        zIndex: 2000, // Asegura que el contador esté por encima de otros elementos.
        background: 'rgba(0,0,0,0.7)', // Fondo negro semi-transparente.
        color: 'white', // Color del texto blanco.
        padding: '10px 15px', // Espaciado interno.
        borderRadius: '4px' // Bordes redondeados.
      }}>
        Mapas abiertos: {maps.length} {/* Muestra la cantidad de mapas en el estado 'maps'. */}
      </div>

      {/* Renderizar todos los mapas */}
      {maps.map((map) => (
        <DraggableMapContainer
          key={map.id} // Propiedad 'key' necesaria para que React pueda identificar de forma única cada elemento en la lista.
          isActive={activeMapId === map.id} // Pasa un booleano para indicar si este mapa es el activo.
          onActivate={() => setActiveMapId(map.id)} // Función que se llama cuando este mapa se activa. Actualiza el 'activeMapId'.
          onClose={() => removeMap(map.id)} // Función que se llama cuando se solicita cerrar este mapa. Llama a 'removeMap' con el ID del mapa.
        />
      ))}
    </div>
  );
}

export default ContenedorMapas;


Siguiente componente : 

import { useState, useRef, useEffect } from 'react';
import MapView from '../componentsAnimacionMaps/MapViewAnimado';

// Componente funcional DraggableMapContainer que permite arrastrar y redimensionar un contenedor de mapa.
const DraggableMapContainer = ({ isActive, onActivate, onClose }) => {
  // Estado para la posición del contenedor del mapa. Se inicializa con coordenadas aleatorias dentro del 20% del ancho y alto de la ventana.
  const [position, setPosition] = useState({
    x: Math.random() * window.innerWidth * 0.2,
    y: Math.random() * window.innerHeight * 0.2
  });
  // Estado para el tamaño del contenedor del mapa. Se inicializa con un 60% del ancho y alto del viewport.
  const [size, setSize] = useState({ width: '60vw', height: '60vh' });
  // Estado para indicar si el contenedor está siendo arrastrado.
  const [isDragging, setIsDragging] = useState(false);
  // Estado para indicar si el contenedor está siendo redimensionado.
  const [isResizing, setIsResizing] = useState(false);
  // Referencia mutable para almacenar el desplazamiento del ratón al iniciar el arrastre.
  const dragOffset = useRef({ x: 0, y: 0 });
  // Referencia mutable para almacenar las dimensiones y la posición inicial al iniciar el redimensionamiento.
  const resizeStart = useRef({ x: 0, y: 0, width: 0, height: 0 });
  // Referencia al elemento div del contenedor del mapa.
  const containerRef = useRef(null);

  // Efecto para resetear los estados de arrastre y redimensionamiento cuando el mapa se desactiva.
  useEffect(() => {
    // Si el mapa ya no está activo y estaba siendo arrastrado o redimensionado,
    if (!isActive && (isDragging || isResizing)) {
      // se detiene el arrastre y el redimensionamiento.
      setIsDragging(false);
      setIsResizing(false);
      // Se restablece el cursor del body al estado por defecto.
      document.body.style.cursor = '';
    }
  }, [isActive, isDragging, isResizing]); // Dependencias del efecto: se ejecuta cuando cambia alguna de estas variables.

  // Función que se ejecuta al hacer clic en el control de redimensionamiento.
  const handleResizeMouseDown = (e) => {
    // Si el mapa no está activo, no se permite el redimensionamiento.
    if (!isActive) return;

    // Detiene la propagación del evento para evitar que se active el arrastre del mapa.
    e.stopPropagation();
    // Establece el estado de redimensionamiento a verdadero.
    setIsResizing(true);
    // Almacena la posición del cursor y las dimensiones actuales del contenedor al inicio del redimensionamiento.
    resizeStart.current = {
      x: e.clientX,
      y: e.clientY,
      width: parseInt(size.width),
      height: parseInt(size.height)
    };
    // Cambia el cursor del body para indicar que se está redimensionando.
    document.body.style.cursor = 'nwse-resize';
  };

  // Función que se ejecuta al hacer clic con el botón secundario (por defecto) en el contenedor del mapa para iniciar el arrastre.
  const handleMouseDown = (e) => {
    // Llama a la función 'onActivate' proporcionada por el componente padre para indicar que este mapa se ha activado.
    onActivate();

    // Solo permite el arrastre con el botón secundario del ratón.
    if (e.button !== 2) return;

    // Previene el comportamiento predeterminado del botón secundario (menú contextual).
    e.preventDefault();
    // Establece el estado de arrastre a verdadero.
    setIsDragging(true);
    // Calcula la diferencia entre la posición del cursor y la posición actual del contenedor para mantener el punto de agarre.
    dragOffset.current = {
      x: e.clientX - position.x,
      y: e.clientY - position.y
    };
    // Cambia el cursor del body para indicar que se está arrastrando.
    document.body.style.cursor = 'grabbing';
  };

  // Función que se ejecuta cuando se mueve el ratón.
  const handleMouseMove = (e) => {
    // Si se está redimensionando y el mapa está activo,
    if (isResizing && isActive) {
      // calcula la diferencia en la posición del cursor desde el inicio del redimensionamiento.
      const deltaX = e.clientX - resizeStart.current.x;
      const deltaY = e.clientY - resizeStart.current.y;

      // Actualiza el estado del tamaño del contenedor, asegurando que no sea menor a ciertos límites.
      setSize({
        width: `${Math.max(300, resizeStart.current.width + deltaX)}px`,
        height: `${Math.max(200, resizeStart.current.height + deltaY)}px`
      });
    }
    // Si se está arrastrando y el mapa está activo,
    else if (isDragging && isActive) {
      // actualiza el estado de la posición del contenedor basándose en la posición actual del cursor y el desplazamiento inicial.
      setPosition({
        x: e.clientX - dragOffset.current.x,
        y: e.clientY - dragOffset.current.y
      });
    }
  };

  // Función que se ejecuta cuando se suelta el botón del ratón.
  const handleMouseUp = () => {
    // Detiene el arrastre y el redimensionamiento.
    setIsDragging(false);
    setIsResizing(false);
    // Restablece el cursor del body a 'grab' si el mapa está activo, o a 'default' si no lo está.
    document.body.style.cursor = isActive ? 'grab' : 'default';
  };

  // Efecto para manejar los eventos globales de mouseup y mousemove.
  useEffect(() => {
    // Función interna para manejar el evento global de mouseup.
    const handleGlobalMouseUp = () => {
      // Si se estaba arrastrando o redimensionando, llama a la función local handleMouseUp para detenerlo.
      if (isDragging || isResizing) {
        handleMouseUp();
      }
    };

    // Añade event listeners para los eventos 'mouseup' y 'mousemove' en la ventana.
    window.addEventListener('mouseup', handleGlobalMouseUp);
    window.addEventListener('mousemove', handleMouseMove);

    // Función de limpieza del efecto: se ejecuta cuando el componente se desmonta o antes de que el efecto se vuelva a ejecutar.
    return () => {
      // Remueve los event listeners para evitar fugas de memoria.
      window.removeEventListener('mouseup', handleGlobalMouseUp);
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, [isDragging, isResizing, isActive]); // Dependencias del efecto: se vuelve a ejecutar si cambia alguno de estos estados.

  // Renderiza el contenedor del mapa.
  return (
    <div
      ref={containerRef} // Asigna la referencia al elemento div.
      className="draggable-map"
      style={{
        position: 'fixed', // Permite la manipulación de la posición con 'top' y 'left'.
        left: `${position.x}px`, // Posición horizontal del contenedor.
        top: `${position.y}px`, // Posición vertical del contenedor.
        width: size.width, // Ancho del contenedor.
        height: size.height, // Alto del contenedor.
        cursor: isActive ? (isDragging ? 'grabbing' : 'grab') : 'default', // Cambia el cursor dependiendo del estado de actividad y arrastre.
        boxShadow: isActive // Aplica una sombra más prominente si el mapa está activo.
          ? '0 0 0 3px #4a90e2, 0 4px 20px rgba(0,0,0,0.3)'
          : '0 4px 10px rgba(0,0,0,0.2)',
        borderRadius: '8px', // Bordes redondeados.
        overflow: 'hidden', // Oculta el contenido que se desborda.
        zIndex: isActive ? 1000 : 100, // Asegura que el mapa activo esté por encima de los demás.
        transition: (isDragging || isResizing) ? 'none' : 'all 0.2s ease', // Aplica una transición suave a los cambios de estilo, excepto durante el arrastre o redimensionamiento.
        opacity: isActive ? 1 : 0.9, // Reduce ligeramente la opacidad si el mapa no está activo.
        minWidth: '300px', // Ancho mínimo del contenedor.
        minHeight: '200px' // Alto mínimo del contenedor.
      }}
      onMouseDown={handleMouseDown} // Asigna la función para iniciar el arrastre al evento mousedown.
      onContextMenu={(e) => e.preventDefault()} // Previene la aparición del menú contextual al hacer clic derecho.
    >
      {/* Componente que renderiza el mapa */}
      <MapView />

      {/* Barra de título del mapa */}
      <div style={{
        position: 'absolute',
        top: 0,
        left: 0,
        right: 0,
        height: '40px',
        background: 'rgba(0,0,0,0.7)',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        padding: '0 10px',
        color: 'white',
        zIndex: 10 // Asegura que la barra de título esté por encima del mapa.
      }}>
        <span>{isActive ? "Mapa activo" : "Mapa"}</span> {/* Muestra un texto diferente si el mapa está activo. */}
        <button
          onClick={onClose} // Llama a la función 'onClose' proporcionada por el padre para eliminar el mapa.
          style={{
            background: '#ff4444',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            padding: '4px 8px',
            cursor: 'pointer'
          }}
        >
          Cerrar
        </button>
      </div>

      {/* Control de redimensionamiento (solo visible si el mapa está activo) */}
      {isActive && (
        <div
          style={{
            position: 'absolute',
            right: 0,
            bottom: 0,
            width: '20px',
            height: '20px',
            background: '#4a90e2',
            cursor: 'nwse-resize',
            zIndex: 10 // Asegura que el control de redimensionamiento esté por encima del mapa.
          }}
          onMouseDown={handleResizeMouseDown} // Asigna la función para iniciar el redimensionamiento al evento mousedown.
        />
      )}
    </div>
  );
};

export default DraggableMapContainer;


siguiente componente 

import React, { memo, lazy, Suspense, Profiler, useState, useEffect, useRef, useMemo, useCallback } from 'react';
import { MapContainer, TileLayer, LayersControl } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';

// 1. Error Boundary optimizado
// ErrorBoundary es un componente que captura errores de renderizado en sus hijos.
// 'memo' se utiliza para optimizar el componente, evitando re-renderizados innecesarios si las props no cambian.
const ErrorBoundary = memo(
  class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> {
    // Define el estado inicial del componente, indicando si ha ocurrido un error.
    state = { hasError: false };

    // Método estático invocado cuando un error ocurre durante el renderizado de un componente hijo.
    static getDerivedStateFromError(error: Error) {
      // Registra el mensaje del error en la consola.
      console.error('MapError:', error.message);
      // Actualiza el estado para indicar que ha ocurrido un error.
      return { hasError: true };
    }

    // Método invocado después de que un error ha sido lanzado por un componente descendiente.
    componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
      // Registra la información de la pila del componente donde ocurrió el error.
      console.error('Component stack:', errorInfo.componentStack);
    }

    // Método de renderizado del componente.
    render() {
      // Si el estado 'hasError' es verdadero, renderiza una interfaz de usuario de fallback.
      if (this.state.hasError) {
        return (
          <div className="error-fallback">
            <h2>Error en el mapa</h2>
            {/* Botón para intentar restablecer el estado de error y re-renderizar los hijos. */}
            <button onClick={() => this.setState({ hasError: false })}>
              Reintentar
            </button>
          </div>
        );
      }
      // Si no hay error, renderiza los componentes hijos que fueron pasados como props.
      return this.props.children;
    }
  }
);

// 2. Precarga de CapaBase
// Variable para controlar si la CapaBase ya ha sido precargada.
let capaBasePreloaded = false;
// Función para iniciar la carga del módulo CapaBase de forma asíncrona.
const preloadCapaBase = () => {
  // Si la CapaBase aún no ha sido precargada,
  if (!capaBasePreloaded) {
    // Inicia la importación del módulo. Esto dispara la carga del código en segundo plano.
    import('./CapaBase');
    // Marca la CapaBase como precargada para evitar importaciones duplicadas.
    capaBasePreloaded = true;
  }
};

// 3. Componentes lazy con precarga
// 'lazy' permite cargar componentes solo cuando son necesarios, mejorando el rendimiento inicial.
// CapaBase se carga de forma lazy, y antes de la carga real, se llama a 'preloadCapaBase' para iniciar la descarga anticipada.
const CapaBase = lazy(() => {
  preloadCapaBase();
  return import('./CapaBase');
});

// CapaFlotante también se carga de forma lazy.
const CapaFlotante = lazy(() => import('./CapaFlotante'));

// 4. Componente principal optimizado
// Componente funcional MapView que renderiza el mapa y sus capas.
const MapView = () => {
  // Estado para controlar si la CapaBase ha sido "cargada" (para la lógica de renderizado condicional).
  const [baseLoaded, setBaseLoaded] = useState(false);
  // Estado para controlar si otras capas (como CapaFlotante) han sido "cargadas".
  const [otherLayersLoaded, setOtherLayersLoaded] = useState(false);
  // Referencia para acceder a la instancia del componente MapContainer de Leaflet.
  const mapRef = useRef<any>(null);
  // Referencia para acceder a la instancia del componente TileLayer de Leaflet.
  const tileLayerRef = useRef<any>(null);
  // Referencia para almacenar el ID del frame de animación para CapaBase.
  const animationFrameIdBase = useRef<number | null>(null);
  // Referencia para almacenar el ID del frame de animación para CapaFlotante.
  const animationFrameIdFlotante = useRef<number | null>(null);

  // Efecto para precargar CapaBase y simular una carga inicial rápida.
  useEffect(() => {
    // Inicia la precarga de CapaBase al montar el componente.
    preloadCapaBase();
    // Simula una carga rápida de la capa base para habilitar la carga de otras capas.
    const timer = setTimeout(() => setBaseLoaded(true), 10);
    // Limpia el timeout si el componente se desmonta antes de que termine.
    return () => clearTimeout(timer);
  }, []); // Se ejecuta solo una vez al montar el componente.

  // Efecto para iniciar la carga de otras capas una vez que la capa base se considera "cargada".
  useEffect(() => {
    // Si la capa base ha sido marcada como cargada,
    if (baseLoaded) {
      // Simula un pequeño retraso antes de marcar otras capas como cargadas.
      const timer = setTimeout(() => setOtherLayersLoaded(true), 100);
      // Limpia el timeout si el componente se desmonta antes de que termine.
      return () => clearTimeout(timer);
    }
  }, [baseLoaded]); // Se ejecuta cuando el estado 'baseLoaded' cambia.

  // Configuraciones del MapContainer memoizadas para evitar re-creaciones innecesarias.
  const mapOptions = useMemo(() => ({
    center: [19.285653, -99.147809] as [number, number], // Centro inicial del mapa (Ciudad de México).
    zoom: 5, // Nivel de zoom inicial.
    preferCanvas: true, // Utiliza Canvas para renderizar los tiles (puede mejorar el rendimiento).
    fadeAnimation: false, // Deshabilita la animación de fundido al cargar nuevos tiles.
    attributionControl: false, // Oculta el control de atribución de Leaflet.
    zoomControl: false, // Oculta los controles de zoom por defecto.
    maxZoom: 18, // Nivel máximo de zoom permitido.
    minZoom: 2, // Nivel mínimo de zoom permitido.
    maxBoundsViscosity: 1.0, // Fuerza el mapa a mantenerse dentro de los límites máximos.
    maxBounds: [
      [-85, -175], // Suroeste de los límites del mapa.
      [85, 175]     // Noreste de los límites del mapa.
    ],
    worldCopyJump: false // Deshabilita el salto de copia del mundo al hacer zoom out.
  }), []);

  // Props del TileLayer memoizadas.
  const tileLayerProps = useMemo(() => ({
    url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', // URL de los tiles de OpenStreetMap.
    attribution: '&copy; OpenStreetMap contributors', // Atribución requerida por OpenStreetMap.
    updateWhenIdle: true, // Actualiza los tiles solo cuando el mapa está quieto.
    updateWhenZooming: false, // No actualiza los tiles durante el zoom (mejora la fluidez).
    keepBuffer: 4, // Número de tiles a mantener en el buffer alrededor de la vista actual.
    maxNativeZoom: 19, // Nivel de zoom nativo máximo de los tiles.
    minZoom: 2, // Nivel mínimo de zoom para mostrar los tiles.
    noWrap: true, // Evita la repetición horizontal de los tiles.
    detectRetina: false, // Deshabilita la detección de pantallas Retina.
    bounds: [
      [-85, -175], // Suroeste de los límites de los tiles.
      [85, 175]     // Noreste de los límites de los tiles.
    ]
  }), []);

  // Estilos del contenedor del mapa memoizados.
  const mapStyles = useMemo(() => ({
    position: 'absolute', // Permite que el mapa ocupe todo el contenedor padre.
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    overflow: 'hidden', // Oculta cualquier contenido que se salga de los límites.
    touchAction: 'none', // Deshabilita las acciones táctiles predeterminadas (mejora el control del mapa).
    zIndex: 0, // Asegura que el mapa esté en la capa base.
    willChange: 'transform', // Indica al navegador que la propiedad 'transform' va a cambiar (optimización).
    backfaceVisibility: 'hidden', // Oculta la parte posterior del elemento durante las transformaciones 3D (optimización).
    backgroundColor: '#f0f0f0' // Color de fondo sólido del mapa.
  }), []);

  // Función para manejar la animación de CapaBase (actualmente vacía).
  const animateBaseLayers = (timestamp: number) => {
    // Aquí puedes agregar la lógica específica para CapaBase
    animationFrameIdBase.current = requestAnimationFrame(animateBaseLayers);
  };

  // Función para manejar la animación de CapaFlotante.
  const animateFlotanteLayers = (timestamp: number) => {
    try {
      // Aquí puedes agregar la lógica específica para CapaFlotante
    } catch (error) {
      console.error('Error en requestAnimationFrame de CapaFlotante:', error);
    }
    animationFrameIdFlotante.current = requestAnimationFrame(animateFlotanteLayers);
  };

  // Efecto para iniciar el loop de animación de CapaBase al montar el componente.
  useEffect(() => {
    animationFrameIdBase.current = requestAnimationFrame(animateBaseLayers);
    // Función de limpieza para cancelar el frame de animación cuando el componente se desmonta.
    return () => {
      if (animationFrameIdBase.current !== null) {
        cancelAnimationFrame(animationFrameIdBase.current);
      }
    };
  }, []); // Se ejecuta solo una vez al montar.

  // Efecto para iniciar el loop de animación de CapaFlotante cuando otras capas se consideran cargadas.
  useEffect(() => {
    if (otherLayersLoaded) {
      animationFrameIdFlotante.current = requestAnimationFrame(animateFlotanteLayers);
    }
    // Función de limpieza para cancelar el frame de animación.
    return () => {
      if (animationFrameIdFlotante.current !== null) {
        cancelAnimationFrame(animationFrameIdFlotante.current);
      }
    };
  }, [otherLayersLoaded]); // Se ejecuta cuando 'otherLayersLoaded' cambia.

  // Función para manejar los cambios en la vista del mapa (zoom, pan).
  const handleViewportChanged = useCallback(() => {
    // Puedes agregar lógica aquí para responder a los cambios en la vista del mapa.
    // Por ejemplo, actualizar el estado o realizar otras acciones.
  }, []); // 'useCallback' memoiza la función para evitar re-creaciones innecesarias.

  return (
    // Envuelve el contenido con el ErrorBoundary para capturar cualquier error en los componentes hijos.
    <ErrorBoundary>
      <div className="map-container">
        {/* Componente principal de react-leaflet que contiene el mapa. */}
        <MapContainer
          {...mapOptions} // Pasa las opciones memoizadas al MapContainer.
          ref={mapRef} // Asigna la referencia al componente MapContainer.
          style={mapStyles} // Aplica los estilos memoizados.
          whenCreated={(map) => {
            mapRef.current = map; // Guarda la instancia del mapa en la referencia.
            map.on('moveend', handleViewportChanged); // Asigna el listener para el evento 'moveend' del mapa.
          }}
        >
          {/* Control para agrupar las capas (base y overlay). */}
          <LayersControl position="topright">
            {/* Capa base del mapa (OpenStreetMap). */}
            <LayersControl.BaseLayer name="OpenStreetMap" checked>
              {/* Componente para renderizar los tiles del mapa. */}
              <TileLayer ref={tileLayerRef} {...tileLayerProps} /> {/* Pasa las props memoizadas al TileLayer. */}
            </LayersControl.BaseLayer>

            {/* Renderiza el componente CapaBase solo cuando 'baseLoaded' es verdadero. */}
            {baseLoaded && (
              // Suspense permite mostrar un fallback mientras el componente lazy se carga.
              <Suspense fallback={null}>
                {/* Profiler mide el tiempo de renderizado del componente hijo. */}
                <Profiler
                  id="CapaBase"
                  // Función llamada después de que CapaBase se renderiza.
                  onRender={(id, phase, actualDuration) => {
                    // Registra en la consola si el tiempo de renderizado excede un umbral.
                    if (actualDuration > 16) {
                      console.log(`CapaBase render time: ${actualDuration.toFixed(2)}ms`);
                    }
                  }}
                >
                  {/* Componente CapaBase cargado de forma lazy. */}
                  <CapaBase />
                </Profiler>
              </Suspense>
            )}

            {/* Renderiza otras capas (CapaFlotante) solo cuando 'otherLayersLoaded' es verdadero. */}
            {otherLayersLoaded && (
              <Suspense fallback={null}>
                {/* Envuelve CapaFlotante con otro ErrorBoundary para manejar errores específicos de esta capa. */}
                <ErrorBoundary>
                  <Profiler
                    id="CapaFlotante"
                    onRender={(id, phase, actualDuration) => {
                      if (actualDuration > 16) {
                        console.log(`CapaFlotante render time: ${actualDuration.toFixed(2)}ms`);
                      }
                    }}
                  >
                    {/* Componente CapaFlotante cargado de forma lazy. */}
                    <CapaFlotante />
                  </Profiler>
                </ErrorBoundary>
              </Suspense>
            )}
          </LayersControl>
        </MapContainer>
      </div>
    </ErrorBoundary>
  );
};

// Estilos globales aplicados dinámicamente al head del documento.
if (typeof document !== 'undefined') {
  const style = document.createElement('style');
  style.textContent = `
    .map-container {
      width: 100vw;
      height: 100vh;
      position: relative;
      background: #f0f0f0;
      overflow: hidden;
      isolation: isolate; /* Crea un nuevo contexto de apilamiento para evitar problemas con elementos externos. */
    }
    .error-fallback {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background: rgba(255,255,255,0.9);
      z-index: 1000; /* Asegura que el fallback esté por encima del mapa. */
    }
  `;
  document.head.append(style);
}

// 'memo' se utiliza nuevamente para optimizar el componente MapView, evitando re-renderizados innecesarios.
export default memo(MapView);


siguiente componente : 

import { memo } from 'react';
import { lazy, Suspense, useState, useEffect, useRef, useCallback } from 'react';
import { LayersControl, MapContainer } from 'react-leaflet';
import { arrayGeoserverBaseLayers } from './geoserverBaselay'; // Importa la lista de configuraciones de capas base.
import OfflineModal from './modal/OfflineModal'; // Importa el modal para indicar que la aplicación está offline.
import ErrorModal from './modal/OnlineModal'; // Importa el modal para mostrar errores de conexión o carga.

// Tiempo de espera en milisegundos para la verificación del estado del servidor.
const SERVER_CHECK_TIMEOUT = 3000;
// Umbral de tiempo en milisegundos para mostrar el modal de carga durante la verificación inicial.
const REQUEST_ANIMATION_FRAME_THRESHOLD = 50;

// Constantes de configuración para los reintentos de carga de capas fallidas.
const RETRY_CONFIG = {
  MAX_IMMEDIATE_RETRIES: 3, // Número máximo de reintentos inmediatos.
  RETRY_DELAY_BASE: 1000, // Tiempo base en milisegundos para el primer reintento.
  LONG_RETRY_DELAY: 3 * 60 * 1000, // Tiempo de espera largo en milisegundos para reintentos posteriores o errores de red (3 minutos).
  NETWORK_ERRORS: [ // Lista de mensajes de error que indican problemas de red.
    'net::ERR_INTERNET_DISCONNECTED',
    'net::ERR_NETWORK_CHANGED',
    'net::ERR_CONNECTION_REFUSED',
    'net::ERR_CONNECTION_TIMED_OUT',
    'net::ERR_CONNECTION_RESET',
    'net::ERR_FAILED',
    'net::ERR_INSUFFICIENT_RESOURCES',
    'net::ERR_HTTP2_PROTOCOL_ERROR' // Añadido un tipo de error HTTP/2.
  ]
};

// Componente funcional CapaBase, responsable de gestionar y renderizar las capas base del mapa.
const CapaBase = () => {
  // Estado para almacenar el estado de conexión de cada servidor de capas (online, error, retrying, loaded).
  const [serverStatus, setServerStatus] = useState({});
  // Estado para indicar si la verificación inicial del servidor está en curso.
  const [loading, setLoading] = useState(true);
  // Estado para rastrear si la aplicación tiene conexión a internet.
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  // Estado para controlar la visibilidad del modal de carga inicial.
  const [showLoadingModal, setShowLoadingModal] = useState(false);
  // Estado para controlar la visibilidad del modal de error de conexión general.
  const [showOnModal, setShowOnModal] = useState(false);
  // Estado para llevar la cuenta de los reintentos para cada capa.
  const [retryCount, setRetryCount] = useState({});
  // Estado para almacenar la última vez que ocurrió un error para cada capa.
  const [lastErrorTime, setLastErrorTime] = useState({});
  // Estado para controlar la visibilidad del modal de desconexión.
  const [showOfflineModal, setShowOfflineModal] = useState(!isOnline);
  // Referencia para el ID del frame de animación utilizado para controlar la visualización del modal de carga inicial.
  const animationFrameId = useRef(null);

  // Efecto para escuchar los eventos de conexión (online y offline) del navegador.
  useEffect(() => {
    // Función que se ejecuta cuando la aplicación vuelve a estar online.
    const handleOnline = () => {
      setIsOnline(true); // Actualiza el estado de conexión.
      setShowOfflineModal(false); // Oculta el modal de desconexión.
    };
    // Función que se ejecuta cuando la aplicación pierde la conexión a internet.
    const handleOffline = () => {
      setIsOnline(false); // Actualiza el estado de conexión.
      setShowOfflineModal(true); // Muestra el modal de desconexión.
      console.error('Error de conexión: net::ERR_INTERNET_DISCONNECTED - No hay conexión a internet');
    };

    // Añade los listeners para los eventos 'online' y 'offline' en la ventana.
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    // Función de limpieza del efecto: remueve los listeners cuando el componente se desmonta.
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []); // El array de dependencias vacío asegura que este efecto se ejecute solo una vez al montar y desmontar.

  // Efecto para verificar el estado de conexión de cada servidor de capas.
  useEffect(() => {
    // Si no hay conexión a internet, no se realiza la verificación del servidor.
    if (!isOnline) return;

    let isMounted = true; // Bandera para evitar actualizaciones de estado en un componente desmontado.
    const abortController = new AbortController(); // Controlador para abortar las peticiones fetch si el componente se desmonta.

    // Función asíncrona para verificar el estado de un servidor.
    const checkServerStatus = async () => {
      const status = {}; // Objeto para almacenar el estado de cada capa.

      // Itera sobre la lista de capas base configuradas.
      for (const layer of arrayGeoserverBaseLayers) {
        try {
          // Construye una URL de prueba para verificar si el servidor WMS está respondiendo.
          const testUrl = `${layer.url}?service=WMS&request=GetCapabilities&version=1.1.1`;
          // Crea una promesa para un timeout. Si la petición no responde en el tiempo definido, se rechaza.
          const timeoutPromise = new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Timeout exceeded')), SERVER_CHECK_TIMEOUT)
          );

          // Realiza la petición fetch para verificar el estado del servidor.
          const fetchPromise = fetch(testUrl, {
            signal: abortController.signal // Asocia el signal del abortController a la petición.
          });

          // Espera la primera promesa que se resuelva o rechace (la petición fetch o el timeout).
          const response = await Promise.race([fetchPromise, timeoutPromise]);

          // Si la respuesta no es exitosa (código de estado HTTP no OK), lanza un error.
          if (!response.ok) {
            throw new Error(`HTTP status ${response.status}`);
          }

          // Si el componente sigue montado, marca el servidor de esta capa como 'online'.
          if (isMounted) status[layer.key] = 'online';
        } catch (error) {
          // Si ocurre un error durante la verificación.
          if (isMounted) {
            status[layer.key] = 'error'; // Marca el servidor de esta capa como 'error'.
            logServerError(layer, error); // Registra el error en la consola.

            // Detecta específicamente errores de conexión a internet.
            if (error.message.includes('Failed to fetch') ||
                error.message.includes('net::ERR_INTERNET_DISCONNECTED')) {

              setShowOnModal(true); // Muestra el modal de error de conexión general.

            }
          }
        }
      }

      // Si el componente sigue montado, actualiza el estado del servidor y marca la carga inicial como completada.
      if (isMounted) {
        setServerStatus(status);
        setLoading(false);
      }
    };

    // Llama a la función para verificar el estado del servidor.
    checkServerStatus();

    // Función de limpieza del efecto: establece la bandera a falso y aborta cualquier petición fetch pendiente.
    return () => {
      isMounted = false;
      abortController.abort();
    };
  }, [isOnline]); // Se ejecuta cuando cambia el estado de conexión a internet.

  // Función para registrar información detallada sobre los errores de carga de capas.
  const logServerError = (layer, error) => {
    console.groupCollapsed(`Error en capa WMS: ${layer.name}`);
    console.error('Tipo:', error.name);
    console.error('Mensaje:', error.message);
    console.error('URL:', layer.url);

    if (error.message.includes('net::ERR_INTERNET_DISCONNECTED')) {
      console.error('Estado:', 'Sin conexión a internet');
    } else if (error.message.includes('Timeout exceeded')) {
      console.error('Estado:', 'Timeout - Servidor no responde');
    } else {
      console.error('Estado:', 'Error en el servidor');
    }

    console.groupEnd();
  };

  // Función que se llama cuando ocurre un error al cargar un tile de una capa.
  const handleTileError = (layer, error) => {
    setServerStatus(prev => ({ ...prev, [layer.key]: 'error' })); // Marca el estado del servidor de la capa como 'error'.
    logServerError(layer, error); // Registra el error.
    handleRetry(layer, error); // Intenta reintentar la carga de la capa.
  };

  // Función para manejar los reintentos de carga de capas fallidas.
  const handleRetry = useCallback((layer, error) => {
    const layerKey = layer.key;
    const currentRetry = retryCount[layerKey] || 0; // Obtiene el número actual de reintentos para la capa.
    const now = Date.now(); // Obtiene la marca de tiempo actual.
    const lastError = lastErrorTime[layerKey] || 0; // Obtiene la marca de tiempo del último error para la capa.

    // Si es un error de red y no ha pasado el tiempo de espera largo, no reintentar inmediatamente.
    if (isNetworkError(error) && (now - lastError < RETRY_CONFIG.LONG_RETRY_DELAY)) {
      console.log(`[Capa ${layer.name}] Error de red detectado. Esperando 3 minutos...`);
      return;
    }

    let retryDelay;
    // Determina el tiempo de espera para el próximo reintento basado en el tipo de error y el número de reintentos.
    if (isNetworkError(error)) {
      retryDelay = RETRY_CONFIG.LONG_RETRY_DELAY;
      console.log(`[Capa ${layer.name}] Error de red grave. Próximo intento en ${retryDelay / 1000 / 60} minutos`);
    } else if (currentRetry >= RETRY_CONFIG.MAX_IMMEDIATE_RETRIES) {
      retryDelay = RETRY_CONFIG.LONG_RETRY_DELAY;
      console.log(`[Capa ${layer.name}] Máximo de reintentos inmediatos alcanzado. Próximo intento en ${retryDelay / 1000 / 60} minutos`);
    } else {
      retryDelay = RETRY_CONFIG.RETRY_DELAY_BASE * (currentRetry + 1);
      console.log(`[Capa ${layer.name}] Reintento ${currentRetry + 1}/${RETRY_CONFIG.MAX_IMMEDIATE_RETRIES} en ${retryDelay / 1000}s`);
    }

    // Configura un timeout para reintentar la carga de la capa después del tiempo de espera determinado.
    setTimeout(() => {
      setServerStatus(prev => ({ ...prev, [layerKey]: 'retrying' })); // Marca el estado del servidor como 'retrying'.
      setRetryCount(prev => ({ ...prev, [layerKey]: currentRetry + 1 })); // Incrementa el contador de reintentos.
      setLastErrorTime(prev => ({ ...prev, [layerKey]: now })); // Actualiza la marca de tiempo del último error.
    }, retryDelay);
  }, [retryCount, lastErrorTime]); // Dependencias del useCallback: se recrea si cambian retryCount o lastErrorTime.

  // Función para verificar si un error es considerado un error de red.
  const isNetworkError = (error) => {
    return RETRY_CONFIG.NETWORK_ERRORS.some(netError =>
      error.message.includes(netError) || error.toString().includes(netError)
    );
  };

  // Carga lazy del componente WMSTileLayer de react-leaflet.
  const LazyWMSTileLayer = lazy(() =>
    import('react-leaflet').then(module => ({ default: module.WMSTileLayer }))
  );

  // Función para controlar la visualización del modal de carga inicial mediante requestAnimationFrame.
  const animateLoadingCheck = (timestamp) => {
    const startTime = performance.now();

    arrayGeoserverBaseLayers.forEach((layer) => {
      // Si el estado de la capa no es 'loaded' y ha pasado el umbral de tiempo, muestra el modal de carga.
      if (serverStatus[layer.key] !== 'loaded' && performance.now() - startTime > REQUEST_ANIMATION_FRAME_THRESHOLD) {
        setShowLoadingModal(true);
      }
    });

    animationFrameId.current = requestAnimationFrame(animateLoadingCheck);
  };

  // Efecto para iniciar y limpiar el loop de animación de la verificación de carga inicial.
  useEffect(() => {
    animationFrameId.current = requestAnimationFrame(animateLoadingCheck);
    return () => {
      if (animationFrameId.current) {
        cancelAnimationFrame(animationFrameId.current);
      }
    };
  }, [serverStatus]); // Se ejecuta cuando cambia el estado del servidor de alguna capa.

  // Si la carga inicial está en curso, no renderiza las capas.
  if (loading) return null;

  return (
    <>
      {/* Renderiza el modal de desconexión si la aplicación está offline. */}
      {showOfflineModal && (
        <OfflineModal onClose={() => setShowOfflineModal(false)} />
      )}
      {/* Renderiza el modal de carga inicial si showLoadingModal es true. */}
      {showLoadingModal && (
        <ErrorModal
          message="Cargando capas... Por favor, espere."
          onClose={() => setShowLoadingModal(false)}
        />
      )}
      {/* Renderiza el modal de error de conexión general si showOnModal es true. */}
      {showOnModal && (
        <ErrorModal
          message="Error de conexión. Verifique su conexión a internet y vuelva a intentarlo."
          onClose={() => setShowOnModal(false)}
        />
      )}

        {/* Mapea la lista de capas base para renderizar cada una como una capa base en el control de capas. */}
        {arrayGeoserverBaseLayers?.map((layer, index) => (
          <LayersControl.BaseLayer
            name={layer?.name} // Nombre de la capa que se mostrará en el control.
            key={`baseLayer-${layer?.key || index}`} // Clave única para el elemento de la lista.
            checked={layer?.checked ?? false} // Indica si la capa está activa por defecto.
          >
            {/* Si el estado del servidor para esta capa es 'error', no renderiza la capa (la oculta). */}
            {serverStatus[layer.key] === 'error' ? (
              <div style={{ display: 'none' }} aria-hidden="true" />
            ) : (
              // Si no hay error, carga el componente WMSTileLayer de forma lazy.
              <Suspense fallback={null}>
                <LazyWMSTileLayer
                  layers={layer?.layers} // Nombre de las capas WMS a solicitar.
                  url={layer?.url} // URL del servicio WMS.
                  transparent={layer?.transparent ?? true} // Indica si las capas deben ser transparentes.
                  format={layer?.format || 'image/png'} // Formato de imagen solicitado.
                  opacity={layer?.opacity ?? 1} // Opacidad de la capa.
                  maxZoom={layer?.maxZoom ?? 19} // Nivel de zoom máximo para la capa.
                  minZoom={layer?.minZoom ?? 0} // Nivel de zoom mínimo para la capa.
                  eventHandlers={{
                    // Manejador de eventos para errores de carga de tiles.
                    error: (e) => handleTileError(layer, e.error),
                    // Manejador de eventos para el inicio de la carga de tiles (solo para logging).
                    loading: () => console.log(`Cargando capa ${layer.name}...`)
                  }}
                  // Función que se llama cuando la capa se añade al mapa.
                  onAdd={() => {
                    setServerStatus(prev => ({ ...prev, [layer.key]: 'loaded' })); // Marca el estado del servidor como 'loaded'.
                    setShowLoadingModal(false); // Oculta el modal de carga inicial.
                  }}
                />
              </Suspense>
            )}
          </LayersControl.BaseLayer>
        ))}
    </>
  );
};

// 'memo' se utiliza para optimizar el componente CapaBase, evitando re-renderizados innecesarios
// si las props (que en este caso no recibe directamente) no cambian. Sin embargo, dado que este
// componente tiene estado interno y se suscribe a eventos globales, su comportamiento de re-renderizado
// dependerá de los cambios de estado.
export default memo(CapaBase);



siguiente componente :

// Definición de constantes para los niveles de zoom máximo y mínimo permitidos en el mapa.
const ZoomMaximo = 18;
const ZoomMinimo = 4;

// Clave de API para el servicio de mapas Thunderforest.
const ThunderforestApiKey = '7c352c8ff1244dd8b732e349e0b0fe8d';

// Función que genera la URL base para las capas de mapa de Thunderforest,
// interpolando el estilo de mapa deseado.
const ThunderforestURL = (style: string) =>
  `https://{s}.tile.thunderforest.com/${style}/{z}/{x}/{y}{r}.png?apikey=${ThunderforestApiKey}`;

// Identificador de la aplicación y código de la aplicación para el servicio de mapas HERE.
const HereAppId = 'eAdkWGYRoc4RfxVo0Z4B';
const HereAppCode = 'TrLJuXVK62IQk0vuXFzaig';

// Función que genera la URL base para las capas de mapa de HERE,
// interpolando el tipo de mapa deseado ('terrain' o 'satellite').
const HereURL = (type: 'terrain' | 'satellite') =>
  `https://2.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/${type}.day/{z}/{x}/{y}/256/png8?app_id=${HereAppId}&app_code=${HereAppCode}&lg=eng`;

// Función utilitaria para crear un objeto de configuración de capa de mapa.
// Recibe el nombre de la capa, una clave única, la URL de los tiles,
// un indicador si la capa está marcada como activa por defecto,
// y los niveles de zoom máximo y mínimo permitidos para esta capa.
const makeLayer = (
  name: string,
  key: string,
  url: string,
  checked = false,
  maxZoom = ZoomMaximo,
  minZoom = ZoomMinimo
) => ({
  name,    // Nombre legible de la capa.
  key,     // Clave única para identificar la capa en la aplicación.
  url,     // URL de los tiles del mapa, con marcadores de posición para {s} (subdominio), {z} (zoom), {x} (columna), {y} (fila) y opcionalmente {r} (para tiles de alta resolución).
  checked, // Booleano que indica si la capa está seleccionada o visible por defecto.
  maxZoom, // Nivel de zoom máximo para esta capa.
  minZoom  // Nivel de zoom mínimo para esta capa.
});

// Array que contiene la configuración de múltiples capas base de mapa,
// listas para ser utilizadas en un control de capas de un mapa (como en Leaflet).
export const arrayGeoserverBaseLayers = [
  // Grupo de capas de Thunderforest
  makeLayer('Transporte Thunderforest (*20)', 'thunderforest_mobile_atlas', ThunderforestURL('mobile-atlas')),
  makeLayer('Topografía Thunderforest (*20)', 'thunderforest_landscape', ThunderforestURL('landscape')),
  makeLayer('Calle Thunderforest (*20)', 'thunderforest_transport', ThunderforestURL('transport')),
  makeLayer('Calle Thunderforest Atlas (*20)', 'thunderforest_atlas', 'https://{s}.tile.thunderforest.com/atlas/{z}/{x}/{y}{r}.png?apikey=6a53e8b25d114a5e9216df5bf9b5e9c8'),

  // Grupo de capas de ESRI
  makeLayer('Topografía ESRI (*15)', 'esri_topo', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', false, 15),
  makeLayer('Satélite ESRI (*18)', 'esri_satellite', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'),

  // Grupo de capas de OpenStreetMap
  makeLayer('Calle OpenStreetMap (*19)', 'osm_standard', 'http://a.tile.openstreetmap.org/{z}/{x}/{y}.png'),
  makeLayer('Calle OpenStreetMap DE (*20)', 'osm_de', 'https://tile.openstreetmap.de/{z}/{x}/{y}.png'),
  makeLayer('Topografía OpenTopoMap (*15)', 'opentopomap', 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', false, 15),
  makeLayer('Topografía Cyclosm (*15)', 'osm_cyclosm', 'https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', false, 15),

  // Grupo de capas de Here Wego
  makeLayer('Topografía Here Wego Terrain (*20)', 'here_terrain', HereURL('terrain')),
  makeLayer('Satélite Here Wego (*20)', 'here_satellite', HereURL('satellite')),

  // Grupo de capas de Google Maps
  makeLayer('Satélite Google Maps (*20)', 'google_satellite', 'http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}'),
  makeLayer('Calles Google Maps (*20)', 'google_maps', 'https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}'),
  makeLayer('Tráfico Google Maps (*18)', 'google_traffic', 'https://mt1.google.com/vt?lyrs=h@159000000,traffic|seconds_into_week:-1&style=3&x={x}&y={y}&z={z}', false, 18),

  // Grupo de capas de Mapbox
  makeLayer('Satélite Mapbox (*20)', 'mapbox_satellite', 'https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.webp?sku=101S0AiAdllT3&access_token=pk.eyJ1Ijoiam9uY2hlbWxhIiwiYSI6IjdXUzRocmsifQ.acEmRifqE4Bh2Xz-IY_4Bw'),

  // Grupo de capas de Carto
  makeLayer('Negro Carto (*20)', 'carto_dark', 'http://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'),
  makeLayer('Blanco Fastly Carto (*20)', 'carto_light', 'https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', true),

  // Grupo de capas de Waze
  makeLayer('Calle Waze World (*20)', 'waze_world', 'https://worldtiles3.waze.com/tiles/{z}/{x}/{y}.png'),
];

suguiente componente :

import React, {
  memo, lazy, Suspense, useState, useEffect, useRef,
  useCallback, Profiler
} from 'react';
import { LayersControl } from 'react-leaflet';
import { arrayGeoserverOverlay } from './geoserverOverlay'; // Importa la lista de configuraciones para las capas overlay.

// Componentes lazy para cargar los tipos de capas de forma diferida.
const LazyWMSTileLayer = lazy(() =>
  import('react-leaflet').then(m => ({ default: m.WMSTileLayer }))
);

const LazyTileLayer = lazy(() =>
  import('react-leaflet').then(m => ({ default: m.TileLayer }))
);

// Configuración para los reintentos de carga de capas fallidas.
const RETRY_CONFIG = {
  MAX_IMMEDIATE_RETRIES: 3, // Número máximo de reintentos inmediatos.
  RETRY_DELAY_BASE: 1000, // Tiempo base en milisegundos para el primer reintento (1 segundo).
  LONG_RETRY_DELAY: 5 * 60 * 1000, // Tiempo de espera largo en milisegundos para reintentos posteriores o errores de red (5 minutos).
  NETWORK_ERRORS: [ // Lista de mensajes de error que indican problemas de red.
    'net::ERR_INTERNET_DISCONNECTED',
    'net::ERR_NETWORK_CHANGED',
    'net::ERR_CONNECTION_REFUSED',
    'net::ERR_CONNECTION_TIMED_OUT',
    'net::ERR_CONNECTION_RESET'
  ]
};

// Callback para el Profiler de React, utilizado para medir el rendimiento de la renderización.
const onRenderCallback = (
  id: string, phase: string, actualDuration: number,
  baseDuration: number, startTime: number,
  commitTime: number, interactions: Set<any>
) => {
  console.log(`Profiler [${id}]:`, { phase, actualDuration, baseDuration });
};

// Componente funcional CapaFlotante, responsable de gestionar y renderizar las capas overlay del mapa.
const CapaFlotante = () => {
  // Estado para rastrear si la aplicación tiene conexión a internet.
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  // Estado para almacenar el estado de carga de cada capa overlay ('loading', 'loaded', 'error', 'retrying').
  const [layerStatus, setLayerStatus] = useState<Record<string, string>>({});
  // Estado para llevar la cuenta del número de reintentos para cada capa.
  const [retryCount, setRetryCount] = useState<Record<string, number>>({});
  // Estado para almacenar la marca de tiempo del último error para cada capa.
  const [lastErrorTime, setLastErrorTime] = useState<Record<string, number>>({});

  // Ref para almacenar un Set de las claves de las capas que se han cargado exitosamente.
  const loadedLayers = useRef<Set<string>>(new Set());
  // Ref para almacenar un objeto con los timers de reintento para cada capa.
  const retryTimers = useRef<Record<string, NodeJS.Timeout>>({});

  // useCallback para verificar si un error dado es un error de red.
  const isNetworkError = useCallback(
    (error: any) =>
      RETRY_CONFIG.NETWORK_ERRORS.some(e =>
        error?.message?.includes(e) || error?.toString().includes(e)
      ),
    [] // El array de dependencias vacío significa que esta función no se recreará en cada renderizado.
  );

  // useCallback para limpiar todos los timers de reintento activos.
  const clearRetryTimers = useCallback(() => {
    Object.values(retryTimers.current).forEach(clearTimeout);
    retryTimers.current = {};
  }, []); // El array de dependencias vacío significa que esta función no se recreará en cada renderizado.

  // Efecto para realizar una precarga de 'react-leaflet' y para limpiar los timers al desmontar el componente.
  useEffect(() => {
    import('react-leaflet'); // Precarga el módulo 'react-leaflet' para mejorar la carga de los componentes lazy.
    return clearRetryTimers; // Retorna la función para limpiar los timers al desmontar el componente.
  }, [clearRetryTimers]); // La dependencia es 'clearRetryTimers', pero como no cambia, este efecto solo se ejecuta al montar y desmontar.

  // Efecto para escuchar los eventos de conexión (online y offline) del navegador.
  useEffect(() => {
    const handleOnline = () => {
      console.log('[Conexión] Red en línea');
      setIsOnline(true); // Actualiza el estado de conexión.
      const now = Date.now();
      // Itera sobre las capas que tuvieron errores y, si ha pasado el tiempo de espera largo, intenta reintentarlas.
      for (const [key, time] of Object.entries(lastErrorTime)) {
        if (now - time >= RETRY_CONFIG.LONG_RETRY_DELAY) {
          console.log(`[Reintento] Capa ${key} después de 5 minutos`);
          handleRetry(key);
        }
      }
    };

    const handleOffline = () => {
      console.error('[Conexión] Sin internet');
      setIsOnline(false); // Actualiza el estado de conexión.
      clearRetryTimers(); // Limpia los timers de reintento al perder la conexión.
    };

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, [lastErrorTime, clearRetryTimers]); // Este efecto se ejecuta cuando cambian 'lastErrorTime' o 'clearRetryTimers'.

  // useCallback para manejar el reintento de carga de una capa específica.
  const handleRetry = useCallback((layerKey: string, layerName = '', error?: any) => {
    const now = Date.now();
    const retry = retryCount[layerKey] || 0;
    const lastError = lastErrorTime[layerKey] || 0;

    // Si hay un error, es un error de red y no ha pasado el tiempo de espera largo, no reintenta inmediatamente.
    if (error && isNetworkError(error) && now - lastError < RETRY_CONFIG.LONG_RETRY_DELAY) {
      console.log(`[${layerName}] Error de red, esperando 5 min`);
      return;
    }

    clearTimeout(retryTimers.current[layerKey]); // Limpia cualquier timer de reintento previo para esta capa.

    // Determina el tiempo de espera para el próximo reintento.
    const retryDelay = error && isNetworkError(error)
      ? RETRY_CONFIG.LONG_RETRY_DELAY
      : retry >= RETRY_CONFIG.MAX_IMMEDIATE_RETRIES
        ? RETRY_CONFIG.LONG_RETRY_DELAY
        : RETRY_CONFIG.RETRY_DELAY_BASE * (retry + 1);

    console.log(`[${layerName}] Reintento en ${retryDelay / 1000}s`);

    // Establece un timer para intentar recargar la capa después del tiempo de espera.
    retryTimers.current[layerKey] = setTimeout(() => {
      loadedLayers.current.delete(layerKey); // Elimina la capa del conjunto de capas cargadas.
      setLayerStatus(prev => ({ ...prev, [layerKey]: 'retrying' })); // Actualiza el estado de la capa a 'retrying'.
      setRetryCount(prev => ({ ...prev, [layerKey]: retry + 1 })); // Incrementa el contador de reintentos.
    }, retryDelay);
  }, [retryCount, lastErrorTime, isNetworkError]); // Este useCallback depende de 'retryCount', 'lastErrorTime' e 'isNetworkError'.

  // useCallback para renderizar una capa WMS.
  const renderWMSLayer = useCallback((layer: any) => {
    const key = layer?.key || layer?.name;
    const name = layer?.name || 'Sin nombre';

    const props = {
      layers: layer?.layers,
      url: layer?.url,
      transparent: layer?.transparent ?? true,
      format: layer?.format || 'image/png',
      opacity: layer?.opacity ?? 0.7,
      maxZoom: layer?.maxZoom ?? 18,
      minZoom: layer?.minZoom ?? 0,
      zIndex: layer?.zIndex || 1,
      className: `wms-layer ${key}`
    };

    return (
      <LazyWMSTileLayer
        {...props}
        eventHandlers={{
          load: () => {
            console.log(`[${name}] Carga exitosa`);
            setLayerStatus(prev => ({ ...prev, [key]: 'loaded' })); // Actualiza el estado de la capa a 'loaded'.
            setRetryCount(prev => ({ ...prev, [key]: 0 })); // Resetea el contador de reintentos.
            setLastErrorTime(prev => ({ ...prev, [key]: 0 })); // Resetea la marca de tiempo del último error.
          },
          error: (e: any) => {
            console.error(`[${name}] Error:`, e.error);
            loadedLayers.current.delete(key); // Elimina la capa del conjunto de capas cargadas.
            setLayerStatus(prev => ({ ...prev, [key]: 'error' })); // Actualiza el estado de la capa a 'error'.
            setLastErrorTime(prev => ({ ...prev, [key]: Date.now() })); // Actualiza la marca de tiempo del último error.
            handleRetry(key, name, e.error); // Intenta reintentar la carga de la capa.
          }
        }}
      />
    );
  }, [handleRetry]); // Este useCallback depende de 'handleRetry'.

  // useCallback para renderizar una capa WMTS (convertida a TileLayer estándar).
  const renderWMTSTileLayer = useCallback((layer: any) => {
    const key = layer?.key || layer?.name;
    const name = layer?.name || 'Sin nombre';

    // Convertir la URL de WMTS a una URL de teselas estándar reemplazando los marcadores.
    const tileUrl = layer?.url
      .replace('{z}', '{z}')
      .replace('{x}', '{x}')
      .replace('{y}', '{y}');

    return (
      <LazyTileLayer
        url={tileUrl}
        opacity={layer?.opacity ?? 0.7}
        maxZoom={layer?.maxZoom ?? 18}
        minZoom={layer?.minZoom ?? 0}
        zIndex={layer?.zIndex || 1}
        className={`wmts-layer ${key}`}
        eventHandlers={{
          load: () => {
            console.log(`[${name}] Carga exitosa`);
            setLayerStatus(prev => ({ ...prev, [key]: 'loaded' })); // Actualiza el estado de la capa a 'loaded'.
            setRetryCount(prev => ({ ...prev, [key]: 0 })); // Resetea el contador de reintentos.
            setLastErrorTime(prev => ({ ...prev, [key]: 0 })); // Resetea la marca de tiempo del último error.
          },
          error: (e: any) => {
            console.error(`[${name}] Error:`, e.error);
            loadedLayers.current.delete(key); // Elimina la capa del conjunto de capas cargadas.
            setLayerStatus(prev => ({ ...prev, [key]: 'error' })); // Actualiza el estado de la capa a 'error'.
            setLastErrorTime(prev => ({ ...prev, [key]: Date.now() })); // Actualiza la marca de tiempo del último error.
            handleRetry(key, name, e.error); // Intenta reintentar la carga de la capa.
          }
        }}
      />
    );
  }, [handleRetry]); // Este useCallback depende de 'handleRetry'.

  // useCallback para renderizar una capa, decidiendo qué tipo de capa renderizar.
  const renderLayer = useCallback((layer: any) => {
    const key = layer?.key || layer?.name;
    const status = layerStatus[key];

    // Si el estado de la capa es 'error', no la renderiza.
    if (status === 'error') return null;

    // Si la capa aún no se ha marcado como cargada, lo hace y loguea la solicitud.
    if (!loadedLayers.current.has(key)) {
      loadedLayers.current.add(key);
      console.log(`[${layer?.name}] Solicitando datos`);
    }

    return (
      <Suspense fallback={null}>
        {/* Renderiza el componente de capa adecuado según el tipo de capa. */}
        {layer.type === 'wms' ? renderWMSLayer(layer) : renderWMTSTileLayer(layer)}
      </Suspense>
    );
  }, [layerStatus, renderWMSLayer, renderWMTSTileLayer]); // Este useCallback depende de 'layerStatus', 'renderWMSLayer' y 'renderWMTSTileLayer'.

  return (
    <>
      {/* Mapea la lista de capas overlay para renderizar cada una dentro de un control de capas. */}
      {arrayGeoserverOverlay.map((layer, index) => {
        const key = layer?.key || `layer-${index}`;
        const name = layer?.name || `Capa ${index}`;
        const status = layerStatus[key];
        // Decide si mostrar la capa: si no está en error y está online, o si está en proceso de reintento.
        const show = (status !== 'error' && isOnline) || status === 'retrying';

        return (
          <Profiler key={`profiler-${key}`} id={`Layer-${key}`} onRender={onRenderCallback}>
            <LayersControl.Overlay
              name={name}
              key={`overlay-${key}`}
              checked={layer?.checked ?? false}
            >
              {/* Envuelve la renderización de la capa con un div condicional para mostrarla u ocultarla. */}
              <div style={{ display: show ? 'block' : 'none' }}>
                {renderLayer(layer)}
              </div>
            </LayersControl.Overlay>
          </Profiler>
        );
      })}
    </>
  );
};

export default memo(CapaFlotante); // 'memo' se utiliza para optimizar el componente funcional CapaFlotante, evitando re-renderizados innecesarios si las props no cambian (aunque este componente no recibe props directamente).


siguiente componente : 

// URL base para el servicio de mapas WMS de NASA GIBS.
const ResourceNasa = 'https://gibs.earthdata.nasa.gov/wms/epsg4326/best/wms.cgi';
// URL base para la obtención de leyendas de capas de NASA GIBS.
const ResourceLegends = 'https://gitc.earthdata.nasa.gov/legends';
// Nivel de zoom máximo permitido para las capas.
const ZoomMaximo = 18;
// Nivel de zoom mínimo permitido para las capas.
const ZoomMinimo = 1;
// Opacidad por defecto para las capas (30%).
const Opacity = 0.3;
// Indica si la transparencia está habilitada por defecto para las capas.
const Transparent = true;
// Formato de imagen por defecto para las capas.
const Format = 'image/png';
// Indica si la capa está marcada como visible por defecto.
const Checked = false;
// Tipo de capa, en este caso WMS (Web Map Service).
const type = 'wms';

// Función para construir un objeto de configuración de capa overlay.
// Recibe el nombre de la capa, una clave única, el nombre de las capas WMS a solicitar,
// la ruta al archivo de leyenda (si existe), la URL del servicio (por defecto NASA),
// y la opacidad (por defecto el valor definido).
const buildLayer = (name: string, key: string, layers: string, legendPath: string, url = ResourceNasa, opacity = Opacity) => ({
  name,          // Nombre legible de la capa.
  key,           // Clave única para identificar la capa.
  layers,        // Nombre de la(s) capa(s) dentro del servicio WMS.
  url,           // URL del servicio WMS para esta capa.
  transparent: Transparent, // Indica si la capa debe ser transparente.
  opacity,       // Nivel de opacidad de la capa.
  type: type,     // Tipo de capa (WMS).
  format: Format,   // Formato de imagen de la capa.
  checked: Checked, // Indica si la capa está visible por defecto.
  maxZoom: ZoomMaximo, // Nivel de zoom máximo para esta capa.
  minZoom: ZoomMinimo, // Nivel de zoom mínimo para esta capa.
  legends: legendPath ? `${ResourceLegends}/${legendPath}` : '', // URL completa a la leyenda de la capa, si se proporciona la ruta.
});

// Array que contiene la configuración de múltiples capas overlay para ser mostradas en el mapa.
export const arrayGeoserverOverlay = [
  // Capas de temperatura VIIRS (NOAA20 y SNPP) para la noche.
  buildLayer('TEMPERATURA VIIRS NOAA20', 'TEMPERATURA NOCHE', 'VIIRS_NOAA20_Brightness_Temp_BandI5_Night', 'VIIRS_Brightness_Temp_BandI5_H.png'),
  buildLayer('TEMPERATURA VIIRS SNPP', 'TEMPERATURA NOCHE SNPP', 'VIIRS_SNPP_Brightness_Temp_BandI5_Night', 'VIIRS_Brightness_Temp_BandI5_H.png'),
  // Capas de temperatura de la superficie terrestre MODIS (día y noche).
  buildLayer('TEMPERATURA DE LA SUPERFICIE DEL TERRESTRE NOCHE', 'TEMPERATURA DE LA SUPERFICIE DEL TERRESTRE NOCHE', 'MODIS_Terra_L3_Land_Surface_Temp_Monthly_Night', 'MODIS_Land_Surface_Temp_H.png'),
  buildLayer('TEMPERATURA DE LA SUPERFICIE DEL TERRESTRE DIA', 'TEMPERATURA DE LA SUPERFICIE DEL TERRESTRE DIA', 'MODIS_Terra_L3_Land_Surface_Temp_Monthly_Day', 'MODIS_Land_Surface_Temp_H.png'),
  // Capa de cirros VIIRS SNPP.
  buildLayer('CIRRUS SNPP', 'CIRRUS SNPP', 'VIIRS_SNPP_Apparent_Reflectance_VNP02MOD_M09', 'VIIRS_Apparent_Reflectance_H.png'),
  // Capas de confianza de cielo limpio VIIRS (NOAA20 y SNPP) para la noche.
  buildLayer('CIELO LIMPIO VIIRS SNPP', 'CIELO LIMPIO VIIRS SNPP', 'VIIRS_SNPP_Clear_Sky_Confidence_Night', 'VIIRS_Clear_Sky_Confidence_H.png'),
  buildLayer('CIELO LIMPIO VIIRS NOAA20', 'CIELO LIMPIO VIIRS NOAA20', 'VIIRS_NOAA20_Clear_Sky_Confidence_Night', 'VIIRS_Clear_Sky_Confidence_H.png'),
  // Capas de radio efectivo de nubes VIIRS (NOAA20 y SNPP).
  buildLayer('NUBE RADIO EFECTIVO VIIRS SNPP', 'NUBE RADIO EFECTIVO VIIRS SNPP', 'VIIRS_SNPP_Cloud_Effective_Radius', 'MODIS_VIIRS_Cloud_Effective_Radius_H.png'),
  buildLayer('NUBE RADIO EFECTIVO VIIRS NOAA20', 'NUBE RADIO EFECTIVO VIIRS NOAA20', 'VIIRS_NOAA20_Cloud_Effective_Radius', 'MODIS_VIIRS_Cloud_Effective_Radius_H.png'),
  // Capas de espesor óptico de nubes VIIRS (NOAA20 y SNPP).
  buildLayer('ESPESOR ÓPTICO DE LA NUBE VIIRS SNPP', 'ESPESOR ÓPTICO DE LA NUBE VIIRS SNPP', 'VIIRS_SNPP_Cloud_Optical_Thickness', 'MODIS_VIIRS_Cloud_Optical_Thickness_H.png'),
  buildLayer('ESPESOR ÓPTICO DE LA NUBE VIIRS NOAA20', 'ESPESOR ÓPTICO DE LA NUBE VIIRS NOAA20', 'VIIRS_NOAA20_Cloud_Optical_Thickness', 'MODIS_VIIRS_Cloud_Optical_Thickness_H.png'),
  // Capa de deposición de polvo mensual MERRA2.
  buildLayer('POLVO MENSUAL', 'POLVO MENSUAL', 'MERRA2_Total_Dust_Deposition_Dry_Wet_Monthly', 'MERRA2_Total_Dust_Deposition_Dry_Wet_Monthly_H.png'),
  // Capa de evaporación terrestre mensual MERRA2.
  buildLayer('EVAPORACION', 'EVAPORACION', 'MERRA2_Evaporation_Land_Monthly', 'MERRA2_Evaporation_Land_Monthly_H.png'),
  // Capas infrarrojas limpias de satélites geoestacionarios (Este y Oeste).
  buildLayer('GEOSTACIONARIA ESTE LIMPIO INFRARROJO', 'GEOSTACIONARIA ESTE LIMPIO INFRARROJO', 'GOES-East_ABI_Band13_Clean_Infrared', 'Clean_Longwave_Infrared_Window_Band_H.png'),
  // Capa de color de satélite geoestacionario Este.
  buildLayer('GEOSTACIONARIA ESTE COLOR', 'GEOSTACIONARIA ESTE COLOR', 'GOES-East_ABI_GeoColor', ''),
  // Capas infrarrojas limpias de satélites geoestacionarios (Este y Oeste).
  buildLayer('GEOSTACIONARIA OESTE LIMPIO INFRARROJO', 'GEOSTACIONARIA OESTE LIMPIO INFRARROJO', 'GOES-West_ABI_Band13_Clean_Infrared', 'Clean_Longwave_Infrared_Window_Band_H.png'),
  // Capa de color de satélite geoestacionario Oeste.
  buildLayer('GEOSTACIONARIA OESTE COLOR', 'GEOSTACIONARIA OESTE COLOR', 'GOES-West_ABI_GeoColor', ''),
  // Capa de superficie impermeable global Landsat.
  buildLayer('SUPERFICIE IMPERMEABLE', 'SUPERFICIE IMPERMEABLE', 'Landsat_Global_Man-made_Impervious_Surface', 'Landsat_Global_Man-made_Impervious_Surface_H.png'),
  // Capa de tasa de precipitación IMERG.
  buildLayer('PRECIPITACIÓN', 'PRECIPITACIÓN', 'IMERG_Precipitation_Rate', 'GPM_Precipitation_Rate_H.png'),
  // Capas de humedad relativa superficial mensual AIRS (día y noche).
  buildLayer('HUMEDAD RELATIVA DIA', 'HUMEDAD RELATIVA DIA', 'AIRS_L3_Surface_Relative_Humidity_Monthly_Day', 'AIRS_Surface_Relative_Humidity_Monthly_Day_H.png'),
  buildLayer('HUMEDAD RELATIVA NOCHE', 'HUMEDAD RELATIVA NOCHE', 'AIRS_L3_Surface_Relative_Humidity_Monthly_Night', 'AIRS_Surface_Relative_Humidity_Monthly_Night_H.png'),
  // Capa de velocidad del viento superficial mensual MERRA2.
  buildLayer('VIENTO', 'VIENTO', 'MERRA2_Surface_Wind_Speed_Monthly', 'MERRA2_Surface_Wind_Speed_Monthly_H.png'),

  // Capas de TELCEL (con URL específica y opacidad 1).
  buildLayer('telcel 5g', 'telcel 5g', '', 'MERRA2_Surface_Wind_Speed_Monthly_H.png', 'https://lbsva.telcel.com/tiles/gwc/service/wmts?layer=telcel:cdmx_no_garantizada&style=&tilematrixset=EPSG:900913&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&TileMatrix=EPSG:900913:{z}&TileCol={x}&TileRow={y}', 1),
  buildLayer('telcel 5g garantizado', 'telcel 5g garantizado', '', 'MERRA2_Surface_Wind_Speed_Monthly_H.png', 'https://lbsva.telcel.com/tiles/gwc/service/wmts?layer=telcel:cdmx_garantizada&style=&tilematrixset=EPSG:900913&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&TileMatrix=EPSG:900913:{z}&TileCol={x}&TileRow={y}', 1),
  buildLayer('telcel 4g', 'telcel 4g', '', 'MERRA2_Surface_Wind_Speed_Monthly_H.png', 'https://lbsva.telcel.com/tiles/gwc/service/wmts?layer=telcel:lte_no_garantizada&style=&tilematrixset=EPSG:900913&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&TileMatrix=EPSG:900913:{z}&TileCol={x}&TileRow={y}', 1),
  buildLayer('telcel 4g garantizado', 'telcel 4g garantizado', '', 'MERRA2_Surface_Wind_Speed_Monthly_H.png', 'https://lbsva.telcel.com/tiles/gwc/service/wmts?layer=telcel:lte_garantizada&style=&tilematrixset=EPSG:900913&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&TileMatrix=EPSG:900913:{z}&TileCol={x}&TileRow={y}', 1),
];

codigo de modales --------------


/* OfflineModal.css */

/* Estilos para la capa de superposición oscura que cubre toda la ventana */
.modal-overlay {
  position: fixed; /* Fija el elemento a la ventana del navegador */
  top: 0;
  left: 0;
  width: 100%; /* Ocupa todo el ancho de la ventana */
  height: 100%; /* Ocupa toda la altura de la ventana */
  background: rgba(0, 0, 0, 0.5); /* Fondo negro semitransparente */
  display: flex; /* Utiliza Flexbox para centrar el contenido */
  justify-content: center; /* Centra horizontalmente */
  align-items: center; /* Centra verticalmente */
  z-index: 1000; /* Asegura que el modal esté por encima de otros elementos */
}

/* Estilos para el contenedor del contenido del modal */
.modal-content {
  background: white; /* Fondo blanco del modal */
  padding: 20px; /* Espacio interno alrededor del contenido */
  border-radius: 8px; /* Bordes redondeados */
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* Sombra suave para elevar el modal */
}

/* Estilos para el encabezado h2 dentro del contenido del modal */
.modal-content h2 {
  margin-top: 0; /* Elimina el margen superior por defecto del h2 */
}

/* Estilos para el botón dentro del contenido del modal */
.modal-content button {
  margin-top: 20px; /* Espacio superior entre el botón y otros elementos */
  padding: 10px 20px; /* Espacio interno del botón (arriba/abajo, izquierda/derecha) */
  background: #007bff; /* Color de fondo azul */
  color: white; /* Color del texto blanco */
  border: none; /* Elimina el borde por defecto del botón */
  border-radius: 4px; /* Bordes ligeramente redondeados */
  cursor: pointer; /* Cambia el cursor a una mano al pasar por encima */
}

/* Estilos para el botón cuando el cursor está sobre él */
.modal-content button:hover {
  background: #005bb5; /* Color de fondo azul más oscuro al pasar el ratón */
}


codigo de modal

// OfflineModal.js
import React from 'react';
import './OfflineModal.css'; // Asegúrate de crear este archivo CSS para estilos

// Componente funcional OfflineModal que se encarga de mostrar un mensaje cuando no hay conexión a internet.
// Recibe una prop 'onClose' que es una función para cerrar el modal.
const OfflineModal = ({ onClose }) => {
  return (
    // Contenedor principal que actúa como la capa de superposición oscura.
    <div className="modal-overlay">
      {/* Contenedor para el contenido del modal (fondo blanco, mensaje, botón). */}
      <div className="modal-content">
        {/* Título del modal */}
        <h2>Algunas capas base estan sin Conexión a Internet</h2>
        {/* Mensaje informativo sobre la falta de conexión. */}
        <p>Sin conexión a internet. Algunas funciones como capas pueden no estar disponibles.</p>
        {/* Botón para cerrar el modal. Al hacer clic, se ejecuta la función 'onClose' pasada como prop. */}
        <button onClick={onClose}>Cerrar</button>
      </div>
    </div>
  );
};

// Exporta el componente OfflineModal para que pueda ser utilizado en otras partes de la aplicación.
export default OfflineModal;


siguiente modal :

// OfflineModal.js
import React from 'react';
import './OfflineModal.css'; // Importa los estilos definidos en el archivo OfflineModal.css.

// Componente funcional OfflineModal.
// Este componente recibe una prop llamada 'onClose', que es una función
// que se ejecutará cuando el usuario quiera cerrar el modal.
const OfflineModal = ({ onClose }) => {
  return (
    // Contenedor principal con la clase 'modal-overlay'.
    // Esta clase probablemente define el fondo oscuro y la posición fija del modal.
    <div className="modal-overlay">
      {/* Contenedor para el contenido real del modal (fondo blanco, texto, botón).
          La clase 'modal-content' definirá su estilo visual. */}
      <div className="modal-content">
        {/* Encabezado del modal, indicando el estado de la conexión a Internet. */}
        <h2>Conexión a Internet</h2>
        {/* Párrafo con un mensaje indicando que el usuario tiene conexión a Internet.
            Aunque el nombre del componente es 'OfflineModal', este mensaje sugiere
            que se está mostrando cuando hay conexión. Podría haber una confusión en el nombre. */}
        <p>Estás trabajando con conexión a Internet.</p>
        {/* Botón para cerrar el modal.
            Al hacer clic en este botón, se llama a la función 'onClose' que se recibió como prop.
            Esto permite al componente padre controlar el cierre del modal. */}
        <button onClick={onClose}>Cerrar</button>
      </div>
    </div>
  );
};

// Exporta el componente OfflineModal para que pueda ser utilizado en otras partes de la aplicación.
// A pesar de su nombre, el mensaje actual indica que se muestra cuando hay conexión a Internet.
export default OfflineModal;