PorFin_v2

PorFin™ | Herramienta de Supervivencia Urbana

PorFin™

🛡️ Herramienta de Supervivencia Urbana

Cuando nadie ve nada, PorFin™ interpreta la normativa, protege tus derechos y te acompaña hasta el destino.

🛡️ PorFin™
🟢 Halo Verde
/*======================================================*/ /* CABECERA */ /*======================================================*/ #topBar{ position:absolute; top:12px; left:12px; right:12px; z-index:9000; display:flex; justify-content:space-between; align-items:center; padding:12px 16px; background:rgba(8,18,30,.85); backdrop-filter:blur(14px); border-radius:18px; color:white; } .logoPorfin{ font-size:20px; font-weight:700; } .estadoEscudo{ padding:8px 14px; border-radius:30px; background:#1d4ed8; font-size:14px; font-weight:bold; } #buscadorDestino{ position:absolute; top:80px; left:12px; right:12px; display:flex; gap:10px; z-index:9000; } #buscadorDestino input{ flex:1; padding:14px; border:none; border-radius:16px; font-size:16px; outline:none; } #buscadorDestino button{ width:56px; border:none; border-radius:16px; font-size:22px; background:#2563eb; color:white; cursor:pointer; } function actualizarHalo(color,texto){ const estado=document.getElementById(“estadoEscudo”); estado.innerHTML=texto; estado.style.background=color; } actualizarHalo(“#16a34a”,”🟢 Halo Verde”);
/*======================================================*/ /* YO + HALO PORFIN™ */ /*======================================================*/ .halo-yo{ position:relative; width:74px; height:74px; display:flex; align-items:center; justify-content:center; } .halo-luz{ position:absolute; width:72px; height:72px; border-radius:50%; animation:haloPulse 2s infinite; opacity:.85; } .halo-verde{ background:rgba(34,197,94,.45); box-shadow: 0 0 15px #22c55e, 0 0 30px #22c55e, 0 0 45px #22c55e; } .halo-ambar{ background:rgba(251,191,36,.45); box-shadow: 0 0 15px #fbbf24, 0 0 30px #fbbf24, 0 0 45px #fbbf24; } .avatar{ position:absolute; width:54px; height:54px; border-radius:50%; border:3px solid white; overflow:hidden; background:white; z-index:5; } .avatar img{ width:100%; height:100%; object-fit:cover; } @keyframes haloPulse{ 0%{ transform:scale(.9); opacity:.45; } 50%{ transform:scale(1.18); opacity:.95; } 100%{ transform:scale(.9); opacity:.45; } } /*======================================================*/ /* MARCADOR YO */ /*======================================================*/ let yoMarker; function crearMarcadorYO(lat,lng,color=”verde”){ const claseHalo=color===”verde” ?”halo-verde” :”halo-ambar”; const icon=L.divIcon({ className:””, iconSize:[74,74], iconAnchor:[37,37], html:`
` }); if(yoMarker){ yoMarker.setLatLng([lat,lng]); yoMarker.setIcon(icon); }else{ yoMarker=L.marker([lat,lng],{ icon:icon }).addTo(map); } } /*======================================================*/ /* MARCADOR YO */ /*======================================================*/ let yoMarker; function crearMarcadorYO(lat,lng,color=”verde”){ const claseHalo=color===”verde” ?”halo-verde” :”halo-ambar”; const icon=L.divIcon({ className:””, iconSize:[74,74], iconAnchor:[37,37], html:`
` }); if(yoMarker){ yoMarker.setLatLng([lat,lng]); yoMarker.setIcon(icon); }else{ yoMarker=L.marker([lat,lng],{ icon:icon }).addTo(map); } } crearMarcadorYO(lat,lng,”verde”); /*======================================================*/ /* BLOQUE 5 – GPS + SEGUIMIENTO PORFIN™ */ /*======================================================*/ let watchID = null; function iniciarGPS() { if (!navigator.geolocation) { alert(“Este dispositivo no dispone de GPS.”); return; } watchID = navigator.geolocation.watchPosition( actualizarPosicion, errorGPS, { enableHighAccuracy: true, maximumAge: 1000, timeout: 10000 } ); } function actualizarPosicion(pos) { const lat = pos.coords.latitude; const lng = pos.coords.longitude; const velocidad = pos.coords.speed ? Math.round(pos.coords.speed * 3.6) : 0; const rumbo = pos.coords.heading ?? 0; crearMarcadorYO(lat, lng, estadoHalo); const punto = proyectarYO(lat, lng, rumbo); map.setView(punto, map.getZoom(), { animate: true }); if (rumbo !== null && velocidad > 5) { document.getElementById(“map”).style.transform = `rotate(${-rumbo}deg)`; } document.getElementById(“velocidad”).innerHTML = velocidad + ” km/h”; } function proyectarYO(lat, lng, rumbo) { const punto = map.project([lat, lng], map.getZoom()); const alto = map.getSize().y; const offset = alto * 0.28; const rad = rumbo * Math.PI / 180; const nuevo = punto.add([ Math.sin(rad) * offset * 0.25, -Math.cos(rad) * offset ]); return map.unproject(nuevo, map.getZoom()); } function errorGPS(error) { console.log(error); } function detenerGPS() { if (watchID !== null) { navigator.geolocation.clearWatch(watchID); watchID = null; } } iniciarGPS(); /*======================================================*/ /* BLOQUE 6 · NAVEGACIÓN INTELIGENTE PORFIN™ */ /*======================================================*/ let rutaControl = null; let destinoActual = null; async function buscarDestino() { const texto = document.getElementById(“destinoInput”).value.trim(); if (texto === “”) return; try { const respuesta = await fetch( “https://nominatim.openstreetmap.org/search?format=json&q=” + encodeURIComponent(texto) ); const datos = await respuesta.json(); if (!datos.length) { alert(“Destino no encontrado.”); return; } destinoActual = L.latLng( parseFloat(datos[0].lat), parseFloat(datos[0].lon) ); calcularRuta(destinoActual); } catch (e) { console.error(e); alert(“Error buscando el destino.”); } } function calcularRuta(destino) { if (!yoMarker) return; if (rutaControl) { map.removeControl(rutaControl); } rutaControl = L.Routing.control({ waypoints: [ yoMarker.getLatLng(), destino ], routeWhileDragging: false, addWaypoints: false, draggableWaypoints: false, fitSelectedRoutes: true, show: false, createMarker: function () { return null; }, lineOptions: { styles: [ { color: “#00b7ff”, opacity: 0.9, weight: 7 } ] } }) .on(“routesfound”, function(e){ const ruta = e.routes[0]; const km = (ruta.summary.totalDistance/1000).toFixed(1); const min = Math.round(ruta.summary.totalTime/60); console.log(“Ruta:”,km+” km”,min+” min”); hablar( “Ruta calculada. ” + km + ” kilómetros. Tiempo estimado ” + min + ” minutos.” ); }) .addTo(map); } function cancelarRuta(){ if(rutaControl){ map.removeControl(rutaControl); rutaControl=null; } destinoActual=null; hablar(“Ruta cancelada.”); } /*======================================================*/ /* BLOQUE 7 · ATLAS NORMATIVO PORFIN™ */ /*======================================================*/ const atlasNormativo = { “madrid”:{ halo:”verde”, mensaje:”Puedes estacionar gratuitamente en SER.” }, “guadalajara”:{ halo:”ambar”, mensaje:”Zona Azul regulada. Consulta condiciones.” }, “barcelona”:{ halo:”ambar”, mensaje:”Zona regulada con limitaciones.” }, “valencia”:{ halo:”verde”, mensaje:”Estacionamiento permitido con Tarjeta.” }, “zaragoza”:{ halo:”verde”, mensaje:”Zona ORA gratuita.” } }; let municipioActual=””; async function actualizarNormativa(lat,lng){ try{ const respuesta=await fetch( `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=12&accept-language=es` ); const datos=await respuesta.json(); const municipio= (datos.address.city|| datos.address.town|| datos.address.village|| datos.address.municipality|| “”).toLowerCase(); if(municipio!==municipioActual){ municipioActual=municipio; cambiarHaloMunicipio(municipio); } }catch(e){ console.log(e); } } function cambiarHaloMunicipio(nombre){ const norma=atlasNormativo[nombre]; if(!norma){ estadoHalo=”ambar”; crearMarcadorYO( yoMarker.getLatLng().lat, yoMarker.getLatLng().lng, “ambar” ); return; } estadoHalo=norma.halo; crearMarcadorYO( yoMarker.getLatLng().lat, yoMarker.getLatLng().lng, estadoHalo ); hablar(norma.mensaje); console.log(“Municipio:”,nombre); console.log(“Normativa:”,norma.mensaje); } actualizarNormativa(lat,lng); /*======================================================*/ /* BLOQUE 8 · MIRADA ADELANTADA™ */ /*======================================================*/ let aviso150=false; let aviso100=false; let aviso50=false; let aviso25=false; let aviso10=false; function reiniciarMiradaAdelantada(){ aviso150=false; aviso100=false; aviso50=false; aviso25=false; aviso10=false; } function comprobarMiradaAdelantada(){ if(!destinoActual) return; if(!yoMarker) return; const yo=yoMarker.getLatLng(); const distancia=calcularDistancia( yo.lat, yo.lng, destinoActual.lat, destinoActual.lng ); if(distancia<=150 && !aviso150){ aviso150=true; hablar("Nos acercamos."); mostrarMensajeHalo( "🟡 Nos acercamos al destino." ); } if(distancia<=100 && !aviso100){ aviso100=true; hablar("Puedes aparcar con tu Tarjeta de Estacionamiento según la normativa."); mostrarMensajeHalo( "🟢 Consulta el Halo PorFin." ); } if(distancia<=50 && !aviso50){ aviso50=true; hablar("Entrando en la calle del destino."); mostrarMensajeHalo( "📍 Calle actual." ); } if(distancia<=25 && !aviso25){ aviso25=true; hablar("Atención a las zonas de carga y descarga."); mostrarMensajeHalo( "⚠ Revisa Carga y Descarga." ); } if(distancia<=10 && !aviso10){ aviso10=true; hablar("Destino alcanzado."); mostrarMensajeHalo( "🛡 Halo confirmado." ); } } function mostrarMensajeHalo(texto){ let caja=document.getElementById("haloInfo"); if(!caja){ caja=document.createElement("div"); caja.id="haloInfo"; caja.style.position="absolute"; caja.style.top="20px"; caja.style.left="50%"; caja.style.transform="translateX(-50%)"; caja.style.zIndex="9999"; caja.style.background="rgba(6,18,6,.90)"; caja.style.color="white"; caja.style.padding="12px 18px"; caja.style.borderRadius="18px"; caja.style.fontWeight="600"; caja.style.backdropFilter="blur(12px)"; caja.style.border="1px solid rgba(255,255,255,.15)"; document.getElementById("map-wrapper").appendChild(caja); } caja.innerHTML=texto; clearTimeout(caja.timer); caja.timer=setTimeout(()=>{ caja.remove(); },5000); } /*======================================================*/ /* BLOQUE 9 · MOTOR DE NAVEGACIÓN PORFIN™ */ /*======================================================*/ let ultimoRumbo = 0; function actualizarNavegacion(lat,lng,heading,speed){ if(!map) return; if(speed < 3){ map.panTo([lat,lng],{ animate:true, duration:0.5 }); return; } if(heading !== null && !isNaN(heading)){ ultimoRumbo = heading; } const punto = proyectarYO(lat,lng,ultimoRumbo); map.panTo(punto,{ animate:true, duration:0.8 }); girarMapa(ultimoRumbo); } function girarMapa(rumbo){ const pane = map.getPane("mapPane"); if(!pane) return; pane.style.transformOrigin="50% 50%"; pane.style.transition="transform .35s linear"; pane.style.transform=`rotate(${-rumbo}deg)`; } function detenerRotacionMapa(){ const pane = map.getPane("mapPane"); if(!pane) return; pane.style.transform="rotate(0deg)"; } actualizarNavegacion( lat, lng, pos.coords.heading, velocidad ); actualizarNavegacion( lat, lng, pos.coords.heading, velocidad ); detenerRotacionMapa(); /*======================================================*/ /* BLOQUE 10 · ATLAS NORMATIVO INTELIGENTE */ /*======================================================*/ let Atlas = {}; /*--------------------------------------*/ /* CARGAR BASE NORMATIVA */ /*--------------------------------------*/ async function cargarAtlas(){ try{ const respuesta = await fetch("atlas_normativo.json"); Atlas = await respuesta.json(); console.log( "✅ Atlas cargado:", Object.keys(Atlas).length, "municipios" ); }catch(error){ console.error("Error cargando Atlas",error); } } /*--------------------------------------*/ /* BUSCAR MUNICIPIO */ /*--------------------------------------*/ function obtenerNormativa(nombreCiudad){ if(!nombreCiudad) return null; const ciudad = nombreCiudad .toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g,"") .trim(); return Atlas[ciudad] || null; } /*--------------------------------------*/ /* ACTUALIZAR HALO */ /*--------------------------------------*/ function actualizarHaloDesdeAtlas(ciudad){ const norma = obtenerNormativa(ciudad); if(!norma){ estadoHalo="ambar"; return; } estadoHalo=norma.halo; if(userMarker){ userMarker.setIcon( createYoIcon(estadoHalo) ); } } /*--------------------------------------*/ /* MENSAJE NORMATIVO */ /*--------------------------------------*/ function mensajeNormativa(ciudad){ const norma=obtenerNormativa(ciudad); if(!norma){ return "Normativa pendiente de incorporar."; } return norma.descripcion; } { "guadalajara": { "halo": "ambar", "descripcion": "Zona Azul regulada. Consulte condiciones municipales." }, "madrid": { "halo": "verde", "descripcion": "SER gratuito para Personas con Movilidad Reducida." }, "barcelona": { "halo": "ambar", "descripcion": "Zona Verde gratuita. Zona Azul con limitaciones." } }
Scroll to Top