Etiquetas

GeoCercas


 GeoCercas

import React, { useRef, useState, memo } from "react";
import { MapContainer, TileLayer } from "react-leaflet";

// Importaci贸n de componentes personalizados para manejar diferentes elementos del mapa
import ManejoSujetoPiloto from './componentes/ManejadorSujetoPiloto';
import ManejoLineas from './componentes/ManejoLineas';
import ManejoPoligono from './componentes/ManejoPoligono';
import ManejoMarcadores from './componentes/ManejoMarcadores';
import ManejoCircunferencia from './componentes/ManejoCirculo';

// 馃敼 Componente funcional memoizado para un bot贸n de toggle reutilizable
// memo() se utiliza para optimizar el rendimiento evitando re-renderizaciones innecesarias
const ToggleButton = memo(({ children, label, top, left, isActive, onClick }) => (
  <>
    {/* Bot贸n HTML con estilos inline para posicionamiento y apariencia */}
    <button
      onClick={onClick} // Funci贸n que se ejecuta al hacer clic en el bot贸n
      style={{
        position: 'absolute', // Permite posicionar el bot贸n exactamente en las coordenadas top y left
        top, // Posici贸n vertical del bot贸n
        left, // Posici贸n horizontal del bot贸n
        zIndex: 1000, // Asegura que el bot贸n est茅 por encima de otros elementos del mapa
        backgroundColor: isActive ? '#4CAF50' : '#f44336', // Cambia el color de fondo seg煤n si el bot贸n est谩 activo o no
        color: 'white', // Color del texto del bot贸n
        padding: '5px 10px', // Espaciado interno del bot贸n
        border: 'none', // Elimina el borde predeterminado del bot贸n
        borderRadius: '4px', // Redondea las esquinas del bot贸n
        cursor: 'pointer', // Cambia el cursor al pasar sobre el bot贸n para indicar que es interactivo
      }}
    >
      {label} {/* Texto que se muestra en el bot贸n */}
    </button>
    {/* Si el bot贸n est谩 activo (isActive es true), renderiza los componentes hijos */}
    {isActive && children}
  </>
));

// Componente funcional principal que contiene el mapa y los controles de toggle
const MapViewGeoCer = () => {
  // useRef para acceder a la instancia del componente MapContainer
  const mapRef = useRef(null);
  // Estado para controlar qu茅 bot贸n de toggle est谩 actualmente activo
  const [activeButton, setActiveButton] = useState(null);

  // Funci贸n para manejar el clic en los botones de toggle
  // Cambia el estado de activeButton para mostrar u ocultar el componente asociado
  const handleToggleButton = (key) => {
    setActiveButton(prev => (prev === key ? null : key));
  };

  return (
    // Componente contenedor del mapa de Leaflet
    <MapContainer
      center={[19.285653, -99.147809]} // Coordenadas iniciales del centro del mapa (Ciudad de M茅xico)
      zoom={13} // Nivel de zoom inicial del mapa
      ref={mapRef} // Asigna la referencia al elemento MapContainer
      style={{ height: "100vh", width: "100vw" }} // Establece la altura y el ancho del mapa para ocupar toda la ventana
    >
      {/* Componente para la capa de tiles del mapa (OpenStreetMap en este caso) */}
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' // Atribuci贸n requerida por OpenStreetMap
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" // URL del servicio de tiles de OpenStreetMap
      />

      {/* Bot贸n de toggle para el manejo del sujeto piloto */}
      <ToggleButton
        label="Sujeto Piloto" // Texto del bot贸n
        top={80} // Posici贸n vertical del bot贸n
        left={10} // Posici贸n horizontal del bot贸n
        isActive={activeButton === 'Sujetopiloto'} // Indica si este bot贸n est谩 activo
        onClick={() => handleToggleButton('Sujetopiloto')} // Funci贸n a ejecutar al hacer clic
      >
        {/* Si el bot贸n est谩 activo, renderiza el componente ManejoSujetoPiloto */}
        <ManejoSujetoPiloto />
      </ToggleButton>

      {/* Bot贸n de toggle para el manejo de l铆neas */}
      <ToggleButton
        label="L铆neas"
        top={120}
        left={10}
        isActive={activeButton === 'lineas'}
        onClick={() => handleToggleButton('lineas')}
      >
        <ManejoLineas />
      </ToggleButton>

      {/* Bot贸n de toggle para el manejo de pol铆gonos */}
      <ToggleButton
        label="Pol铆gonos"
        top={160}
        left={10}
        isActive={activeButton === 'poligonos'}
        onClick={() => handleToggleButton('poligonos')}
      >
        <ManejoPoligono />
      </ToggleButton>

      {/* Bot贸n de toggle para el manejo de marcadores */}
      <ToggleButton
        label="Marcadores"
        top={200}
        left={10}
        isActive={activeButton === 'marcadores'}
        onClick={() => handleToggleButton('marcadores')}
      >
        <ManejoMarcadores />
      </ToggleButton>

      {/* Bot贸n de toggle para el manejo de c铆rculos */}
      <ToggleButton
        label="C铆rculos"
        top={240}
        left={10}
        isActive={activeButton === 'circulos'}
        onClick={() => handleToggleButton('circulos')}
      >
        <ManejoCircunferencia />
      </ToggleButton>
    </MapContainer>
  );
};

// Exporta el componente MapViewGeoCer para su uso en otras partes de la aplicaci贸n
export default MapViewGeoCer;

siguiente componente : 

import React, { useRef, useState, useCallback, useEffect } from "react";
import { TileLayer, useMapEvents, Marker, Popup, Circle, Polyline, Polygon, LayerGroup } from "react-leaflet";
import L from "leaflet"; // Importaci贸n de la biblioteca Leaflet base
import icon from "./icon"; // Importaci贸n de un icono personalizado para los marcadores
import * as turf from "@turf/turf"; // Importaci贸n de la biblioteca turf.js para an谩lisis geoespacial
import ManejoSujetoPilotoAviso from "./ManejadorSujetoPilotoAviso"; // Importaci贸n de un componente para mostrar avisos relacionados con el sujeto piloto

// Componente funcional MapEvents que utiliza el hook useMapEvents para manejar eventos del mapa
const MapEvents = ({ onMapClick }) => {
    // useMapEvents permite adjuntar listeners a los eventos del mapa
    useMapEvents({
        // Escucha el evento de 'click' en el mapa
        click: (e) => {
            // Cuando se hace clic en el mapa, llama a la funci贸n onMapClick proporcionando las coordenadas (latitud y longitud) del clic
            onMapClick(e.latlng);
        }
    });
    // Este componente no renderiza nada visualmente, solo adjunta el event listener
    return null;
};

// Componente funcional ManejoSujetoPiloto que gestiona la visualizaci贸n e interacci贸n con marcadores en el mapa
const ManejoSujetoPiloto = () => {
    // Estado para almacenar la lista de datos geogr谩ficos (marcadores)
    const [datosGeograficos, setDatosGeograficos] = useState([]);
    // Estado para almacenar la lista de l铆neas dibujadas en el mapa
    const [lineas, setLineas] = useState([]);
    // Estado para almacenar el ID del marcador actualmente seleccionado (para mostrar informaci贸n o realizar acciones sobre 茅l)
    const [marcadorSeleccionado, setMarcadorSeleccionado] = useState(null);
    // Estado para almacenar la informaci贸n del punto m谩s cercano a un marcador seleccionado
    const [puntoMasCercano, setPuntoMasCercano] = useState(null);
    // Estado para controlar la visibilidad de un aviso relacionado con el punto m谩s cercano
    const [puntoMasCercanoAviso, setPuntoMasCercanoAviso] = useState(false);

    // useRef para mantener una referencia a los popups de los marcadores (aunque no se est茅 utilizando expl铆citamente aqu铆 para controlar la apertura/cierre)
    const popupRefs = useRef([]);
    // useRef para mantener una referencia al LayerGroup que contiene los marcadores y otros elementos relacionados
    const layerGroupRef = useRef(null); // A帽adida referencia faltante

    // useCallback para memoizar la funci贸n que agrega un nuevo marcador
    const agregarMarcador = useCallback((latlng) => {
        // Crea un nuevo objeto de marcador con un ID 煤nico (timestamp) y las coordenadas con precisi贸n de 6 decimales
        const nuevoMarcador = {
            id: Date.now(),
            lat: parseFloat(latlng.lat.toFixed(6)),
            lng: parseFloat(latlng.lng.toFixed(6))
        };
        // Actualiza el estado de datosGeograficos a帽adiendo el nuevo marcador al array anterior
        setDatosGeograficos((prev) => [...prev, nuevoMarcador]);
    }, []); // La funci贸n solo depende de setDatosGeograficos, por lo que el array de dependencias est谩 vac铆o

    // useCallback para memoizar la funci贸n que elimina un marcador por su ID
    const eliminarMarcador = useCallback((id) => {
        // Actualiza el estado de datosGeograficos filtrando el array para excluir el marcador con el ID proporcionado
        setDatosGeograficos(prev => prev.filter(marker => marker.id !== id));
        // Si el marcador eliminado era el seleccionado, resetea el estado de marcadorSeleccionado y puntoMasCercano
        if (marcadorSeleccionado === id) {
            setMarcadorSeleccionado(null);
            setPuntoMasCercano(null);
        }
    }, [marcadorSeleccionado]); // La funci贸n depende de marcadorSeleccionado

    // useCallback para memoizar la funci贸n que actualiza la posici贸n de un marcador existente
    const actualizarPosicionMarcador = useCallback((id, latlng) => {
        // Actualiza el estado de datosGeograficos mapeando sobre el array y modificando el marcador con el ID proporcionado
        setDatosGeograficos(prev =>
            prev.map(marker =>
                marker.id === id ? {
                    ...marker,
                    lat: parseFloat(latlng.lat.toFixed(6)),
                    lng: parseFloat(latlng.lng.toFixed(6))
                } : marker
            )
        );
    }, []); // La funci贸n solo depende de setDatosGeograficos

    // useCallback para memoizar la funci贸n que agrega una nueva l铆nea al estado de l铆neas
    const agregarLinea = useCallback((coordenadas) => {
        // Actualiza el estado de l铆neas a帽adiendo las nuevas coordenadas de la l铆nea al array anterior
        setLineas(prev => [...prev, coordenadas]);
    }, []); // La funci贸n solo depende de setLineas

    // useCallback para memoizar la funci贸n que calcula el punto m谩s cercano a un marcador seleccionado utilizando la biblioteca turf.js
    const calcularPuntoMasCercano = useCallback((markerId) => {
        // Si hay menos de dos marcadores, no se puede calcular un punto m谩s cercano
        if (datosGeograficos.length < 2) {
            setPuntoMasCercano(null);
            return;
        }

        // Encuentra el marcador para el cual se va a calcular el punto m谩s cercano
        const marcador = datosGeograficos.find(m => m.id === markerId);
        if (!marcador) return;

        // Crea un objeto de punto turf.js para el marcador actual
        const punto = turf.point([marcador.lng, marcador.lat]);
        let puntoMasCercano = null;
        let distanciaMinima = Infinity;
        let coordenadasCercano = null;

        // Itera sobre todos los dem谩s marcadores para encontrar el m谩s cercano
        datosGeograficos.forEach(otroMarker => {
            // Excluye el marcador actual de la comparaci贸n
            if (otroMarker.id !== markerId) {
                // Crea un objeto de punto turf.js para el otro marcador
                const otroPunto = turf.point([otroMarker.lng, otroMarker.lat]);
                // Calcula la distancia en metros entre los dos puntos usando turf.distance
                const distancia = turf.distance(punto, otroPunto, { units: 'meters' });
                const otraCoordenadas = [otroMarker.lng, otroMarker.lat];
                // Si la distancia actual es menor que la distancia m铆nima encontrada hasta ahora, actualiza el punto m谩s cercano y la distancia m铆nima
                if (distancia < distanciaMinima) {
                    distanciaMinima = distancia;
                    puntoMasCercano = otroMarker;
                    coordenadasCercano = otraCoordenadas;
                    setPuntoMasCercanoAviso(true); // Activa el aviso del punto m谩s cercano
                }
            }
        });

        // Actualiza el estado de puntoMasCercano con la informaci贸n del marcador m谩s cercano y la distancia
        setPuntoMasCercano(puntoMasCercano ? {
            id: puntoMasCercano.id,
            distancia: distanciaMinima,
            coordenadas: coordenadasCercano
        } : null);
    }, [datosGeograficos]); // La funci贸n depende del estado de datosGeograficos

    // useEffect para recalcular el punto m谩s cercano cuando cambia el marcador seleccionado
    useEffect(() => {
        if (marcadorSeleccionado) {
            calcularPuntoMasCercano(marcadorSeleccionado);
        } else {
            setPuntoMasCercano(null);
        }
    }, [marcadorSeleccionado, calcularPuntoMasCercano]); // Se ejecuta cuando cambia marcadorSeleccionado o calcularPuntoMasCercano

    // useCallback para memoizar el handler del evento de clic en el mapa
    const handleMapClick = useCallback((latlng) => {
        agregarMarcador(latlng); // Llama a la funci贸n para agregar un nuevo marcador en la ubicaci贸n del clic
    }, [agregarMarcador]); // Depende de la funci贸n agregarMarcador

    // useCallback para memoizar el handler del evento de dragend de un marcador
    const handleDragEnd = useCallback((markerId) => (e) => {
        actualizarPosicionMarcador(markerId, e.target.getLatLng()); // Llama a la funci贸n para actualizar la posici贸n del marcador arrastrado
    }, [actualizarPosicionMarcador]); // Depende de la funci贸n actualizarPosicionMarcador

    // useCallback para memoizar el handler del evento de clic en el bot贸n de eliminar de un popup
    const handleDeleteMarker = useCallback((markerId, e) => {
        e.stopPropagation(); // Evita que el clic en el bot贸n se propague al mapa y cause otros eventos
        eliminarMarcador(markerId); // Llama a la funci贸n para eliminar el marcador
    }, [eliminarMarcador]); // Depende de la funci贸n eliminarMarcador

    // Funci贸n para obtener la posici贸n (latitud y longitud) de un marcador dado su ID
    const getMarkerPosition = (id) => {
        const marker = datosGeograficos.find(m => m.id === id);
        return marker ? [marker.lat, marker.lng] : null;
    };

    return (
        <>
            {/* LayerGroup para agrupar los marcadores y otros elementos relacionados */}
            <LayerGroup ref={layerGroupRef}>
                {/* Mapea sobre el array de datosGeograficos para renderizar un Marker para cada objeto */}
                {datosGeograficos.map((marker, index) => {
                    // Determina si el marcador actual es el seleccionado
                    const esSeleccionado = marcadorSeleccionado === marker.id;
                    // Determina si el marcador seleccionado tiene un punto m谩s cercano calculado
                    const tieneCercano = puntoMasCercano && esSeleccionado;

                    return (
                        // React.Fragment para agrupar elementos sin agregar un nodo extra al DOM
                        <React.Fragment key={marker.id}>
                            {/* Componente Marker de react-leaflet */}
                            <Marker
                                position={[marker.lat, marker.lng]} // Posici贸n del marcador
                                icon={icon} // Icono personalizado para el marcador
                                draggable // Permite que el marcador sea arrastrable
                                eventHandlers={{
                                    // Handler para el evento dragend (cuando se suelta el marcador despu茅s de arrastrar)
                                    dragend: handleDragEnd(marker.id),
                                    // Handler para el evento popupopen (cuando se abre el popup del marcador)
                                    popupopen: () => setMarcadorSeleccionado(marker.id),
                                    // Handler para el evento popupclose (cuando se cierra el popup del marcador)
                                    popupclose: () => setMarcadorSeleccionado(null)
                                }}
                                // Referencia al elemento Marker (puede ser 煤til para acceder a la instancia de Leaflet subyacente)
                                ref={(ref) => {
                                    if (ref) popupRefs.current[index] = ref;
                                }}
                            >
                                {/* Componente Popup que se muestra al hacer clic en el marcador */}
                                <Popup>
                                    <div>
                                        <p>ID: {marker.id}</p>
                                        {/* Si el marcador est谩 seleccionado y se ha encontrado un punto m谩s cercano, muestra la informaci贸n */}
                                        {tieneCercano && puntoMasCercano?.coordenadas && (
                                            <div>
                                                <h4>Punto m谩s cercano:</h4>
                                                <p>Distancia: {puntoMasCercano.distancia.toFixed(2)} metros</p>
                                            </div>
                                        )}
                                        {/* Bot贸n para eliminar el marcador */}
                                        <button
                                            onClick={(e) => handleDeleteMarker(marker.id, e)}
                                            style={{
                                                marginTop: '10px',
                                                padding: '5px 10px',
                                                backgroundColor: '#ff4444',
                                                color: 'white',
                                                border: 'none',
                                                borderRadius: '4px',
                                                cursor: 'pointer'
                                            }}
                                        >
                                            Eliminar marcador
                                        </button>
                                    </div>
                                </Popup>
                            </Marker>
                            {/* Si se ha activado el aviso del punto m谩s cercano y hay coordenadas disponibles, renderiza el componente ManejoSujetoPilotoAviso */}
                            {puntoMasCercanoAviso && puntoMasCercano?.coordenadas && (
                                <ManejoSujetoPilotoAviso
                                    coordenadasAviso={[puntoMasCercano.coordenadas[1], puntoMasCercano.coordenadas[0]]} // [lat, lng]
                                    coordenadasOrigen={[marker.lat, marker.lng]}
                                    layerGroupRef={layerGroupRef}
                                />
                            )}
                        </React.Fragment>
                    );
                })}
            </LayerGroup>

            {/* Si hay un marcador seleccionado y se ha encontrado un punto m谩s cercano, renderiza una Polyline para conectar ambos puntos */}
            {marcadorSeleccionado && puntoMasCercano?.id && (
                <Polyline
                    positions={[
                        getMarkerPosition(marcadorSeleccionado),
                        getMarkerPosition(puntoMasCercano.id)
                    ].filter(Boolean)} // Filtra valores nulos en caso de que las posiciones no se encuentren
                    color="#ff7800"
                    weight={4}
                    dashArray="10, 5" // Estilo de l铆nea punteada
                />
            )}

            {/* Mapea sobre el array de l铆neas para renderizar componentes Polyline */}
            {lineas.map((linea, index) => (
                <Polyline key={`linea-${index}`} positions={linea} color="red" />
            ))}

            {/* Componente MapEvents para manejar los clics en el mapa y agregar nuevos marcadores */}
            <MapEvents onMapClick={handleMapClick} />
        </>
    );
};

