/* Torre Auro — Mapa de competencia por nicho (decisión por nivel) */ const _nicheCache = {}; // eco → places[] function _hav(aLat, aLng, bLat, bLng) { const R = 6371000, toR = Math.PI / 180; const dLat = (bLat - aLat) * toR, dLng = (bLng - aLng) * toR; const s = Math.sin(dLat/2)**2 + Math.cos(aLat*toR)*Math.cos(bLat*toR)*Math.sin(dLng/2)**2; return 2 * R * Math.asin(Math.sqrt(s)); } function NicheMap({ floor }) { const z = ZONES[floor.zone]; const eco = z.eco; const comp = NICHE_COMPETITION[eco] || NICHE_COMPETITION.corporativo; const acc = z.accHex; const mapRef = useRef(null); const clusterRef = useRef(null); const placesRef = useRef([]); const [ring, setRing] = useState(3000); const [status, setStatus] = useState('cargando'); const [counts, setCounts] = useState({ 1000:0, 3000:0, 5000:0 }); const ringRef = useRef(3000); function applyFilter() { const cl = clusterRef.current; if (!cl) return; cl.clearLayers(); const r = ringRef.current; cl.addLayers(placesRef.current.filter(p => p.dist <= r).map(p => p.marker)); } async function fetchNiche(signal) { if (_nicheCache[eco]) return _nicheCache[eco]; const { lat, lng } = TORRE_COORD; const out = []; const seen = new Set(); try { const url = `https://www.inegi.org.mx/app/api/denue/v1/consulta/Buscar/${encodeURIComponent(comp.term)}/${lat},${lng}/5000/${DENUE_TOKEN}`; const res = await fetch(url, { signal }); if (res.ok) { const data = await res.json(); (data || []).forEach(row => { const la = parseFloat(row.Latitud), ln = parseFloat(row.Longitud); if (!la || !ln) return; const id = row.Id || (row.Nombre + la + ln); if (seen.has(id)) return; seen.add(id); out.push({ lat:la, lng:ln, name: row.Nombre || row.Razon_social || 'Establecimiento', clase: row.Clase_actividad || '', dist: _hav(lat, lng, la, ln) }); }); } } catch (e) { /* ignore */ } _nicheCache[eco] = out; return out; } useEffect(() => { if (!window.L || mapRef.current) return; const el = document.getElementById('nichemap-' + floor.id); if (!el) return; let cancelled = false; const ac = new AbortController(); const { lat, lng } = TORRE_COORD; const map = window.L.map(el, { center:[lat,lng], zoom:14, scrollWheelZoom:false, zoomControl:true, attributionControl:false }); mapRef.current = map; window.L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { maxZoom:19 }).addTo(map); [5000,3000,1000].forEach(r => window.L.circle([lat,lng], { radius:r, color:acc, weight:1, opacity:.4, fill:false, dashArray:'4 6' }).addTo(map)); const torreIcon = window.L.divIcon({ className:'lmk-torre-wrap', html:'
Torre Auro · exclusividad
', iconSize:[0,0] }); window.L.marker([lat,lng], { icon:torreIcon, zIndexOffset:1000 }).addTo(map); const cluster = window.L.markerClusterGroup({ maxClusterRadius:90, showCoverageOnHover:false, spiderfyOnMaxZoom:false, disableClusteringAtZoom:17, chunkedLoading:true, iconCreateFunction: (c) => { const n = c.getChildCount(); const size = n < 8 ? 40 : n < 20 ? 56 : n < 45 ? 74 : 92; return window.L.divIcon({ className:'lmk-blob-wrap', html:``, iconSize:[size,size] }); }, }); clusterRef.current = cluster; map.addLayer(cluster); map.setView([lat,lng], 13); (async () => { const places = await fetchNiche(ac.signal); if (cancelled || mapRef.current !== map) return; places.forEach(p => { const icon = window.L.divIcon({ className:'lmk-pin-wrap', html:``, iconSize:[16,16], iconAnchor:[8,8] }); p.marker = window.L.marker([p.lat,p.lng], { icon }); p.marker.bindTooltip(`${p.name}
${p.clase || ''}`, { direction:'top', offset:[0,-8], className:'lmk-tip' }); }); placesRef.current = places; const c = { 1000:0, 3000:0, 5000:0 }; places.forEach(p => { if (p.dist<=1000) c[1000]++; if (p.dist<=3000) c[3000]++; if (p.dist<=5000) c[5000]++; }); setCounts(c); applyFilter(); setStatus(places.length ? 'ok' : 'empty'); })(); return () => { cancelled = true; ac.abort(); map.remove(); mapRef.current = null; clusterRef.current = null; placesRef.current = []; }; }, [floor.id]); function pickRing(r) { setRing(r); ringRef.current = r; applyFilter(); } const RINGS = [{ v:1000, l:'1 km' }, { v:3000, l:'3 km' }, { v:5000, l:'5 km' }]; const nearby = counts[ring] || 0; return (
04 / Tu competencia

El mercado está afuera.
La exclusividad, adentro.

Torre Auro garantiza exclusividad de giro por categoría. Esto significa cero competencia directa de tu nicho dentro del edificio — mientras el mercado de {comp.label} ya está activo en el entorno inmediato.

Radio {RINGS.map(r => ( ))}
); } Object.assign(window, { NicheMap });