From eeccb595510ee5cd0fa232837e2a02c82d078d82 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Wed, 27 Aug 2025 16:31:57 -0600 Subject: [PATCH] =?UTF-8?q?Se=20agreg=C3=B3=20el=20buscador=20de=20obras?= =?UTF-8?q?=20y=20las=20obras=20se=20agrupan=20por=20direcci=C3=B3n=20y=20?= =?UTF-8?q?estado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/Components/Dashboard/Maps.vue | 431 +++++++++++++++------ 1 file changed, 311 insertions(+), 120 deletions(-) diff --git a/resources/js/Components/Dashboard/Maps.vue b/resources/js/Components/Dashboard/Maps.vue index bc18343..951783c 100644 --- a/resources/js/Components/Dashboard/Maps.vue +++ b/resources/js/Components/Dashboard/Maps.vue @@ -45,8 +45,8 @@ const visibleMarkersCount = computed(() => { // Coordenadas del centro de Comalcalco, Tabasco const COMALCALCO_CENTER = { - lat: 18.2669, - lng: -93.2072, + lat: 18.26, + lng: -93.25, }; // Límites expandidos de Tabasco (más amplios para capturar todas las obras) @@ -60,6 +60,80 @@ const TABASCO_BOUNDS = { // Cache para geocodificación (evitar llamadas duplicadas) const geocodingCache = new Map(); +const searchQuery = ref(''); +const searchResults = ref([]); +const showSearchResults = ref(false); +const searchLoading = ref(false); + +const searchAddresses = async () => { + if (!searchQuery.value.trim()) { + searchResults.value = []; + showSearchResults.value = false; + return; + } + + searchLoading.value = true; + const query = searchQuery.value.toLowerCase(); + + // Buscar en las obras existentes + const allObras = [ + ...props.counters.sin_avances.detalles.map((obra) => ({ + ...obra, + estado: "sin_avances", + })), + ...props.counters.abiertas.detalles.map((obra) => ({ + ...obra, + estado: "abiertas", + })), + ...props.counters.finalizadas.detalles.map((obra) => ({ + ...obra, + estado: "finalizadas", + })), + ]; + + // Filtrar obras que coincidan con la búsqueda + const matches = allObras.filter(obra => + obra.direccion_obra?.toLowerCase().includes(query) || + obra.num_proyecto?.toLowerCase().includes(query) + ); + + searchResults.value = matches.slice(0, 10); // Limitar a 10 resultados + showSearchResults.value = true; + searchLoading.value = false; +}; + +//Función para ir a una obra específica +const goToObra = async (obra) => { + searchQuery.value = ''; + searchResults.value = []; + showSearchResults.value = false; + + // Buscar el marcador correspondiente + const markerData = markers.value.find(m => + m.obras.some(o => + o.num_proyecto === obra.num_proyecto && + o.direccion_obra === obra.direccion_obra + ) + ); + + if (markerData) { + // Centrar el mapa en el marcador + map.value.setCenter(markerData.marker.getPosition()); + map.value.setZoom(16); + + // Abrir el InfoWindow + markers.value.forEach((m) => m.infoWindow?.close()); + markerData.infoWindow.open(map.value, markerData.marker); + } +}; + +// Limpiar resultados de búsqueda +const clearSearch = () => { + searchQuery.value = ''; + searchResults.value = []; + showSearchResults.value = false; +}; + const initMap = async () => { loading.value = true; error.value = null; @@ -68,7 +142,7 @@ const initMap = async () => { const loader = new Loader({ apiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY, version: "weekly", - libraries: ["places"], + libraries: ["places", "geometry"], }); const google = await loader.load(); @@ -142,60 +216,77 @@ const loadMarkers = async () => { })) ); - // Agrupar obras por dirección - const obrasPorDireccion = new Map(); + // Agrupar obras por grupo + const obrasPorGrupo = new Map(); allObras.forEach(obra => { - const direccion = obra.direccion_obra; - if (!obrasPorDireccion.has(direccion)) { - obrasPorDireccion.set(direccion, []); + const clave = `${obra.direccion_obra}|${obra.estado}`; + if(!obrasPorGrupo.has(clave)){ + obrasPorGrupo.set(clave, { + direccion: obra.direccion_obra, + estado: obra.estado, + obras: [] + }); } - obrasPorDireccion.get(direccion).push(obra); + obrasPorGrupo.get(clave).obras.push(obra); }); processingStats.value.total = allObras.length; // Procesar grupos de obras por dirección en lotes - const direcciones = Array.from(obrasPorDireccion.keys()); + const grupos = Array.from(obrasPorGrupo.values()); const batchSize = 5; - for (let i = 0; i < direcciones.length; i += batchSize) { - const batch = direcciones.slice(i, i + batchSize); - const batchPromises = batch.map(direccion => - createMarkerForDireccion(direccion, obrasPorDireccion.get(direccion)) + for (let i = 0; i < grupos.length; i += batchSize) { + const batch = grupos.slice(i, i + batchSize); + const batchPromises = batch.map(grupo => + createMarkerForGroup(grupo) ); await Promise.allSettled(batchPromises); - if (i + batchSize < direcciones.length) { + if (i + batchSize < grupos.length) { await new Promise((resolve) => setTimeout(resolve, 100)); } } updateMarkers(); }; -const createMarkerForDireccion = async (direccion, obrasEnDireccion) => { +const createMarkerForGroup = async (grupo) => { + const { direccion, obras: obrasEnGrupo } = grupo; if (!direccion || !geocoder.value) { - obrasEnDireccion.forEach(() => processingStats.value.failed++); + processingStats.value.failed += obrasEnGrupo.length; return; } + // Normalización corregida de direcciones + let direccionNormalizada = direccion + .replace(/^RA\.\s*/i, "Ranchería ") + .replace(/CIUDAD\s+/i, "") + .replace(/2DA\s+SECCION/gi, "2 sección") + .replace(/\s+/g, " ") + .replace(/,\s*,/g, ",") + .trim(); + // Construir direcciones alternativas para geocodificación const addresses = [ + `${direccionNormalizada}, Comalcalco, Tabasco, México`, `${direccion}, Comalcalco, Tabasco, México`, - `${direccion}, Tabasco, México`, - `${direccion}, México`, + `${direccionNormalizada.replace('Ranchería ', '')}, Comalcalco, Tabasco, México`, + `${direccion.replace('RA. ', '')}, Comalcalco, Tabasco`, + `Comalcalco, Tabasco, México` ]; // Verificar cache primero const cacheKey = direccion; if (geocodingCache.has(cacheKey)) { const cachedData = geocodingCache.get(cacheKey); - processingStats.value.duplicates += obrasEnDireccion.length - 1; - return createMarkerFromPosition(obrasEnDireccion, cachedData.position, cachedData.formatted_address); + processingStats.value.duplicates += obrasEnGrupo.length - 1; + return createMarkerFromPosition(grupo, cachedData.position, cachedData.formatted_address); } // Intentar geocodificación con direcciones alternativas - for (const address of addresses) { + for (let i = 0; i < addresses.length; i++) { + const address = addresses[i]; try { const result = await geocodeAddress(address); if (result.success) { @@ -205,16 +296,16 @@ const createMarkerForDireccion = async (direccion, obrasEnDireccion) => { formatted_address: result.formatted_address, }); - return createMarkerFromPosition(obrasEnDireccion, result.position, result.formatted_address); + return createMarkerFromPosition(grupo, result.position, result.formatted_address); } } catch (err) { - console.warn(`Error geocodificando ${address}:`, err); + console.warn(`Error en intento ${i + 1}: ${address}`, err); } } // Si todas las direcciones fallan, crear marcador fallback - processingStats.value.failed += obrasEnDireccion.length; - + processingStats.value.failed += obrasEnGrupo.length; + // Crear marcador fallback con offset aleatorio const randomOffset = { lat: (Math.random() - 0.5) * 0.02, lng: (Math.random() - 0.5) * 0.02, @@ -225,23 +316,66 @@ const createMarkerForDireccion = async (direccion, obrasEnDireccion) => { lng: COMALCALCO_CENTER.lng + randomOffset.lng, }; - return createMarkerFromPosition(obrasEnDireccion, fallbackPosition, "Ubicación aproximada - Comalcalco, Tabasco"); + return createMarkerFromPosition(grupo, fallbackPosition, "Ubicación aproximada - Comalcalco, Tabasco"); }; const geocodeAddress = (address) => { - return new Promise((resolve, reject) => { - geocoder.value.geocode({ address }, (results, status) => { - if (status === "OK" && results[0]) { - const position = results[0].geometry.location; - const lat = position.lat(); - const lng = position.lng(); + return new Promise((resolve) => { + const request = { + address: address, + region: 'mx', + bounds: new google.maps.LatLngBounds( + new google.maps.LatLng(18.1, -93.4), + new google.maps.LatLng(18.4, -93.0) + ), + }; - resolve({ - success: true, - position: { lat, lng }, - formatted_address: results[0].formatted_address, - }); + geocoder.value.geocode(request, (results, status) => { + if (status === "OK" && results && results.length > 0) { + let bestResult = null; + + for (let result of results) { + const formatted = result.formatted_address.toLowerCase(); + + // Priorizar resultados que mencionen Comalcalco + if (formatted.includes('comalcalco')) { + bestResult = result; + break; + } + + // Si no encuentra Comalcalco, verificar distancia + if (!bestResult) { + const position = result.geometry.location; + const comalcalcoCenter = new google.maps.LatLng(COMALCALCO_CENTER.lat, COMALCALCO_CENTER.lng); + const distance = google.maps.geometry.spherical.computeDistanceBetween(position, comalcalcoCenter); + + // Si está dentro de 50km de Comalcalco, considerarlo válido + if (distance <= 50000) { + bestResult = result; + } + } + } + + if (bestResult) { + const position = bestResult.geometry.location; + const lat = position.lat(); + const lng = position.lng(); + + resolve({ + success: true, + position: { lat, lng }, + formatted_address: bestResult.formatted_address, + }); + } else { + console.warn(`No se encontró resultado válido para "${address}"`); + resolve({ + success: false, + status: 'NO_VALID_RESULT', + address, + }); + } } else { + console.warn(`Geocodificación falló para "${address}": ${status}`); resolve({ success: false, status, @@ -252,19 +386,18 @@ const geocodeAddress = (address) => { }); }; -const createMarkerFromPosition = (obrasEnPosicion, position, formatted_address) => { - const lat = position.lat; - const lng = position.lng; +const createMarkerFromPosition = (grupo, position, formatted_address) => { + const { obras: obrasEnPosicion, estado } = grupo; - // Verificar límites expandidos - if ( - lat < TABASCO_BOUNDS.south || - lat > TABASCO_BOUNDS.north || - lng < TABASCO_BOUNDS.west || - lng > TABASCO_BOUNDS.east - ) { - processingStats.value.outOfBounds += obrasEnPosicion.length; - } + const statusOffsets = { + sin_avances: { lat: 0.00008, lng: -0.00005 }, // Arriba-Izquierda + abiertas: { lat: 0, lng: 0.0001 }, // Derecha + finalizadas: { lat: -0.00004,lng: -0.00005 }, // Abajo-Izquierda + }; + + const offset = statusOffsets[estado] || { lat: 0, lng: 0 }; + const lat = position.lat + offset.lat; + const lng = position.lng + offset.lng; // Usar la primera obra para definir el marcador principal const obraPrincipal = obrasEnPosicion[0]; @@ -272,11 +405,11 @@ const createMarkerFromPosition = (obrasEnPosicion, position, formatted_address) const marker = new google.maps.Marker({ position: new google.maps.LatLng(lat, lng), map: map.value, - title: obrasEnPosicion.length === 1 - ? `${obraPrincipal.num_proyecto} - ${obraPrincipal.direccion_obra}` - : `${obrasEnPosicion.length} obras en ${obraPrincipal.direccion_obra}`, - icon: createCustomMarker(obraPrincipal.estado), - visible: filters[obraPrincipal.estado], + title: obrasEnPosicion.length > 1 + ? `${obrasEnPosicion.length} obras en ${obraPrincipal.direccion_obra} (${estado.replace('_', ' ')})` + : `${obraPrincipal.num_proyecto} - ${obraPrincipal.direccion_obra}`, + icon: createCustomMarker(estado), // El color del marcador depende del estado del grupo. + visible: filters[estado], animation: google.maps.Animation.DROP, }); @@ -304,75 +437,17 @@ const createMarkerFromPosition = (obrasEnPosicion, position, formatted_address) const createCustomMarker = (estado) => { const color = markerColors[estado]; - const width = 32; - const height = 48; - return { + return { url: `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(` - - - - - - - - - - - - - - - - - - - - - - + + + + `)}`, - scaledSize: new google.maps.Size(width, height), - anchor: new google.maps.Point(width / 2, height - 4), - origin: new google.maps.Point(0, 0), + scaledSize: new google.maps.Size(24, 36), + anchor: new google.maps.Point(12, 36) }; }; @@ -571,6 +646,122 @@ onMounted(() => {
+ +
+
+
+ +
+ + + + + + + + + + +
+
+
+ + +
+
+
+
+

+ {{ obra.num_proyecto || 'N/A' }} +

+

+ {{ obra.direccion_obra }} +

+
+
+ + {{ + obra.estado === 'sin_avances' ? 'Sin avances' : + obra.estado === 'abiertas' ? 'Abierta' : 'Finalizada' + }} + +
+
+
+
+ + +
+
+ No se encontraron resultados +
+
+
+