// Exporta el componente ManejoSujetoPiloto para su uso en otras partes de la aplicaci贸n
export default ManejoSujetoPiloto;

siguiente componente 

import React, { useEffect, useRef } from "react"; // Importa React y los hooks useEffect y useRef
import { Circle } from "react-leaflet"; // Importa el componente Circle de react-leaflet (aunque no se usa directamente en este c贸digo)
import L from "leaflet"; // Importa la biblioteca principal de Leaflet
import * as turf from "@turf/turf"; // Importa la biblioteca turf.js para an谩lisis espacial (aunque no se usa en este c贸digo)

// Define el componente funcional ManejoSujetoPilotoAviso
const ManejoSujetoPilotoAviso = ({
  coordenadasAviso, // Propiedad que recibe un array con las coordenadas [latitud, longitud] del aviso
  coordenadasOrigen, // Propiedad que recibe las coordenadas de origen (no se utiliza en este componente)
  layerGroupRef // Propiedad que recibe una referencia mutable a un grupo de capas de Leaflet
}) => {
  // Crea una referencia mutable para el objeto L.Circle de Leaflet
  const circleRef = useRef<L.Circle | null>(null);

  // Hook useEffect que se ejecuta despu茅s de cada renderizado (similar a componentDidMount y componentWillUnmount)
  useEffect(() => {
    // Verifica si la referencia al grupo de capas no es v谩lida o si las coordenadas del aviso no est谩n definidas o no son un array
    if (!layerGroupRef.current || !coordenadasAviso || !Array.isArray(coordenadasAviso)) {
      return; // Sale de la funci贸n si alguna de las dependencias no es v谩lida
    }

    // Valida las coordenadas del aviso
    const [lat, lng] = coordenadasAviso; // Desestructura el array de coordenadas en latitud y longitud
    if (typeof lat !== 'number' || typeof lng !== 'number' || isNaN(lat) || isNaN(lng)) {
      console.error('Coordenadas inv谩lidas:', coordenadasAviso);
      return; // Sale de la funci贸n si las coordenadas no son n煤meros v谩lidos
    }

    // Crear un nuevo c铆rculo de Leaflet
    const circle = new L.Circle([lat, lng], {
      radius: 50, // Define el radio del c铆rculo en metros
      color: 'blue', // Define el color del borde del c铆rculo
      fillColor: 'blue', // Define el color de relleno del c铆rculo
      fillOpacity: 0.2 // Define la opacidad del relleno del c铆rculo (0.2 es 20% de opacidad)
    });

    // Agregar el c铆rculo al grupo de capas proporcionado por la referencia
    layerGroupRef.current.addLayer(circle);
    // Guarda la instancia del c铆rculo en la referencia mutable para poder acceder a ella despu茅s
    circleRef.current = circle;

    // Imprime en la consola las coordenadas del aviso
    console.log("Coordenadas del aviso:", coordenadasAviso);

    // Funci贸n de limpieza que se ejecuta cuando el componente se desmonta o antes de que el useEffect se vuelva a ejecutar
    return () => {
      // Verifica si la referencia al c铆rculo y al grupo de capas son v谩lidas
      if (circleRef.current && layerGroupRef.current) {
        // Elimina el c铆rculo del grupo de capas para evitar fugas de memoria y elementos duplicados
        layerGroupRef.current.removeLayer(circleRef.current);
      }
    };
    // El useEffect se ejecutar谩 cada vez que cambien las coordenadas del aviso o la referencia al grupo de capas
  }, [coordenadasAviso, layerGroupRef]);

  // El componente no renderiza ning煤n elemento visual directamente
  return null;
};

// Exporta el componente ManejoSujetoPilotoAviso para que pueda ser utilizado en otras partes de la aplicaci贸n
export default ManejoSujetoPilotoAviso;



siguiente componente 


import React, { useState, useEffect, useCallback, useRef, createContext, useContext } from "react";
import {
  MapContainer, // Componente principal del mapa de Leaflet
  TileLayer, // Componente para agregar una capa de tiles (im谩genes del mapa)
  useMapEvents, // Hook para acceder a los eventos del mapa
  Polyline, // Componente para dibujar l铆neas en el mapa
  Polygon,  // Componente para dibujar pol铆gonos en el mapa
  Popup,    // Componente para mostrar ventanas emergentes en el mapa
  Marker,   // Componente para mostrar marcadores en el mapa
  LayerGroup // Componente para agrupar capas de Leaflet
} from "react-leaflet";
import * as turf from "@turf/turf"; // Importa la biblioteca turf.js para an谩lisis geoespacial (buffer, etc.)
import L from "leaflet"; // Importa la biblioteca Leaflet base
import PuntoMonitoreadoLinea from "./PuntoMonitoreadoLinea"; // Importa un componente personalizado para puntos de monitoreo de l铆nea

// Define un icono personalizado para los v茅rtices de las l铆neas editables
const vertexIcon = new L.Icon({
  iconUrl: 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-512.png',
  iconSize: [20, 20],
  iconAnchor: [10, 10]
});

// Crea un Context de React para compartir datos geogr谩ficos entre componentes
const DatosGeograficosContext = createContext();

// Hook personalizado para acceder f谩cilmente al contexto de datos geogr谩ficos
export const useDatosGeograficos = () => {
  const context = useContext(DatosGeograficosContext);
  if (!context) {
    throw new Error("useDatosGeograficos debe ser usado dentro de un DatosGeograficosProvider");
  }
  return context;
};

// Provider del Context de DatosGeograficos que envuelve a los componentes que necesitan acceder a estos datos
const DatosGeograficosProvider = ({ children }) => {
  // Estado para almacenar la lista de l铆neas dibujadas, cada l铆nea contiene sus puntos y su buffer
  const [lineas, setLineas] = useState([]);
  // Estado para almacenar los buffers (pol铆gonos alrededor de las l铆neas)
  const [buffers, setBuffers] = useState([]);
  // Estado para almacenar los puntos temporales que el usuario est谩 dibujando para crear una nueva l铆nea
  const [puntosTemporales, setPuntosTemporales] = useState([]);
  // Estado para controlar el radio del buffer alrededor de las l铆neas (en kil贸metros)
  const [radioBuffer, setRadioBuffer] = useState(0.1);
  // Estado para almacenar el 铆ndice de la l铆nea actualmente seleccionada
  const [lineaSeleccionada, setLineaSeleccionada] = useState(null);
  // Estado para indicar si el mapa est谩 en modo de edici贸n de una l铆nea
  const [editando, setEditando] = useState(false);

  // useCallback para memoizar la funci贸n que agrega un punto temporal al dibujar una nueva l铆nea
  const agregarPuntoTemporal = useCallback((latlng) => {
    setPuntosTemporales((prev) => [...prev, { lat: latlng.lat, lng: latlng.lng }]);
  }, []);

  // useCallback para memoizar la funci贸n que calcula el buffer alrededor de una l铆nea utilizando turf.js
  const calcularBuffer = useCallback((linea) => {
    if (!Array.isArray(linea) || linea.length < 2) return null;
    // Crea un objeto LineString de turf a partir de las coordenadas de la l铆nea
    const lineString = turf.lineString(linea.map(point => [point.lng, point.lat]));
    // Calcula el buffer alrededor de la LineString con el radio especificado
    const buffered = turf.buffer(lineString, radioBuffer, { units: 'kilometers' });
    return buffered;
  }, [radioBuffer]); // Depende del radio del buffer

  // useCallback para memoizar la funci贸n que finaliza el dibujo de una nueva l铆nea
  const finalizarLinea = useCallback(() => {
    if (puntosTemporales.length > 1) {
      // Crea una nueva l铆nea con los puntos temporales actuales
      const nuevaLinea = [...puntosTemporales];
      // Calcula el buffer para la nueva l铆nea
      const nuevoBuffer = calcularBuffer(nuevaLinea);
      // Agrega la nueva l铆nea y su buffer al estado
      setLineas((prev) => [...prev, { puntos: nuevaLinea, buffer: nuevoBuffer }]);
      setBuffers((prev) => [...prev, nuevoBuffer]);
      // Limpia los puntos temporales y desactiva el modo de edici贸n
      setPuntosTemporales([]);
      setEditando(false);
      return true;
    }
    // Si no hay suficientes puntos para crear una l铆nea, limpia los puntos temporales y deselecciona la l铆nea
    setPuntosTemporales([]);
    setLineaSeleccionada(null);
    setEditando(false);
    return false;
  }, [puntosTemporales, calcularBuffer]); // Depende de los puntos temporales y la funci贸n para calcular el buffer

  // useCallback para memoizar la funci贸n que selecciona una l铆nea por su 铆ndice
  const seleccionarLinea = useCallback((index) => {
    setLineaSeleccionada(index);
  }, []);

  // useCallback para memoizar la funci贸n que elimina la l铆nea actualmente seleccionada
  const eliminarLineaSeleccionada = useCallback(() => {
    if (lineaSeleccionada !== null) {
      // Filtra la lista de l铆neas y buffers para eliminar la l铆nea seleccionada
      setLineas(prev => prev.filter((_, i) => i !== lineaSeleccionada));
      setBuffers(prev => prev.filter((_, i) => i !== lineaSeleccionada));
      // Resetea el estado de la l铆nea seleccionada, puntos temporales y modo de edici贸n
      setLineaSeleccionada(null);
      setPuntosTemporales([]);
      setEditando(false);
    }
  }, [lineaSeleccionada]); // Depende de la l铆nea seleccionada

  // useCallback para memoizar la funci贸n que actualiza el radio del buffer
  const actualizarRadioBuffer = useCallback((nuevoRadio) => {
    setRadioBuffer(nuevoRadio);
    // Recalcula los buffers para todas las l铆neas existentes con el nuevo radio
    setBuffers(
      lineas.map((linea) => calcularBuffer(linea.puntos || linea))
    );
  }, [lineas, calcularBuffer]); // Depende de la lista de l铆neas y la funci贸n para calcular el buffer

  // useCallback para memoizar la funci贸n que actualiza la posici贸n de un v茅rtice de una l铆nea editable
    const actualizarVertice = useCallback((lineaIndex, verticeIndex, newLatLng) => {
        setLineas(prev => prev.map((linea, i) => {
            if (i === lineaIndex) {
                // Crea una copia de los puntos de la l铆nea y actualiza el v茅rtice espec铆fico
                const nuevosPuntos = [...linea.puntos];
                nuevosPuntos[verticeIndex] = { lat: newLatLng.lat, lng: newLatLng.lng };
                return { ...linea, puntos: nuevosPuntos };
            }
            return linea;
        }));
        // Actualiza los buffers despu茅s de modificar los v茅rtices
    }, []);

  // useCallback para memoizar la funci贸n que regenera los buffers de todas las l铆neas
  const regenerarBuffers = useCallback(() => {
    setBuffers(lineas.map(linea => calcularBuffer(linea.puntos || linea)));
  }, [lineas, calcularBuffer]); // Depende de la lista de l铆neas y la funci贸n para calcular el buffer

  // Objeto con los valores y funciones que se proporcionar谩n a los componentes consumidores
  const value = {
    lineas,
    buffers,
    puntosTemporales,
    radioBuffer,
    lineaSeleccionada,
    editando,
    agregarPuntoTemporal,
    finalizarLinea,
    seleccionarLinea,
    eliminarLineaSeleccionada,
    actualizarRadioBuffer,
    actualizarVertice,
    setEditando,
    regenerarBuffers
  };

  // Proporciona el contexto a los componentes hijos
  return (
    <DatosGeograficosContext.Provider value={value}>
      {children}
    </DatosGeograficosContext.Provider>
  );
};

// Componente funcional MapEvents para manejar eventos del mapa (clic y clic derecho)
const MapEvents = ({ onMapClick, onMapRightClick }) => {
  useMapEvents({
    click: (e) => onMapClick(e.latlng), // Llama a la funci贸n onMapClick con las coordenadas del clic
    contextmenu: (e) => {
      e.originalEvent.preventDefault(); // Evita el men煤 contextual predeterminado del navegador
      onMapRightClick(e); // Llama a la funci贸n onMapRightClick con el evento
    }
  });
  return null; // Este componente no renderiza nada visualmente
};

// Componente funcional ManejoLineas que utiliza el contexto para gestionar la creaci贸n y edici贸n de l铆neas
const ManejoLineas = () => {
  // Accede a los valores y funciones del contexto de datos geogr谩ficos
  const {
    lineas,
    buffers,
    puntosTemporales,
    radioBuffer,
    lineaSeleccionada,
    editando,
    agregarPuntoTemporal,
    finalizarLinea,
    seleccionarLinea,
    eliminarLineaSeleccionada,
    actualizarRadioBuffer,
    actualizarVertice,
    setEditando,
    regenerarBuffers
  } = useDatosGeograficos();

  // Estado para controlar qu茅 l铆nea muestra el componente PuntoMonitoreadoLinea
  const [mostrarPunto, setMostrarPunto] = useState<number | null>(null);
  // useRef para mantener una referencia al LayerGroup que contiene las l铆neas y buffers
  const layerGroupRef = useRef(null);
  // useRef para mantener una referencia a los popups de las l铆neas
  const popupRefs = useRef([]);
  // Estado para controlar la visibilidad del di谩logo de confirmaci贸n de eliminaci贸n
  const [mostrarConfirmacion, setMostrarConfirmacion] = useState(false);

  // useCallback para memoizar el handler del evento de clic en el mapa
  const handleMapClick = useCallback((latlng) => {
    // Si no se est谩 editando una l铆nea, agrega un punto temporal
    if (!editando) {
      agregarPuntoTemporal(latlng);
    }
  }, [agregarPuntoTemporal, editando]); // Depende de la funci贸n para agregar puntos temporales y el estado de edici贸n

  // useCallback para memoizar el handler del evento de clic derecho en el mapa
  const handleMapRightClick = useCallback(() => {
    // Si no se est谩 editando, finaliza la l铆nea actual
    if (!editando) {
      const seCreo = finalizarLinea();
      // Si se cre贸 una nueva l铆nea, la selecciona y abre su popup despu茅s de un peque帽o retraso
      if (seCreo) {
        setTimeout(() => {
          const nuevaLineaIndex = lineas.length;
          seleccionarLinea(nuevaLineaIndex);
          const ref = popupRefs.current[nuevaLineaIndex];
          if (ref && ref._map) {
            ref.openOn(ref._map);
          }
        }, 100);
      }
    }
  }, [editando, finalizarLinea, lineas.length, seleccionarLinea]); // Depende del estado de edici贸n, la funci贸n para finalizar la l铆nea, la longitud de las l铆neas y la funci贸n para seleccionar una l铆nea

  // Handler para el cambio en el input del radio del buffer
  const handleRadioChange = (e) => {
    const nuevoRadio = parseFloat(e.target.value);
    if (!isNaN(nuevoRadio)) {
      actualizarRadioBuffer(nuevoRadio);
    }
  };

  // Handler para seleccionar una l铆nea al hacer clic en ella
  const handleSeleccionarLinea = (index) => {
    seleccionarLinea(index);
    // Abre el popup de la l铆nea seleccionada
    const ref = popupRefs.current[index];
    if (ref && ref._map) {
      ref.openOn(ref._map);
    }
  };

  // Funci贸n para iniciar el modo de edici贸n de una l铆nea
  const iniciarEdicion = (index) => {
    setEditando(true);
    seleccionarLinea(index);
  };

  // Funci贸n para finalizar el modo de edici贸n de una l铆nea
  const finalizarEdicion = () => {
    setEditando(false);
    regenerarBuffers(); // Recalcula los buffers despu茅s de la edici贸n
  };

  return (
    <>
      <LayerGroup ref={layerGroupRef}>
        {/* Mapea sobre la lista de l铆neas para renderizar cada una */}
        {lineas.map((linea, index) => (
          <React.Fragment key={`linea-${index}`}>
            {/* Renderiza el buffer de la l铆nea como un Pol铆gono */}
            {buffers[index] && (
              <Polygon
                positions={buffers[index].geometry.coordinates[0].map(coord => ({ lat: coord[1], lng: coord[0] }))}
                color={lineaSeleccionada === index ? "#ff6347" : "#009688"} // Cambia el color si la l铆nea est谩 seleccionada
                fillOpacity={0.3}
                stroke={false}
              >
                {/* Popup asociado al buffer para acciones espec铆ficas de la l铆nea */}
                <Popup ref={(ref) => popupRefs.current[index] = ref}>
                  <button
                    onClick={() => setMostrarPunto(index)}
                    style={{
                      padding: '8px 16px',
                      backgroundColor: '#4CAF50',
                      color: 'white',
                      border: 'none',
                      borderRadius: '4px',
                      cursor: 'pointer',
                      marginRight: '10px',
                      marginBottom: '10px'
                    }}
                  >
                    Agregar Punto Monitoreo
                  </button>
                </Popup>
              </Polygon>
            )}

            {/* Renderiza el componente para agregar puntos de monitoreo a la l铆nea */}
            {mostrarPunto === index && (
              <PuntoMonitoreadoLinea layerGroupRef={layerGroupRef} buffer={buffers[index]}/>
            )}

            {/* Renderiza una Polyline invisible pero interactiva para facilitar la selecci贸n de la l铆nea */}
            <Polyline
              positions={linea.puntos || linea}
              color="transparent"
              weight={15}
              opacity={0}
              interactive={true}
              eventHandlers={{
                click: () => handleSeleccionarLinea(index)
              }}
            />

            {/* Renderiza la Polyline visible que representa la l铆nea */}
            <Polyline
              positions={linea.puntos || linea}
              color={lineaSeleccionada === index ? "red" : "blue"} // Cambia el color si la l铆nea est谩 seleccionada
              weight={6}
            >
              {/* Popup asociado a la l铆nea para mostrar opciones de edici贸n y eliminaci贸n */}
              <Popup ref={(ref) => popupRefs.current[index] = ref}>
                <div style={{ padding: '10px' }}>
                  <h4 style={{ marginTop: 0 }}>L铆nea {index + 1}</h4>

                  {/* Botones de Editar y Eliminar si no se est谩 editando */}
                  {!editando && (
                    <>
                      <button
                        onClick={() => iniciarEdicion(index)}
                        style={{
                          padding: '8px 16px',
                          backgroundColor: '#4CAF50',
                          color: 'white',
                          border: 'none',
                          borderRadius: '4px',
                          cursor: 'pointer',
                          marginRight: '10px',
                          marginBottom: '10px'
                        }}
                      >
                        Editar
                      </button>
                      <button
                        onClick={(e) => {
                          e.stopPropagation();
                          setMostrarConfirmacion(true);
                        }}
                        style={{
                          padding: '8px 16px',
                          backgroundColor: '#ff4444',
                          color: 'white',
                          border: 'none',
                          borderRadius: '4px',
                          cursor: 'pointer',
                          marginBottom: '10px'
                        }}
                      >
                        Eliminar
                      </button>
                    </>
                  )}

                  {/* Bot贸n de Finalizar Edici贸n si se est谩 editando la l铆nea actual */}
                  {editando && lineaSeleccionada === index && (
                    <button
                      onClick={() => finalizarEdicion()}
                      style={{
                        padding: '8px 16px',
                        backgroundColor: '#2196F3',
                        color: 'white',
                        border: 'none',
                        borderRadius: '4px',
                        cursor: 'pointer',
                        marginBottom: '10px'
                      }}
                    >
                      Finalizar Edici贸n
                    </button>
                  )}

                  {/* Input para cambiar el radio del buffer */}
                  <div style={{ marginTop: '10px' }}>
                    <label>
                      Radio del buffer (km):
                      <input
                        type="number"
                        step="0.01"
                        min="0.01"
                        max="10"
                        value={radioBuffer}
                        onChange={handleRadioChange}
                        style={{ width: '60px', marginLeft: '5px' }}
                      />
                    </label>
                  </div>
                </div>
              </Popup>
            </Polyline>

            {/* Renderiza Markers para los v茅rtices de la l铆nea si se est谩 editando la l铆nea actual */}
            {editando && lineaSeleccionada === index && (linea.puntos || linea).map((punto, verticeIndex) => (
              <Marker
                key={`vertice-${index}-${verticeIndex}`}
                position={[punto.lat, punto.lng]}
                icon={vertexIcon}
                draggable={true}
                eventHandlers={{
                  dragend: (e) => {
                    actualizarVertice(index, verticeIndex, e.target.getLatLng());
                  }
                }}
              />
            ))}
          </React.Fragment>
        ))}

        {/* Renderiza la l铆nea temporal que se est谩 dibujando */}
        {puntosTemporales.length > 1 && (
          <Polyline positions={puntosTemporales} color="red" dashArray="4" />
        )}
      </LayerGroup>

      {/* Componente MapEvents para manejar clics en el mapa */}
      <MapEvents
        onMapClick={handleMapClick}
        onMapRightClick={handleMapRightClick}
      />

      {/* Di谩logo de confirmaci贸n para eliminar una l铆nea */}
      {mostrarConfirmacion && (
        <div style={{
          position: 'fixed',
          top: 0, left: 0, right: 0, bottom: 0,
          backgroundColor: 'rgba(0,0,0,0.5)',
          display: 'flex', justifyContent: 'center', alignItems: 'center',
          zIndex: 1000
        }}>
          <div style={{
            backgroundColor: 'white',
            padding: '20px',
            borderRadius: '8px',
            width: '300px',
            boxShadow: '0 2px 10px rgba(0,0,0,0.3)'
          }}>
            <h3>¿Est谩s seguro?</h3>
            <p>¿Deseas eliminar la l铆nea seleccionada?</p>
            <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '20px' }}>
              <button
                onClick={() => setMostrarConfirmacion(false)}
                style={{ marginRight: '10px', padding: '6px 12px' }}
              >
                Cancelar
              </button>
              <button
                onClick={() => {
                  eliminarLineaSeleccionada();
                  setMostrarConfirmacion(false);
                }}
                style={{
                  backgroundColor: '#ff4444',
                  color: 'white',
                  padding: '6px 12px',
                  border: 'none',
                  borderRadius: '4px',
                  cursor: 'pointer'
                }}
              >
                Eliminar
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

// Componente Provider que envuelve la aplicaci贸n y proporciona el contexto de datos geogr谩ficos
export const LineasProvider = ({ children }) => (
  <DatosGeograficosProvider>
    {children}
    <ManejoLineas />
  </DatosGeograficosProvider>
);

export default LineasProvider;




suguiente componennte : 

import React, { useState, useEffect, useRef } from "react";
import { useMapEvents } from "react-leaflet"; // Hook para acceder a los eventos del mapa de Leaflet
import L from "leaflet"; // Importa la biblioteca principal de Leaflet
import icon from "./icon"; // Importa el icono personalizado para los marcadores
import * as turf from "@turf/turf"; // Importa la biblioteca turf.js para an谩lisis espacial

// Define la interfaz para las coordenadas de latitud y longitud
interface Coordenadas {
  lat: number;
  lng: number;
}

// Define la interfaz para las propiedades del componente PuntoMonitoreadoProps
interface PuntoMonitoreadoProps {
  layerGroupRef: React.RefObject<L.LayerGroup>; // Referencia mutable a un grupo de capas de Leaflet
  buffer: turf.helpers.Feature<turf.helpers.Polygon | turf.helpers.MultiPolygon>; // Objeto GeoJSON que representa un buffer (pol铆gono o multipol铆gono)
}

/**
 * Funci贸n para corregir la geometr铆a de un buffer, asegurando que el primer y 煤ltimo punto de cada anillo sean iguales
 * para formar pol铆gonos cerrados v谩lidos.
 * @param buffer Objeto GeoJSON del buffer a corregir.
 * @returns Objeto GeoJSON del buffer corregido o null si la geometr铆a no es v谩lida.
 */
function corregirGeometria(
  buffer: turf.helpers.Feature<turf.helpers.Polygon | turf.helpers.MultiPolygon>
): turf.helpers.Feature<turf.helpers.Polygon | turf.helpers.MultiPolygon> | null {
  // Verifica si el buffer tiene geometr铆a y coordenadas definidas
  if (!buffer.geometry || !buffer.geometry.coordinates) {
    console.error("Buffer no tiene geometr铆a o coordenadas definidas.");
    return null;
  }

  // Obtiene el tipo de geometr铆a del buffer
  const tipo = buffer.geometry.type;

  // Procesa si la geometr铆a es un Pol铆gono
  if (tipo === "Polygon") {
    // Itera sobre cada anillo (exterior e interior) del pol铆gono
    const coords = buffer.geometry.coordinates.map(anillo => {
      // Obtiene el primer y 煤ltimo punto del anillo
      const primero = anillo[0];
      const ultimo = anillo[anillo.length - 1];
      // Verifica si el primer y 煤ltimo punto son diferentes
      if (primero[0] !== ultimo[0] || primero[1] !== ultimo[1]) {
        // Si son diferentes, agrega el primer punto al final para cerrar el pol铆gono
        return [...anillo, primero];
      }
      // Si ya est谩n cerrados, devuelve el anillo sin cambios
      return anillo;
    });
    // Crea un nuevo objeto GeoJSON de tipo Pol铆gono con las coordenadas corregidas y las propiedades originales
    return turf.polygon(coords, buffer.properties || {});
  }

  // Procesa si la geometr铆a es un MultiPol铆gono
  if (tipo === "MultiPolygon") {
    // Itera sobre cada pol铆gono del MultiPol铆gono
    const coords = buffer.geometry.coordinates.map(poligono =>
      // Itera sobre cada anillo de cada pol铆gono
      poligono.map(anillo => {
        // Obtiene el primer y 煤ltimo punto del anillo
        const primero = anillo[0];
        const ultimo = anillo[anillo.length - 1];
        // Verifica si el primer y 煤ltimo punto son diferentes
        if (primero[0] !== ultimo[0] || primero[1] !== ultimo[1]) {
          // Si son diferentes, agrega el primer punto al final para cerrar el pol铆gono
          return [...anillo, primero];
        }
        // Si ya est谩n cerrados, devuelve el anillo sin cambios
        return anillo;
      })
    );
    // Crea un nuevo objeto GeoJSON de tipo MultiPol铆gono con las coordenadas corregidas y las propiedades originales
    return turf.multiPolygon(coords, buffer.properties || {});
  }

  // Si el tipo de geometr铆a no es Pol铆gono ni MultiPol铆gono, muestra un error
  console.error("Tipo de geometr铆a no soportado.");
  return null;
}

// Componente funcional de React para mostrar y gestionar un punto de monitoreo en el mapa
const PuntoMonitoreadoLinea: React.FC<PuntoMonitoreadoProps> = ({ layerGroupRef, buffer }) => {
  // Estado para almacenar las coordenadas del punto de monitoreo
  const [coordenadas, setCoordenadas] = useState<Coordenadas | null>(null);
  // Referencia mutable para el marcador de Leaflet
  const markerRef = useRef<L.Marker | null>(null);

  // Hook para manejar eventos del mapa (doble clic y clic derecho)
  useMapEvents({
    // Evento de doble clic en el mapa
    dblclick: (e) => {
      // Establece las coordenadas del clic como las coordenadas del punto de monitoreo
      setCoordenadas(e.latlng);
    },
    // Evento de clic con el bot贸n derecho del rat贸n (men煤 contextual)
    contextmenu: (e) => {
      // Evita el comportamiento predeterminado del men煤 contextual del navegador
      e.originalEvent.preventDefault();
      // Establece las coordenadas del clic como las coordenadas del punto de monitoreo
      setCoordenadas(e.latlng);
    }
  });

  // Hook useEffect que se ejecuta cuando cambian las coordenadas, la referencia al grupo de capas o el buffer
  useEffect(() => {
    // Verifica si hay coordenadas, una referencia v谩lida al grupo de capas y un buffer
    if (coordenadas && layerGroupRef.current && buffer) {
      // Si ya existe un marcador, lo elimina del grupo de capas
      if (markerRef.current) {
        layerGroupRef.current.removeLayer(markerRef.current);
      }

      // Crea un nuevo marcador de Leaflet en las coordenadas actuales
      const marker = L.marker([coordenadas.lat, coordenadas.lng], {
        icon: icon, // Asigna el icono personalizado al marcador
        draggable: true // Permite que el marcador sea arrastrable
      });

      // Agrega el marcador al grupo de capas
      layerGroupRef.current.addLayer(marker);
      // Actualiza la referencia al marcador
      markerRef.current = marker;

      // Crea un objeto turf.js de tipo Punto con las coordenadas
      const punto = turf.point([coordenadas.lng, coordenadas.lat]);
      // Corrige la geometr铆a del buffer utilizando la funci贸n corregirGeometria
      const bufferCorregido = corregirGeometria(buffer);
      // Variable para almacenar el mensaje de estado (dentro o fuera del buffer)
      let mensajeEstado = "";

      // Verifica si el buffer corregido es v谩lido
      if (bufferCorregido) {
        // Utiliza turf.booleanPointInPolygon para verificar si el punto est谩 dentro del buffer
        const dentro = turf.booleanPointInPolygon(punto, bufferCorregido);
        // Establece el mensaje de estado basado en si el punto est谩 dentro o fuera
        mensajeEstado = dentro
          ? "✅ El punto est谩 <b>dentro</b> del buffer."
          : "❌ El punto est谩 <b>fuera</b> del buffer.";
        // Imprime el estado en la consola (sin etiquetas HTML)
        console.log(mensajeEstado.replace(/<[^>]*>/g, ""));
      } else {
        // Si el buffer no es v谩lido, establece un mensaje de error
        mensajeEstado = "⚠️ Buffer no v谩lido.";
        console.error(mensajeEstado);
      }

      // Define el contenido del popup que se mostrar谩 al hacer clic en el marcador
      const popupContent = `
        <div>
          <p><b>Ubicaci贸n:</b> ${coordenadas.lat.toFixed(5)}, ${coordenadas.lng.toFixed(5)}</p>
          <p>${mensajeEstado}</p>
          <button id="eliminar-marcador" style="padding: 5px 10px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer;">Eliminar</button>
        </div>
      `;
      // Asocia el popup al marcador y lo abre inicialmente
      marker.bindPopup(popupContent).openPopup();

      // Agrega un evento 'dragend' al marcador para actualizar las coordenadas cuando se arrastra
      marker.on("dragend", (e) => {
        const position = e.target.getLatLng();
        setCoordenadas({ lat: position.lat, lng: position.lng });
      });

      // Agrega un evento 'popupopen' al marcador para manejar el bot贸n de eliminar dentro del popup
      marker.on("popupopen", () => {
        // Obtiene la referencia al bot贸n de eliminar por su ID
        const btn = document.getElementById("eliminar-marcador");
        // Si el bot贸n existe
        if (btn) {
          // Define la funci贸n que se ejecutar谩 al hacer clic en el bot贸n de eliminar
          btn.onclick = () => {
            // Verifica si la referencia al marcador actual es v谩lida
            if (markerRef.current) {
              // Elimina el marcador del grupo de capas
              layerGroupRef.current?.removeLayer(markerRef.current);
              // Limpia la referencia al marcador
              markerRef.current = null;
              // Limpia el estado de las coordenadas
              setCoordenadas(null);
            }
          };
        }
      });
    }
    // El useEffect se ejecutar谩 cada vez que cambien las coordenadas, la referencia al grupo de capas o el buffer
  }, [coordenadas, layerGroupRef, buffer]);

  // El componente no renderiza nada visualmente por s铆 mismo, ya que manipula directamente las capas del mapa
  return null;
};

// Exporta el componente PuntoMonitoreadoLinea para su uso en otras partes de la aplicaci贸n
export default PuntoMonitoreadoLinea;


siguiente componente 

import React, { useState, useCallback, useRef } from "react"; // Importa React y los hooks useState, useCallback y useRef
import {
  useMapEvents, // Hook para acceder a los eventos del mapa de Leaflet
  Polygon, // Componente para renderizar pol铆gonos en el mapa
  Polyline, // Componente para renderizar l铆neas poligonales en el mapa
  Popup, // Componente para mostrar ventanas emergentes en el mapa
  Marker, // Componente para renderizar marcadores en el mapa
  LayerGroup // Componente para agrupar capas de Leaflet
} from "react-leaflet"; // Importa componentes espec铆ficos de react-leaflet
import L from "leaflet"; // Importa la biblioteca principal de Leaflet
import * as turf from "@turf/turf"; // Importa la biblioteca turf.js para an谩lisis espacial
import PuntoMonitoreadoPoligono from './PuntoMonitoreadoPoligono'; // Importa un componente personalizado para el manejo de puntos de monitoreo dentro de un pol铆gono

// Define una interfaz para representar coordenadas de latitud y longitud
interface LatLng {
  lat: number;
  lng: number;
}

// Define una interfaz para representar un pol铆gono con su ID y sus v茅rtices
interface Poligono {
  id: number;
  vertices: LatLng[];
}

// Define las propiedades del componente MapEvents
interface MapEventsProps {
  onMapClick: (latlng: LatLng) => void; // Funci贸n que se llama al hacer clic en el mapa
  onMapRightClick: (e: any) => void; // Funci贸n que se llama al hacer clic derecho en el mapa
}

// Componente funcional MapEvents que utiliza el hook useMapEvents para manejar eventos del mapa
const MapEvents: React.FC<MapEventsProps> = ({ onMapClick, onMapRightClick }) => {
  useMapEvents({
    click: (e) => onMapClick(e.latlng), // Asocia el evento 'click' del mapa a la funci贸n onMapClick, pasando las coordenadas del clic
    contextmenu: (e) => { // Asocia el evento 'contextmenu' (clic derecho) del mapa
      e.originalEvent.preventDefault(); // Previene el men煤 contextual predeterminado del navegador
      onMapRightClick(e); // Llama a la funci贸n onMapRightClick, pasando el evento
    }
  });
  return null; // Este componente no renderiza nada visualmente, solo maneja eventos del mapa
};

// Define un icono personalizado para los v茅rtices del pol铆gono
const vertexIcon = new L.Icon({
  iconUrl: 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-512.png', // URL de la imagen del icono
  iconSize: [20, 20], // Tama帽o del icono en p铆xeles
  iconAnchor: [10, 10] // Punto del icono que corresponde a la ubicaci贸n del marcador
});

// Componente funcional ManejoPoligono que gestiona la creaci贸n, selecci贸n, eliminaci贸n y edici贸n de pol铆gonos
const ManejoPoligono: React.FC = () => {
  // Estado para almacenar la lista de pol铆gonos creados
  const [poligonos, setPoligonos] = useState<Poligono[]>([]);
  // Estado para almacenar los puntos temporales que forman un pol铆gono en proceso de creaci贸n
  const [puntosTemporales, setPuntosTemporales] = useState<LatLng[]>([]);
  // Estado para almacenar el ID del pol铆gono actualmente seleccionado
  const [poligonoSeleccionado, setPoligonoSeleccionado] = useState<number | null>(null);
  // Estado para controlar la visibilidad del cuadro de confirmaci贸n de eliminaci贸n
  const [mostrarConfirmacion, setMostrarConfirmacion] = useState(false);
  // Estado para almacenar el ID del pol铆gono que se va a eliminar
  const [poligonoAEliminar, setPoligonoAEliminar] = useState<number | null>(null);
  // Estado para controlar la visibilidad del componente PuntoMonitoreadoPoligono para un pol铆gono espec铆fico
  const [mostrarPunto, setMostrarPunto] = useState<number | null>(null);
  // Referencia mutable para almacenar las referencias a los componentes Popup de cada pol铆gono
  const popupRefs = useRef<any[]>([]);
  // Referencia mutable para el grupo de capas donde se renderizar谩n los pol铆gonos y otros elementos
  const layerGroupRef = useRef<L.LayerGroup | null>(null);

  // Funci贸n useCallback para agregar un nuevo punto temporal a la lista de puntos temporales
  const agregarPuntoTemporal = useCallback((latlng: LatLng) => {
    setPuntosTemporales((prev) => [...prev, latlng]);
  }, []);

  // Funci贸n useCallback para finalizar la creaci贸n de un pol铆gono
  const finalizarPoligono = useCallback(() => {
    // Verifica si hay al menos 3 puntos temporales para formar un pol铆gono
    if (puntosTemporales.length > 2) {
      const nuevoPoligono: Poligono = {
        id: Date.now(), // Genera un ID 煤nico basado en la marca de tiempo actual
        vertices: [...puntosTemporales, puntosTemporales[0]] // Cierra el pol铆gono agregando el primer punto al final
      };
      // Actualiza el estado de los pol铆gonos agregando el nuevo pol铆gono a la lista
      setPoligonos((prev) => [...prev, nuevoPoligono]);
    }
    // Limpia la lista de puntos temporales para la creaci贸n del siguiente pol铆gono
    setPuntosTemporales([]);
    // Desselecciona cualquier pol铆gono previamente seleccionado
    setPoligonoSeleccionado(null);
  }, [puntosTemporales]);

  // Funci贸n useCallback para seleccionar un pol铆gono por su ID
  const seleccionarPoligono = useCallback((id: number) => {
    setPoligonoSeleccionado(id);
  }, []);

  // Funci贸n useCallback para eliminar el pol铆gono actualmente seleccionado
  const eliminarPoligonoSeleccionado = useCallback(() => {
    if (poligonoSeleccionado !== null) {
      // Filtra la lista de pol铆gonos, manteniendo solo aquellos cuyo ID no coincide con el pol铆gono seleccionado
      setPoligonos(prev => prev.filter(p => p.id !== poligonoSeleccionado));
      // Desselecciona el pol铆gono
      setPoligonoSeleccionado(null);
      // Limpia los puntos temporales
      setPuntosTemporales([]);
    }
  }, [poligonoSeleccionado]);

  // Funci贸n useCallback para actualizar la posici贸n de un v茅rtice de un pol铆gono
  const actualizarVertice = useCallback((poligonoId: number, verticeIndex: number, newLatLng: LatLng) => {
    // Actualiza el estado de los pol铆gonos mapeando la lista y modificando el pol铆gono correspondiente
    setPoligonos(prev =>
      prev.map(poligono => {
        if (poligono.id === poligonoId) {
          const nuevosVertices = [...poligono.vertices];
          nuevosVertices[verticeIndex] = newLatLng;

          // Si se mueve el primer v茅rtice, tambi茅n se actualiza el 煤ltimo para mantener el pol铆gono cerrado
          if (verticeIndex === 0) {
            nuevosVertices[nuevosVertices.length - 1] = newLatLng;
          }

          return { ...poligono, vertices: nuevosVertices };
        }
        return poligono;
      })
    );
  }, []);

  // Funci贸n useCallback que se llama al hacer clic en el mapa para agregar un punto temporal
  const handleMapClick = useCallback((latlng: LatLng) => {
    agregarPuntoTemporal(latlng);
  }, [agregarPuntoTemporal]);

  // Funci贸n useCallback que se llama al hacer clic derecho en el mapa para finalizar el pol铆gono actual
  const handleMapRightClick = useCallback(() => {
    finalizarPoligono();
  }, [finalizarPoligono]);

  // Funci贸n para seleccionar un pol铆gono y abrir su Popup
  const handleSeleccionarPoligono = (id: number) => {
    seleccionarPoligono(id);
    // Encuentra el 铆ndice del pol铆gono seleccionado en la lista
    const index = poligonos.findIndex(p => p.id === id);
    // Obtiene la referencia al Popup del pol铆gono
    const ref = popupRefs.current[index];
    // Si la referencia y el mapa asociado al Popup existen, abre el Popup
    if (ref && ref._map) {
      ref.openOn(ref._map);
    }
  };

  // Funci贸n para convertir un array de v茅rtices (LatLng) a un objeto turf.js Polygon
  const convertirAPoligonoTurf = (vertices: LatLng[]): turf.helpers.Feature<turf.helpers.Polygon> => {
    // Mapea los v茅rtices para convertirlos al formato [longitude, latitude] requerido por turf.js
    const coords = vertices.map(vertice => [vertice.lng, vertice.lat]);
    // Crea un objeto turf.js Polygon con las coordenadas
    return turf.polygon([coords]);
  };

  return (
    <>
      {/* LayerGroup para contener todos los pol铆gonos y otros elementos relacionados */}
      <LayerGroup ref={layerGroupRef}>
        {/* Mapea la lista de pol铆gonos para renderizar cada uno */}
        {poligonos.map((poligono, index) => {
          // Determina si el pol铆gono actual est谩 seleccionado
          const esSeleccionado = poligonoSeleccionado === poligono.id;

          return (
            <React.Fragment key={`poligono-${poligono.id}`}>
              {/* Renderiza el componente Polygon de react-leaflet */}
              <Polygon
                positions={poligono.vertices} // Pasa los v茅rtices del pol铆gono
                color={esSeleccionado ? "red" : "blue"} // Cambia el color del borde si est谩 seleccionado
                fillOpacity={0.3} // Define la opacidad del relleno
                eventHandlers={{
                  click: () => handleSeleccionarPoligono(poligono.id) // Asocia un evento de clic para seleccionar el pol铆gono
                }}
              >
                {/* Renderiza un Popup asociado al pol铆gono */}
                <Popup ref={(ref) => popupRefs.current[index] = ref}>
                  <div style={{ padding: '10px' }}>
                    <h4 style={{ marginTop: 0 }}>Pol铆gono {index + 1}</h4>
                    {/* Bot贸n para eliminar el pol铆gono */}
                    <button
                      onClick={(e) => {
                        e.stopPropagation(); // Evita que el clic en el bot贸n se propague al pol铆gono subyacente
                        setPoligonoAEliminar(poligono.id); // Guarda el ID del pol铆gono a eliminar
                        setMostrarConfirmacion(true); // Muestra el cuadro de confirmaci贸n
                      }}
                      style={{
                        padding: '8px 16px',
                        backgroundColor: '#ff4444',
                        color: 'white',
                        border: 'none',
                        borderRadius: '4px',
                        cursor: 'pointer',
                        marginBottom: '10px'
                      }}
                    >
                      Eliminar Pol铆gono
                    </button>
                    {/* Bot贸n para agregar un punto de monitoreo al pol铆gono */}
                    <button
                      onClick={() => setMostrarPunto(index)}
                      style={{
                        padding: '8px 16px',
                        backgroundColor: '#4CAF50',
                        color: 'white',
                        border: 'none',
                        borderRadius: '4px',
                        cursor: 'pointer',
                        marginRight: '10px',
                        marginBottom: '10px'
                      }}
                    >
                      Agregar Punto Monitoreo
                    </button>
                  </div>
                </Popup>
              </Polygon>

              {/* Renderiza el componente PuntoMonitoreadoPoligono si mostrarPunto coincide con el 铆ndice del pol铆gono */}
              {mostrarPunto === index && (
                <PuntoMonitoreadoPoligono
                  layerGroupRef={layerGroupRef}
                  buffer={convertirAPoligonoTurf(poligono.vertices)} // Pasa el pol铆gono convertido a formato turf.js como buffer
                />
              )}

              {/* Si el pol铆gono est谩 seleccionado, renderiza marcadores en cada v茅rtice para permitir la edici贸n */}
              {esSeleccionado && poligono.vertices.map((vertice, verticeIndex) => (
                <Marker
                  key={`vertice-${poligono.id}-${verticeIndex}`}
                  position={[vertice.lat, vertice.lng]}
                  icon={vertexIcon} // Usa el icono personalizado para los v茅rtices
                  draggable // Permite que el marcador sea arrastrable
                  eventHandlers={{
                    dragend: (e) => {
                      // Llama a la funci贸n para actualizar la posici贸n del v茅rtice al finalizar el arrastre
                      actualizarVertice(poligono.id, verticeIndex, e.target.getLatLng());
                    }
                  }}
                />
              ))}
            </React.Fragment>
          );
        })}
      </LayerGroup>

      {/* Renderiza una Polyline para mostrar la l铆nea temporal mientras se dibuja un nuevo pol铆gono */}
      {puntosTemporales.length > 1 && (
        <Polyline
          positions={puntosTemporales}
          color="red"
          dashArray="4" // Estilo de l铆nea punteada
        />
      )}

      {/* Componente MapEvents para manejar los clics en el mapa */}
      <MapEvents
        onMapClick={handleMapClick}
        onMapRightClick={handleMapRightClick}
      />

      {/* Renderiza un cuadro de confirmaci贸n para la eliminaci贸n del pol铆gono */}
      {mostrarConfirmacion && (
        <div style={{
          position: 'fixed',
          top: 0, left: 0, right: 0, bottom: 0,
          backgroundColor: 'rgba(0,0,0,0.5)',
          display: 'flex', justifyContent: 'center', alignItems: 'center',
          zIndex: 1000
        }}>
          <div style={{
            backgroundColor: 'white',
            padding: '20px',
            borderRadius: '8px',
            width: '300px',
            boxShadow: '0 2px 10px rgba(0,0,0,0.3)'
          }}>
            <h3>¿Est谩s seguro?</h3>
            <p>¿Deseas eliminar el pol铆gono seleccionado?</p>
            <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '20px' }}>
              {/* Bot贸n para cancelar la eliminaci贸n */}
              <button
                onClick={() => {
                  setMostrarConfirmacion(false);
                  setPoligonoAEliminar(null);
                }}
                style={{ marginRight: '10px', padding: '6px 12px' }}
              >
                Cancelar
              </button>
              {/* Bot贸n para confirmar y eliminar el pol铆gono */}
              <button
                onClick={() => {
                  seleccionarPoligono(poligonoAEliminar!);
                  eliminarPoligonoSeleccionado();
                  setMostrarConfirmacion(false);
                  setPoligonoAEliminar(null);
                }}
                style={{
                  backgroundColor: '#ff4444',
                  color: 'white',
                  padding: '6px 12px',
                  border: 'none',
                  borderRadius: '4px',
                  cursor: 'pointer'
                }}
              >
                Eliminar
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

export default ManejoPoligono;

siguiente componente 

import React, { useState, useEffect, useRef } from "react"; // Importa React y los hooks useState, useEffect y useRef
import { useMapEvents } from "react-leaflet"; // Hook para acceder a los eventos del mapa de Leaflet
import L from "leaflet"; // Importa la biblioteca principal de Leaflet
import icon from "./icon"; // Importa el icono personalizado para los marcadores (asumiendo que existe un archivo llamado "icon" en la misma carpeta)
import * as turf from "@turf/turf"; // Importa la biblioteca turf.js para an谩lisis espacial

// Define la interfaz para las coordenadas de latitud y longitud
interface Coordenadas {
  lat: number;
  lng: number;
}

// Define la interfaz para las propiedades del componente PuntoMonitoreadoProps
interface PuntoMonitoreadoProps {
  layerGroupRef: React.RefObject<L.LayerGroup>; // Referencia mutable a un grupo de capas de Leaflet
  buffer: turf.helpers.Feature<turf.helpers.Polygon | turf.helpers.MultiPolygon>; // Objeto GeoJSON que representa un buffer (pol铆gono o multipol铆gono)
}

/**
 * Funci贸n para corregir la geometr铆a de un buffer, asegurando que el primer y 煤ltimo punto de cada anillo sean iguales
 * para formar pol铆gonos cerrados v谩lidos.
 * @param buffer Objeto GeoJSON del buffer a corregir.
 * @returns Objeto GeoJSON del buffer corregido o null si la geometr铆a no es v谩lida.
 */
function corregirGeometria(
  buffer: turf.helpers.Feature<turf.helpers.Polygon | turf.helpers.MultiPolygon>
): turf.helpers.Feature<turf.helpers.Polygon | turf.helpers.MultiPolygon> | null {
  // Verifica si el buffer tiene geometr铆a y coordenadas definidas
  if (!buffer.geometry || !buffer.geometry.coordinates) {
    console.error("Buffer no tiene geometr铆a o coordenadas definidas.");
    return null;
  }

  // Obtiene el tipo de geometr铆a del buffer
  const tipo = buffer.geometry.type;

  // Procesa si la geometr铆a es un Pol铆gono
  if (tipo === "Polygon") {
    // Itera sobre cada anillo (exterior e interior) del pol铆gono
    const coords = buffer.geometry.coordinates.map(anillo => {
      // Obtiene el primer y 煤ltimo punto del anillo
      const primero = anillo[0];
      const ultimo = anillo[anillo.length - 1];
      // Verifica si el primer y 煤ltimo punto son diferentes
      if (primero[0] !== ultimo[0] || primero[1] !== ultimo[1]) {
        // Si son diferentes, agrega el primer punto al final para cerrar el pol铆gono
        return [...anillo, primero];
      }
      // Si ya est谩n cerrados, devuelve el anillo sin cambios
      return anillo;
    });
    // Crea un nuevo objeto GeoJSON de tipo Pol铆gono con las coordenadas corregidas y las propiedades originales
    return turf.polygon(coords, buffer.properties || {});
  }

  // Procesa si la geometr铆a es un MultiPol铆gono
  if (tipo === "MultiPolygon") {
    // Itera sobre cada pol铆gono del MultiPol铆gono
    const coords = buffer.geometry.coordinates.map(poligono =>
      // Itera sobre cada anillo de cada pol铆gono
      poligono.map(anillo => {
        // Obtiene el primer y 煤ltimo punto del anillo
        const primero = anillo[0];
        const ultimo = anillo[anillo.length - 1];
        // Verifica si el primer y 煤ltimo punto son diferentes
        if (primero[0] !== ultimo[0] || primero[1] !== ultimo[1]) {
          // Si son diferentes, agrega el primer punto al final para cerrar el pol铆gono
          return [...anillo, primero];
        }
        // Si ya est谩n cerrados, devuelve el anillo sin cambios
        return anillo;
      })
    );
    // Crea un nuevo objeto GeoJSON de tipo MultiPol铆gono con las coordenadas corregidas y las propiedades originales
    return turf.multiPolygon(coords, buffer.properties || {});
  }

  // Si el tipo de geometr铆a no es Pol铆gono ni MultiPol铆gono, muestra un error
  console.error("Tipo de geometr铆a no soportado.");
  return null;
}

// Componente funcional de React para mostrar y gestionar un punto de monitoreo dentro de un pol铆gono
const PuntoMonitoreadoPoligono: React.FC<PuntoMonitoreadoProps> = ({ layerGroupRef, buffer }) => {
  // Estado para almacenar las coordenadas del punto de monitoreo
  const [coordenadas, setCoordenadas] = useState<Coordenadas | null>(null);
  // Referencia mutable para el marcador de Leaflet
  const markerRef = useRef<L.Marker | null>(null);

  /**
   * Funci贸n para crear un objeto GeoJSON de tipo Pol铆gono a partir de un array de objetos LatLng.
   * Asegura que el pol铆gono tenga al menos 3 puntos y est茅 cerrado.
   * @param puntos Array de objetos con propiedades lat y lng.
   * @returns Objeto GeoJSON de tipo Pol铆gono o null si no se puede crear.
   */
  const crearPoligonoDesdeLatLng = (puntos: { lat: number, lng: number }[]): turf.helpers.Feature<turf.helpers.Polygon> | null => {
    // Verifica si el array de puntos es v谩lido y tiene al menos 3 puntos
    if (!Array.isArray(puntos) || puntos.length < 3) {
      console.error("Se requieren al menos 3 puntos para formar un pol铆gono.");
      return null;
    }

    // Convierte las coordenadas al formato [longitude, latitude] que espera Turf.js
    const coords: [number, number][] = puntos.map(p => [p.lng, p.lat]);

    // Asegura que el pol铆gono est茅 cerrado (el primer punto debe ser igual al 煤ltimo)
    const primero = coords[0];
    const ultimo = coords[coords.length - 1];
    if (primero[0] !== ultimo[0] || primero[1] !== ultimo[1]) {
      coords.push(primero); // Agrega el primer punto al final para cerrar el anillo
    }

    // Crea el pol铆gono como un objeto Feature de Turf.js
    return turf.polygon([coords]);
  }

  // Hook para manejar eventos del mapa (doble clic y clic derecho)
  useMapEvents({
    // Evento de doble clic en el mapa
    dblclick: (e) => {
      // Establece las coordenadas del clic como las coordenadas del punto de monitoreo
      setCoordenadas(e.latlng);
    },
    // Evento de clic con el bot贸n derecho del rat贸n (men煤 contextual)
    contextmenu: (e) => {
      // Evita el comportamiento predeterminado del men煤 contextual del navegador
      e.originalEvent.preventDefault();
      // Establece las coordenadas del clic como las coordenadas del punto de monitoreo
      setCoordenadas(e.latlng);
    }
  });

  // Hook useEffect que se ejecuta cuando cambian las coordenadas, la referencia al grupo de capas o el buffer
  useEffect(() => {
    // Verifica si hay coordenadas, una referencia v谩lida al grupo de capas y un buffer
    if (coordenadas && layerGroupRef.current && buffer) {
      // Si ya existe un marcador, lo elimina del grupo de capas
      if (markerRef.current) {
        layerGroupRef.current.removeLayer(markerRef.current);
      }

      // Crea un nuevo marcador de Leaflet en las coordenadas actuales
      const marker = L.marker([coordenadas.lat, coordenadas.lng], {
        icon: icon, // Asigna el icono personalizado al marcador
        draggable: true // Permite que el marcador sea arrastrable
      });

      // Agrega el marcador al grupo de capas
      layerGroupRef.current.addLayer(marker);
      // Actualiza la referencia al marcador
      markerRef.current = marker;

      // Crea un objeto turf.js de tipo Punto con las coordenadas
      const punto = turf.point([coordenadas.lng, coordenadas.lat]);
      // Corrige la geometr铆a del buffer utilizando la funci贸n corregirGeometria
      const bufferCorregido = corregirGeometria(buffer);
      // Variable para almacenar el mensaje de estado (dentro o fuera del buffer)
      let mensajeEstado = "";

      // Verifica si el buffer corregido es v谩lido
      if (bufferCorregido) {
        // Utiliza turf.booleanPointInPolygon para verificar si el punto est谩 dentro del buffer
        const dentro = turf.booleanPointInPolygon(punto, bufferCorregido);
        // Establece el mensaje de estado basado en si el punto est谩 dentro o fuera
        mensajeEstado = dentro
          ? "✅ El punto est谩 <b>dentro</b> del buffer."
          : "❌ El punto est谩 <b>fuera</b> del buffer.";
        // Imprime el estado en la consola (sin etiquetas HTML)
        console.log(mensajeEstado.replace(/<[^>]*>/g, ""));
      } else {
        // Si el buffer no es v谩lido, establece un mensaje de error
        mensajeEstado = "⚠️ Buffer no v谩lido.";
        console.error(mensajeEstado);
      }

      // Define el contenido del popup que se mostrar谩 al hacer clic en el marcador
      const popupContent = `
        <div>
          <p><b>Ubicaci贸n:</b> ${coordenadas.lat.toFixed(5)}, ${coordenadas.lng.toFixed(5)}</p>
          <p>${mensajeEstado}</p>
          <button id="eliminar-marcador" style="padding: 5px 10px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer;">Eliminar</button>
        </div>
      `;
      // Asocia el popup al marcador y lo abre inicialmente
      marker.bindPopup(popupContent).openPopup();

      // Agrega un evento 'dragend' al marcador para actualizar las coordenadas cuando se arrastra
      marker.on("dragend", (e) => {
        const position = e.target.getLatLng();
        setCoordenadas({ lat: position.lat, lng: position.lng });
      });

      // Agrega un evento 'popupopen' al marcador para manejar el bot贸n de eliminar dentro del popup
      marker.on("popupopen", () => {
        // Obtiene la referencia al bot贸n de eliminar por su ID
        const btn = document.getElementById("eliminar-marcador");
        // Si el bot贸n existe
        if (btn) {
          // Define la funci贸n que se ejecutar谩 al hacer clic en el bot贸n de eliminar
          btn.onclick = () => {
            // Verifica si la referencia al marcador actual es v谩lida
            if (markerRef.current) {
              // Elimina el marcador del grupo de capas
              layerGroupRef.current?.removeLayer(markerRef.current);
              // Limpia la referencia al marcador
              markerRef.current = null;
              // Limpia el estado de las coordenadas
              setCoordenadas(null);
            }
          };
        }
      });
    }
    // El useEffect se ejecutar谩 cada vez que cambien las coordenadas, la referencia al grupo de capas o el buffer
  }, [coordenadas, layerGroupRef, buffer]);

  // El componente no renderiza nada visualmente por s铆 mismo, ya que manipula directamente las capas del mapa
  return null;
};

// Exporta el componente PuntoMonitoreadoPoligono para su uso en otras partes de la aplicaci贸n
export default PuntoMonitoreadoPoligono;


siguiente componente

import React, { useState, useCallback } from "react"; // Importa React y los hooks useState y useCallback
import { useMapEvents, Marker, Popup } from "react-leaflet"; // Importa hooks y componentes espec铆ficos de react-leaflet
import icon from "./icon"; // Importa el icono personalizado para los marcadores (asumiendo que existe un archivo llamado "icon" en la misma carpeta)

// Componente funcional MapEvents que utiliza el hook useMapEvents para manejar eventos del mapa
const MapEvents = ({ onMapClick }) => {
  // Utiliza el hook useMapEvents para adjuntar listeners a los eventos del mapa
  useMapEvents({
    click: (e) => { // Define una funci贸n que se ejecuta cuando se hace clic en el mapa
      onMapClick(e.latlng); // Llama a la funci贸n onMapClick proporcionada por las props, pasando las coordenadas del clic
    }
  });
  return null; // Este componente no renderiza nada visualmente, solo maneja eventos del mapa
};

// Componente funcional ManejoMarcadores que gestiona la creaci贸n, visualizaci贸n, edici贸n y eliminaci贸n de marcadores en el mapa
const ManejoMarcadores = () => {
  // Estado para almacenar la lista de datos geogr谩ficos (cada elemento representa un marcador)
  const [datosGeograficos, setDatosGeograficos] = useState([]);

  // Funci贸n useCallback para agregar un nuevo marcador a la lista de datos geogr谩ficos
  const agregarMarcador = useCallback((latlng) => {
    setDatosGeograficos((prev) => [ // Actualiza el estado de datosGeograficos
      ...prev, // Mantiene los marcadores existentes
      { // Crea un nuevo objeto para el marcador
        id: Date.now(), // Genera un ID 煤nico basado en la marca de tiempo actual
        lat: parseFloat(latlng.lat.toFixed(6)), // Extrae y formatea la latitud a 6 decimales
        lng: parseFloat(latlng.lng.toFixed(6)) // Extrae y formatea la longitud a 6 decimales
      }
    ]);
  }, []); // La funci贸n agregarMarcador solo depende de s铆 misma, por lo que el array de dependencias est谩 vac铆o

  // Funci贸n useCallback para eliminar un marcador de la lista de datos geogr谩ficos por su ID
  const eliminarMarcador = useCallback((id) => {
    setDatosGeograficos(prev => prev.filter(marker => marker.id !== id)); // Filtra la lista, manteniendo solo los marcadores cuyo ID no coincide con el ID proporcionado
  }, []); // La funci贸n eliminarMarcador solo depende de s铆 misma

  // Funci贸n useCallback para actualizar la posici贸n de un marcador existente en la lista de datos geogr谩ficos
  const actualizarPosicionMarcador = useCallback((id, latlng) => {
    setDatosGeograficos(prev => // Actualiza el estado de datosGeograficos
      prev.map(marker => // Mapea la lista de marcadores
        marker.id === id // Verifica si el ID del marcador actual coincide con el ID proporcionado
          ? { // Si coincide, crea un nuevo objeto de marcador con la posici贸n actualizada
              ...marker,
              lat: parseFloat(latlng.lat.toFixed(6)), // Actualiza la latitud
              lng: parseFloat(latlng.lng.toFixed(6)) // Actualiza la longitud
            }
          : marker // Si no coincide, mantiene el marcador existente sin cambios
      )
    );
  }, []); // La funci贸n actualizarPosicionMarcador solo depende de s铆 misma

  // Funci贸n useCallback que se llama cuando se hace clic en el mapa (a trav茅s del componente MapEvents)
  const handleMapClick = useCallback((latlng) => {
    agregarMarcador(latlng); // Llama a la funci贸n para agregar un nuevo marcador en las coordenadas del clic
    console.log("Nuevo marcador agregado:", latlng); // Imprime un mensaje en la consola con las coordenadas del nuevo marcador
  }, [agregarMarcador]); // La funci贸n handleMapClick depende de la funci贸n agregarMarcador

  // Funci贸n useCallback que se llama cuando se finaliza el arrastre de un marcador
  const handleDragEnd = useCallback((markerId) => (e) => {
    actualizarPosicionMarcador(markerId, e.target.getLatLng()); // Llama a la funci贸n para actualizar la posici贸n del marcador con el ID proporcionado a las nuevas coordenadas
  }, [actualizarPosicionMarcador]); // La funci贸n handleDragEnd depende de la funci贸n actualizarPosicionMarcador

  // Funci贸n useCallback que se llama cuando se hace clic en el bot贸n de eliminar dentro del Popup de un marcador
  const handleDeleteMarker = useCallback((markerId, e) => {
    e.stopPropagation(); // Evita que el evento de clic se propague al marcador subyacente
    eliminarMarcador(markerId); // Llama a la funci贸n para eliminar el marcador con el ID proporcionado
  }, [eliminarMarcador]); // La funci贸n handleDeleteMarker depende de la funci贸n eliminarMarcador

  return (
    <>
      {/* Mapea la lista de datosGeograficos para renderizar un componente Marker por cada elemento */}
      {datosGeograficos.map((marker) => (
        <Marker
          key={marker.id} // Propiedad key requerida por React para la renderizaci贸n eficiente de listas
          position={[marker.lat, marker.lng]} // Define la posici贸n del marcador utilizando la latitud y longitud del dato geogr谩fico
          icon={icon} // Asigna el icono personalizado al marcador
          draggable // Permite que el marcador sea arrastrable por el usuario
          eventHandlers={{
            dragend: handleDragEnd(marker.id) // Asocia la funci贸n handleDragEnd al evento 'dragend' (cuando se suelta el marcador despu茅s de arrastrar), pasando el ID del marcador
          }}
        >
          {/* Renderiza un componente Popup asociado al marcador (se muestra al hacer clic en el marcador) */}
          <Popup>
            <div>
              <p>Latitud: {marker.lat.toFixed(13)}</p> {/* Muestra la latitud con 13 decimales */}
              <p>Longitud: {marker.lng.toFixed(13)}</p> {/* Muestra la longitud con 13 decimales */}
              {/* Bot贸n para eliminar el marcador */}
              <button onClick={(e) => handleDeleteMarker(marker.id, e)}>
                Eliminar marcador
              </button>
            </div>
          </Popup>
        </Marker>
      ))}

      {/* Renderiza el componente MapEvents para habilitar la captura de clics en el mapa */}
      <MapEvents onMapClick={handleMapClick} />
    </>
  );
};

export default ManejoMarcadores;

siguiente componente

import React, { useRef, useState, createContext, useContext, useCallback } from "react";
import { TileLayer, useMapEvents, Marker, Popup, Circle, LayerGroup } from "react-leaflet";
import L from "leaflet";
import icon from "./icon";
import iconAgarradera from "./iconagarradera";
import "leaflet-geometryutil";
import PuntoMonitoreadoCirculo from "./PuntoMonitoreadoCirculo";

// 馃幆 Contexto para manejar los datos geogr谩ficos relacionados con los c铆rculos
const DatosGeograficosContext = createContext();

// Hook personalizado para acceder f谩cilmente al contexto de datos geogr谩ficos
export const useDatosGeograficos = () => {
  const context = useContext(DatosGeograficosContext);
  if (!context) {
    throw new Error("useDatosGeograficos debe ser usado dentro de un DatosGeograficosProvider");
  }
  return context;
};

// Funci贸n utilitaria para redondear n煤meros a una precisi贸n espec铆fica
const roundToPrecision = (value, decimals = 3) => {
  return parseFloat(value.toFixed(decimals));
};

// 馃摝 Proveedor del contexto de datos geogr谩ficos
const DatosGeograficosProvider = ({ children }) => {
  // Estado para almacenar la lista de datos geogr谩ficos (cada elemento representa un c铆rculo)
  const [datosGeograficos, setDatosGeograficos] = useState([]);

  // Funci贸n useCallback para agregar un nuevo marcador (que representa el centro de un c铆rculo)
  const agregarMarcador = useCallback((latlng) => {
    setDatosGeograficos((prev) => [
      ...prev,
      {
        id: Date.now(), // Genera un ID 煤nico para el marcador
        lat: roundToPrecision(latlng.lat, 6), // Latitud del centro del c铆rculo, redondeada a 6 decimales
        lng: roundToPrecision(latlng.lng, 6), // Longitud del centro del c铆rculo, redondeada a 6 decimales
        radio: 500 // Radio inicial del c铆rculo en metros
      }
    ]);
  }, []); // La funci贸n agregarMarcador solo depende de s铆 misma

  // Funci贸n useCallback para eliminar un marcador (y su c铆rculo asociado) por su ID
  const eliminarMarcador = useCallback((id) => {
    setDatosGeograficos(prev => prev.filter(marker => marker.id !== id)); // Filtra la lista, manteniendo solo los marcadores cuyo ID no coincide con el ID proporcionado
  }, []); // La funci贸n eliminarMarcador solo depende de s铆 misma

  // Funci贸n useCallback para actualizar la posici贸n del centro de un c铆rculo
  const actualizarPosicionMarcador = useCallback((id, latlng) => {
    setDatosGeograficos(prev =>
      prev.map(marker =>
        marker.id === id ? { // Si el ID del marcador coincide
          ...marker,
          lat: roundToPrecision(latlng.lat, 6), // Actualiza la latitud del centro
          lng: roundToPrecision(latlng.lng, 6) // Actualiza la longitud del centro
        } : marker // Si no coincide, mantiene el marcador sin cambios
      )
    );
  }, []); // La funci贸n actualizarPosicionMarcador solo depende de s铆 misma

  // Funci贸n useCallback para actualizar el radio de un c铆rculo
  const actualizarRadioMarcador = useCallback((id, radio) => {
    setDatosGeograficos(prev =>
      prev.map(marker =>
        marker.id === id ? { // Si el ID del marcador coincide
          ...marker,
          radio: roundToPrecision(radio) // Actualiza el radio del c铆rculo
        } : marker // Si no coincide, mantiene el marcador sin cambios
      )
    );
  }, []); // La funci贸n actualizarRadioMarcador solo depende de s铆 misma

  // Objeto que contiene los valores y las funciones que se proporcionar谩n al contexto
  const value = {
    datosGeograficos,
    agregarMarcador,
    eliminarMarcador,
    actualizarPosicionMarcador,
    actualizarRadioMarcador
  };

  // Proporciona el contexto a los componentes hijos
  return (
    <DatosGeograficosContext.Provider value={value}>
      {children}
    </DatosGeograficosContext.Provider>
  );
};

// Componente funcional MapEvents para capturar clics en el mapa y llamar a la funci贸n onMapClick
const MapEvents = ({ onMapClick }) => {
  useMapEvents({
    click: (e) => {
      onMapClick(e.latlng); // Llama a la funci贸n onMapClick proporcionada por las props con las coordenadas del clic
    }
  });
  return null; // Este componente no renderiza nada visualmente
};

// 馃幆 Funci贸n utilitaria para calcular un punto en el borde de un c铆rculo dado su centro y radio
const calcularPuntoBorde = (lat, lng, radio) => {
  if (!lat || !lng || !radio) return L.latLng(0, 0); // Devuelve un LatLng(0, 0) si faltan datos
  const centro = L.latLng(lat, lng); // Crea un objeto LatLng para el centro del c铆rculo
  // Utiliza la funci贸n 'destination' de leaflet-geometryutil para calcular un punto a una distancia y direcci贸n dadas
  return L.GeometryUtil.destination(centro, 90, radio); // 90 grados corresponden al Este
};

// Componente funcional ManejoCircunferencia que gestiona la visualizaci贸n y la interacci贸n con los c铆rculos
const ManejoCircunferencia = () => {
  // Accede a las funciones y al estado del contexto de datos geogr谩ficos
  const {
    datosGeograficos,
    agregarMarcador,
    eliminarMarcador,
    actualizarPosicionMarcador,
    actualizarRadioMarcador
  } = useDatosGeograficos();

  // Estado para controlar la visibilidad del componente PuntoMonitoreadoCirculo para un c铆rculo espec铆fico
  const [mostrarPunto, setMostrarPunto] = useState<number | null>(null);
  // Referencia mutable para el grupo de capas donde se renderizar谩n los c铆rculos y sus elementos
  const layerGroupRef = useRef<L.LayerGroup | null>(null);

  // Funci贸n useCallback para agregar un nuevo marcador (centro del c铆rculo) al hacer clic en el mapa
  const handleMapClick = useCallback((latlng) => {
    agregarMarcador(latlng);
    console.log("Nuevo marcador agregado:", latlng);
  }, [agregarMarcador]);

  // Funci贸n useCallback para actualizar la posici贸n del centro del c铆rculo al finalizar el arrastre del marcador central
  const handleDragEnd = useCallback((markerId) => (e) => {
    actualizarPosicionMarcador(markerId, e.target.getLatLng());
  }, [actualizarPosicionMarcador]);

  // Funci贸n useCallback para eliminar un c铆rculo al hacer clic en el bot贸n de eliminar en su popup
  const handleDeleteMarker = useCallback((markerId, e) => {
    e.stopPropagation(); // Evita que el evento de clic se propague al marcador subyacente
    eliminarMarcador(markerId);
  }, [eliminarMarcador]);

  // Funci贸n useCallback para actualizar el radio del c铆rculo al cambiar el valor del input en el popup
  const handleRadioChange = useCallback((markerId) => (e) => {
    const nuevoRadio = parseFloat(e.target.value);
    if (!isNaN(nuevoRadio) && nuevoRadio > 0) {
      actualizarRadioMarcador(markerId, nuevoRadio);
    }
  }, [actualizarRadioMarcador]);

  // Funci贸n useCallback para actualizar el radio del c铆rculo al finalizar el arrastre del marcador de agarre
  const handleDragRadioEnd = useCallback((marker) => (e) => {
    const centro = L.latLng(marker.lat, marker.lng); // Obtiene las coordenadas del centro del c铆rculo
    const borde = e.target.getLatLng(); // Obtiene las coordenadas del punto de agarre arrastrado
    const nuevoRadio = centro.distanceTo(borde); // Calcula la distancia entre el centro y el punto de agarre (nuevo radio)

    console.log(`Radio calculado: ${nuevoRadio}`);
    actualizarRadioMarcador(marker.id, nuevoRadio);

    // Verificaci贸n de consistencia (opcional, para depuraci贸n)
    const puntoBordeCalculado = calcularPuntoBorde(marker.lat, marker.lng, nuevoRadio);
    const distanciaVerificacion = centro.distanceTo(puntoBordeCalculado);
    console.log(`Verificaci贸n - Radio: ${nuevoRadio}, Distancia: ${distanciaVerificacion}`);
  }, [actualizarRadioMarcador]);

  return (
    <>
      <LayerGroup ref={layerGroupRef}>
        {datosGeograficos.map((marker, index) => {
          // Calcula un punto en el borde del c铆rculo para colocar el marcador de agarre
          const puntoBorde = calcularPuntoBorde(marker.lat, marker.lng, marker.radio);
          const centro = L.latLng(marker.lat, marker.lng);
          const distanciaVerificacion = centro.distanceTo(puntoBorde);

          console.log(`Marcador ${marker.id} - Radio: ${marker.radio}, Distancia verificada: ${distanciaVerificacion}`);

          return (
            <React.Fragment key={marker.id}>
              {/* Marcador central (para mover todo el c铆rculo) */}
              <Marker
                position={[marker.lat, marker.lng]}
                icon={icon}
                draggable
                eventHandlers={{
                  dragend: handleDragEnd(marker.id)
                }}
              >
                <Popup>
                  <div>
                    <p>Latitud: {marker.lat.toFixed(6)}</p>
                    <p>Longitud: {marker.lng.toFixed(6)}</p>
                    <div>
                      <label>
                        Radio (m):
                        <input
                          type="number"
                          min="1"
                          value={marker.radio}
                          onChange={handleRadioChange(marker.id)}
                          style={{ width: '70px', marginLeft: '5px' }}
                        />
                      </label>
                    </div>
                    <p>Distancia verificada: {distanciaVerificacion.toFixed(3)} m</p>
                    <button onClick={(e) => handleDeleteMarker(marker.id, e)}>
                      Eliminar marcador
                    </button>
                    <button
                      onClick={() => setMostrarPunto(index)}
                      style={{
                        padding: '8px 16px',
                        backgroundColor: '#4CAF50',
                        color: 'white',
                        border: 'none',
                        borderRadius: '4px',
                        cursor: 'pointer',
                        marginTop: '10px'
                      }}
                    >
                      Agregar Punto Monitoreo
                    </button>
                  </div>
                </Popup>
              </Marker>

              {/* C铆rculo principal */}
              <Circle
                center={[marker.lat, marker.lng]}
                radius={marker.radio}
                color="blue"
                fillColor="blue"
                fillOpacity={0.2}
              />

              {/* C铆rculo ligeramente m谩s grande (efecto visual) */}
              <Circle
                center={[marker.lat, marker.lng]}
                radius={marker.radio + 3}
                color="#85c1e9"
                fillColor="#85c1e9"
                fillOpacity={0.2}
              />

              {/* Marcador de agarre para cambiar el radio */}
              <Marker
                position={[puntoBorde.lat, puntoBorde.lng]}
                draggable
                icon={iconAgarradera}
                eventHandlers={{
                  dragend: handleDragRadioEnd(marker)
                }}
              />

              {/* Componente para manejar el punto de monitoreo dentro del c铆rculo */}
              {mostrarPunto === index && (
                <PuntoMonitoreadoCirculo
                  layerGroupRef={layerGroupRef}
                  buffer={{
                    center: [marker.lat, marker.lng],
                    radius: marker.radio,
                    borde : [puntoBorde.lat, puntoBorde.lng]
                  }}
                />
              )}
            </React.Fragment>
          );
        })}
      </LayerGroup>

      <MapEvents onMapClick={handleMapClick} />
    </>
  );
};

// 馃摝 Componente proveedor que envuelve la funcionalidad de manejo de c铆rculos con el proveedor de datos
export const CirculoProvider = ({ children }) => (
  <DatosGeograficosProvider>
    {children}
    <ManejoCircunferencia />
  </DatosGeograficosProvider>
);

export default CirculoProvider;

siguiente codigo  

import React, { useState, useEffect, useRef } from "react"; // Importa React y los hooks useState, useEffect y useRef
import { useMapEvents } from "react-leaflet"; // Hook para acceder a los eventos del mapa de Leaflet
import L from "leaflet"; // Importa la biblioteca principal de Leaflet
import icon from "./icon"; // Importa el icono personalizado para los marcadores
import * as turf from "@turf/turf"; // Importa la biblioteca turf.js para an谩lisis espacial

// Define la interfaz para las coordenadas de latitud y longitud
interface Coordenadas {
  lat: number;
  lng: number;
}

// Define la interfaz para las propiedades del componente PuntoMonitoreadoProps
interface PuntoMonitoreadoProps {
  layerGroupRef: React.RefObject<L.LayerGroup>; // Referencia mutable a un grupo de capas de Leaflet
  buffer: {
    center: [number, number]; // [lng, lat] - Coordenadas del centro del c铆rculo
    borde: [number, number];  // [lng, lat] - Coordenadas de un punto en el borde del c铆rculo
    radius: number;          // Radio del c铆rculo (redundante ya que se puede calcular con centro y borde)
  };
  onRadiusChange?: (newRadius: number) => void; // Propiedad opcional para pasar una funci贸n que se llama cuando cambia el radio
}

// Componente funcional PuntoMonitoreadoCirculo para gestionar un punto de monitoreo dentro de un c铆rculo
const PuntoMonitoreadoCirculo: React.FC<PuntoMonitoreadoProps> = ({
  layerGroupRef,
  buffer,
  onRadiusChange
}) => {
  // Estado para almacenar las coordenadas del punto de monitoreo
  const [coordenadas, setCoordenadas] = useState<Coordenadas | null>(null);
  // Estado para controlar la visibilidad del input para modificar el radio
  const [showRadiusInput, setShowRadiusInput] = useState(false);
  // Estado para almacenar el nuevo valor del radio antes de aplicarlo
  const [newRadius, setNewRadius] = useState(buffer.radius);
  // Referencia mutable para el marcador de Leaflet del punto de monitoreo
  const markerRef = useRef<L.Marker | null>(null);

  // Hook para manejar eventos del mapa (doble clic y clic derecho) para establecer la ubicaci贸n del punto de monitoreo
  useMapEvents({
    dblclick: (e) => {
      setCoordenadas({ lat: e.latlng.lat, lng: e.latlng.lng });
    },
    contextmenu: (e) => {
      e.originalEvent.preventDefault(); // Evita el men煤 contextual del navegador
      setCoordenadas({ lat: e.latlng.lat, lng: e.latlng.lng });
    }
  });

  // useEffect para actualizar el estado del radio cuando cambia el radio proporcionado en las props del buffer
  useEffect(() => {
    setNewRadius(buffer.radius); // Sincroniza el estado local del radio con el valor del buffer
  }, [buffer.radius]);

  // useEffect principal para gestionar la creaci贸n, actualizaci贸n y eliminaci贸n del marcador del punto de monitoreo
  useEffect(() => {
    // Si no hay coordenadas o la referencia al grupo de capas no es v谩lida, no hacer nada
    if (!coordenadas || !layerGroupRef.current) return;

    // Limpieza: eliminar el marcador anterior y sus listeners si existen
    if (markerRef.current) {
      markerRef.current.off(); // Elimina todos los listeners del marcador anterior
      layerGroupRef.current.removeLayer(markerRef.current); // Elimina el marcador del mapa
      markerRef.current = null; // Resetea la referencia al marcador
    }

    // Crear un nuevo marcador en las coordenadas seleccionadas
    const marker = L.marker([coordenadas.lat, coordenadas.lng], {
      icon: icon, // Asigna el icono personalizado
      draggable: true // Permite que el marcador sea arrastrable
    });

    markerRef.current = marker; // Guarda la referencia al nuevo marcador
    layerGroupRef.current.addLayer(marker); // Agrega el marcador al grupo de capas del mapa

    // Turf.js para c谩lculos geom茅tricos
    const punto = turf.point([coordenadas.lat, coordenadas.lng]); // Crea un punto turf desde las coordenadas del marcador
    const centro = turf.point(buffer.center); // Crea un punto turf desde el centro del c铆rculo
    const borde = turf.point(buffer.borde); // Crea un punto turf desde un punto en el borde del c铆rculo

    // Calcular distancias usando turf.distance
    const distanciaPuntoCentro = turf.distance(centro, punto, { units: 'meters' }); // Distancia entre el punto de monitoreo y el centro del c铆rculo
    const distanciaBordeCentro = turf.distance(centro, borde, { units: 'meters' }); // Distancia entre el borde del c铆rculo y su centro (radio real)

    console.log("Distancia del punto al centro:", distanciaPuntoCentro.toFixed(2), "m");
    console.log("Distancia del borde al centro (radio real):", distanciaBordeCentro.toFixed(2), "m");
    console.log("Radio proporcionado:", buffer.radius.toFixed(2), "m");

    // Determinar si el punto de monitoreo est谩 dentro del c铆rculo (con una posible tolerancia)
    const estaDentro = distanciaPuntoCentro <= distanciaBordeCentro;

    // Crear el mensaje de estado para el popup
    const mensajeEstado = estaDentro
      ? "✅ El punto est谩 <b>dentro</b> del c铆rculo."
      : "❌ El punto est谩 <b>fuera</b> del c铆rculo.";

    // Contenido del popup del marcador
    const popupContent = `
      <div>
        <p><b>Ubicaci贸n:</b> ${coordenadas.lat.toFixed(5)}, ${coordenadas.lng.toFixed(5)}</p>
        <p><b>Radio actual:</b> ${buffer.radius.toFixed(2)} m </p>
        <p>Este radio tiene 30 m de tolerancia</p>
        <p>${mensajeEstado}</p>
        <div style="margin-top: 10px;">
          <button id="eliminar-marcador" style="padding: 5px 10px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer;">
            Eliminar
          </button>
        </div>
      </div>
    `;

    marker.bindPopup(popupContent).openPopup(); // Asocia el popup al marcador y lo abre

    // Evento de arrastre del marcador para actualizar las coordenadas
    marker.on("dragend", (e) => {
      const nuevaPos = e.target.getLatLng();
      setCoordenadas({ lat: nuevaPos.lat, lng: nuevaPos.lng });
    });

    // Eventos del popup (abrir) para manejar los botones dentro
    marker.on("popupopen", () => {
      const btnModificar = document.getElementById("modificar-radio"); // (Este bot贸n no est谩 en el popup definido)
      const btnEliminar = document.getElementById("eliminar-marcador");

      // (La l贸gica para el bot贸n "modificar-radio" est谩 comentada impl铆citamente ya que el bot贸n no existe en el popup)
      // if (btnModificar) {
      //   btnModificar.onclick = () => {
      //     setShowRadiusInput(true);
      //     marker.closePopup();
      //   };
      // }

      // L贸gica para el bot贸n "eliminar-marcador"
      if (btnEliminar) {
        btnEliminar.onclick = () => {
          if (markerRef.current) {
            markerRef.current.off(); // Elimina los listeners del marcador
            layerGroupRef.current?.removeLayer(markerRef.current); // Elimina el marcador del mapa
            markerRef.current = null; // Resetea la referencia
            setCoordenadas(null); // Limpia las coordenadas
          }
        };
      }
    });

    // Funci贸n de limpieza que se ejecuta al desmontar el componente o antes de que el efecto se vuelva a ejecutar
    return () => {
      if (markerRef.current) {
        markerRef.current.off();
        layerGroupRef.current?.removeLayer(markerRef.current);
        markerRef.current = null;
      }
    };
  }, [coordenadas, buffer, layerGroupRef, showRadiusInput]); // Dependencias del useEffect

  // Handler para el cambio en el input del radio
  const handleRadiusChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setNewRadius(parseFloat(e.target.value));
  };

  // Handler para el env铆o del formulario de modificaci贸n del radio
  const handleRadiusSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (onRadiusChange && newRadius > 0) {
      onRadiusChange(newRadius); // Llama a la funci贸n proporcionada para notificar el cambio de radio
    }
    setShowRadiusInput(false); // Cierra el input del radio
  };

  // Renderiza un input para modificar el radio si showRadiusInput es true
  if (showRadiusInput) {
    return (
      <div className="radius-input-container" style={{
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        zIndex: 1000,
        backgroundColor: 'white',
        padding: '20px',
        borderRadius: '8px',
        boxShadow: '0 2px 10px rgba(0,0,0,0.2)'
      }}>
        <h3 style={{ marginTop: 0 }}>Modificar Radio</h3>
        <form onSubmit={handleRadiusSubmit}>
          <div style={{ marginBottom: '10px' }}>
            <label>
              Nuevo radio (metros):
              <input
                type="text"
                value={newRadius}
                onChange={handleRadiusChange}
                style={{ marginLeft: '10px', padding: '5px' }}
              />
            </label>
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between' }}>
            <button
              type="submit"
              style={{
                padding: '5px 15px',
                backgroundColor: '#4CAF50',
                color: 'white',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer'
              }}
            >
              Aplicar
            </button>
            <button
              type="button"
              onClick={() => setShowRadiusInput(false)}
              style={{
                padding: '5px 15px',
                backgroundColor: '#f44336',
                color: 'white',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer'
              }}
            >
              Cancelar
            </button>
          </div>
        </form>
      </div>
    );
  }

  // Si showRadiusInput es false, no renderizar nada
  return null;
};

export default PuntoMonitoreadoCirculo;


siguiente componente 

import L from "leaflet"; // Importa la biblioteca principal de Leaflet

// Crea un icono personalizado de Leaflet
const Icon = L.icon({
  iconSize: [30, 41],   // [ancho, alto] en p铆xeles del icono
  iconAnchor: [15, 41], // [x, y] en p铆xeles que corresponden a la ubicaci贸n del marcador (parte inferior central)
  popupAnchor: [2, -40], // [x, y] en p铆xeles donde se abrir谩 la ventana emergente relativa al anchor del icono
  iconUrl: "/icons/map_marker.png", // Ruta a la imagen del icono principal
  shadowSize: [45, 45], // [ancho, alto] en p铆xeles de la imagen de la sombra
  shadowAnchor: [10, 40], // [x, y] en p铆xeles que corresponden a la ubicaci贸n de la sombra relativa a su esquina superior izquierda
  shadowUrl: "/icons/marker-shadow.png" // Ruta a la imagen de la sombra del marcador
});

// Exporta el icono personalizado para que pueda ser utilizado en otros componentes
export default Icon;

siguiente componente 

import { useState, useEffect } from 'react'; // Importa los hooks useState y useEffect de React
import { useMapEvent, useMap } from 'react-leaflet'; // Importa los hooks useMapEvent y useMap de react-leaflet

const ZoomDisplay = () => {
  // Estado para almacenar el nivel de zoom actual del mapa
  const [zoom, setZoom] = useState<number | null>(null);
  // Estado para almacenar la escala actual del mapa como una cadena de texto
  const [scale, setScale] = useState<string | null>(null);
  // Hook para acceder a la instancia del mapa de Leaflet
  const map = useMap();

  // Estilos en l铆nea para el contenedor que mostrar谩 el zoom y la escala
  const containerStyle = {
    position: 'absolute', // Posicionamiento absoluto para colocarlo sobre el mapa
    bottom: '20px', // Margen desde la parte inferior del mapa
    left: '50%', // Centrar horizontalmente
    transform: 'translateX(-50%)', // Ajustar el centrado basado en el ancho del contenedor
    backgroundColor: 'rgba(255, 255, 255, 0.7)', // Fondo blanco semitransparente
    padding: '5px 10px', // Espaciado interno
    borderRadius: '5px', // Bordes redondeados
    border: '2px solid rgba(0, 0, 0, 0.2)', // Borde sutil
    zIndex: 1000, // Asegurar que est茅 por encima de otros elementos del mapa
    boxShadow: '0 0 10px rgba(0, 0, 0, 0.2)', // Sombra suave
    fontFamily: 'Arial, sans-serif', // Fuente
    fontSize: '14px', // Tama帽o de la fuente
    fontWeight: 'bold', // Texto en negrita
    color: '#333', // Color del texto
    display: 'flex', // Usar flexbox para la disposici贸n interna
    gap: '15px' // Espacio entre los elementos (zoom y escala)
  };

  // Funci贸n para calcular la escala del mapa basada en el nivel de zoom actual
  const calculateScale = (currentZoom: number) => {
    const latitude = map.getCenter().lat; // Obtiene la latitud del centro del mapa
    const earthCircumference = 40075017; // Circunferencia de la Tierra en metros
    const screenResolution = 256; // P铆xeles por tile (t铆picamente 256 para Leaflet)
    // F贸rmula para calcular el denominador de la escala
    const scaleDenominator = (earthCircumference * Math.cos((latitude * Math.PI) / 180)) /
                             (screenResolution * Math.pow(2, currentZoom));

    return formatScale(scaleDenominator); // Formatea la escala para una mejor visualizaci贸n
  };

  // Funci贸n para formatear el denominador de la escala a una cadena legible
  const formatScale = (rawScale: number): string => {
    if (rawScale >= 1000000) { // Si la escala es de un mill贸n o m谩s
      return `${(rawScale / 1000000).toFixed(1)}M`; // Muestra en millones (ej: 1.5M)
    } else if (rawScale >= 1000) { // Si la escala es de mil o m谩s
      return `${(rawScale / 1000).toFixed(0)}K`; // Muestra en miles (ej: 50K)
    }
    return rawScale.toFixed(0); // Si es menor que mil, muestra el n煤mero entero
  };

  // useEffect que se ejecuta una vez al montar el componente
  useEffect(() => {
    const initialZoom = map.getZoom(); // Obtiene el nivel de zoom inicial del mapa
    setZoom(initialZoom); // Establece el estado del zoom inicial
    setScale(calculateScale(initialZoom)); // Calcula y establece la escala inicial
  }, [map]); // Dependencia: solo se ejecuta cuando la instancia del mapa cambia (poco probable)

  // Hook para escuchar el evento 'zoomend' del mapa (cuando el zoom ha terminado de cambiar)
  useMapEvent('zoomend', (event) => {
    const newZoom = event.target.getZoom(); // Obtiene el nuevo nivel de zoom
    setZoom(newZoom); // Actualiza el estado del zoom
    setScale(calculateScale(newZoom)); // Recalcula y actualiza la escala
  });

  // Hook para escuchar el evento 'moveend' del mapa (cuando el mapa ha terminado de moverse)
  useMapEvent('moveend', () => {
    if (zoom !== null) { // Asegurarse de que el zoom tenga un valor
      setScale(calculateScale(zoom)); // Recalcula la escala ya que la latitud del centro puede haber cambiado
    }
  });

  return (
    <div style={containerStyle}>
      {zoom !== null && ( // Mostrar la informaci贸n del zoom solo si el estado zoom no es nulo
        <div>
          <span style={{ marginRight: '5px' }}>馃攳</span> {/* Icono de lupa */}
          Zoom: {zoom.toFixed(1)} {/* Muestra el nivel de zoom con un decimal */}
        </div>
      )}
      {scale !== null && ( // Mostrar la informaci贸n de la escala solo si el estado scale no es nulo
        <div>
          <span style={{ marginRight: '5px' }}>馃搹</span> {/* Icono de regla */}
          Escala: 1:{scale} {/* Muestra la escala en formato 1:X */}
        </div>
      )}
    </div>
  );
};

export default ZoomDisplay;