Help
RSS
API
Feed
Maltego
Contact
Domain > accesibilimad.com
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2026-01-31
91.108.98.174
(
ClassC
)
2026-03-01
148.135.128.82
(
ClassC
)
Port 80
HTTP/1.1 301 Moved PermanentlyDate: Sun, 01 Mar 2026 14:02:20 GMTContent-Type: text/htmlContent-Length: 795Connection: keep-aliveLocation: https://accesibilimad.com/platform: hostingerpanel: hpanelContent-Security-Policy: upgrade-insecure-requestsServer: hcdnalt-svc: h3:443; ma86400x-hcdn-request-id: 5953dc952409f6027d1adc14076a2015-phx-edge7x-hcdn-cache-status: MISSx-hcdn-upstream-rt: 0.270 !DOCTYPE html>html styleheight:100%>head>meta nameviewport contentwidthdevice-width, initial-scale1, shrink-to-fitno />title> 301 Moved Permanently/title>style>@media (prefers-color-scheme:dark){body{background-color:#000!important}}/style>/head>body stylecolor: #444; margin:0;font: normal 14px/20px Arial, Helvetica, sans-serif; height:100%; background-color: #fff;>div styleheight:auto; min-height:100%; > div styletext-align: center; width:800px; margin-left: -400px; position:absolute; top: 30%; left:50%;> h1 stylemargin:0; font-size:150px; line-height:150px; font-weight:bold;>301/h1>h2 stylemargin-top:20px;font-size: 30px;>Moved Permanently/h2>p>The document has been permanently moved./p>/div>/div>/body>/html>
Port 443
HTTP/1.1 200 OKDate: Sun, 01 Mar 2026 14:02:20 GMTContent-Type: text/html; charsetUTF-8Transfer-Encoding: chunkedConnection: keep-aliveVary: Accept-EncodingX-Powered-By: PHP/8.2.29set-cookie: PHPSESSIDg1rnrbrenfo8c8f48t1a5cfl6t; path/; HttpOnly; SameSiteStrict; secureExpires: Thu, 19 Nov 1981 08:52:00 GMTCache-Control: no-store, no-cache, must-revalidatePragma: no-cacheX-Frame-Options: DENYX-XSS-Protection: 1; modeblockX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originStrict-Transport-Security: max-age31536000; includeSubDomainsContent-Security-Policy: upgrade-insecure-requestsplatform: hostingerpanel: hpanelServer: hcdnalt-svc: h3:443; ma86400x-hcdn-request-id: e1993f6314885732566ad7aa93412670-phx-edge8x-hcdn-cache-status: DYNAMICx-hcdn-upstream-rt: 0.491 !DOCTYPE html>html langes>head>script typeapplication/ld+json nonceoquuvXcFYBhYOlI4Era6oQ>{ @context: https://schema.org, @type: WebApplication, name: AccesibiliMad, url: https://accesibilimad.com/, inLanguage: es, description: Mapa conversacional que integra datos abiertos del Ayuntamiento de Madrid para mostrar, por proximidad, servicios de salud, movilidad, seguridad, sociales, educación y ocio., applicationCategory: GovernmentApplication, operatingSystem: Any, areaServed: { @type: City, name: Madrid, address: { addressLocality: Madrid, addressCountry: ES } }, provider: { @type: Organization, name: AccesibiliMad }, potentialAction: { @type: SearchAction, target: https://accesibilimad.com/?query{search_term_string}, query-input: required namesearch_term_string }}/script>script async srchttps://www.googletagmanager.com/gtag/js?idG-NSGRXG35W2 nonceoquuvXcFYBhYOlI4Era6oQ>/script>script nonceoquuvXcFYBhYOlI4Era6oQ> window.dataLayer window.dataLayer || ; function gtag(){dataLayer.push(arguments);} gtag(js, new Date()); gtag(config, G-NSGRXG35W2);/script> meta charsetUTF-8 /> meta nameviewport contentwidthdevice-width, initial-scale1.0 /> title>AccesibiliMad/title>meta namedescription contentMapa conversacional que integra datos abiertos del Ayuntamiento de Madrid para mostrar, por proximidad, servicios de salud, movilidad, seguridad, sociales, educación y ocio.>link relcanonical hrefhttps://accesibilimad.com/>meta namerobots contentindex,follow,max-image-preview:large>meta nametheme-color content#003df6>meta propertyog:type contentwebsite>meta propertyog:site_name contentAccesibiliMad>meta propertyog:title contentAccesibiliMad · servicios públicos cerca de ti>meta propertyog:description contentMapa conversacional que integra datos abiertos del Ayuntamiento de Madrid para mostrar, por proximidad, servicios de salud, movilidad, seguridad, sociales, educación y ocio.>meta propertyog:url contenthttps://accesibilimad.com/>meta propertyog:image contenthttps://accesibilimad.com/avatar_677.png>meta nametwitter:card contentsummary_large_image>meta nametwitter:title contentAccesibiliMad · servicios públicos cerca de ti>meta nametwitter:description contentMapa conversacional que integra datos abiertos del Ayuntamiento de Madrid para mostrar, por proximidad, servicios de salud, movilidad, seguridad, sociales, educación y ocio.>meta nametwitter:image contenthttps://accesibilimad.com/avatar_677.png>link relpreconnect hrefhttps://tile.openstreetmap.org crossorigin>link reldns-prefetch hrefhttps://tile.openstreetmap.org>link relpreconnect hrefhttps://unpkg.com crossorigin>link reldns-prefetch hrefhttps://unpkg.com> link relicon typeimage/png hrefavatar_677.png> link relstylesheet hrefhttps://unpkg.com/leaflet@1.9.4/dist/leaflet.css integritysha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY crossorigin/> style nonceoquuvXcFYBhYOlI4Era6oQ> html, body { overflow-x: hidden; min-height: 100vh; display: flex; flex-direction: column; } body { background: linear-gradient(to bottom, #9bb5ff, #003df6); font-family: Arial, sans-serif; margin: 0; padding: 0; color: #003df6; } .container { max-width: 800px; margin: 40px auto; background: #fff; border-radius: 15px; box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); padding: 20px; text-align: center; box-sizing: border-box; flex: 1; /* Para que el footer se vaya al final si hay poco contenido */ width: 95%; } h1 { text-align: center; color: #003df6; } .logo { display: block; margin: 0 auto; width: 150px; margin-bottom: 10px; } .ayuntamiento-logo { display: block; margin: 0 auto; width: 100%; max-width: 300px; margin-bottom: 20px; } .input-group { position: relative; display: flex; align-items: center; gap: 10px; margin-bottom: 5px; } inputtypetext { flex-grow: 1; padding: 12px; border-radius: 10px; border: 1px solid #ccc; background-color: #0A15511A; color: #003df6; box-sizing: border-box; font-size: 16px; } button { width: 100%; background-color: #003df6; color: white; border: none; padding: 12px; border-radius: 10px; cursor: pointer; transition: background-color 0.3s; } button:hover { background-color: #002ac6; } button:disabled { background-color: #cccccc; cursor: not-allowed; } .voice-btn { /* Estructura flexible */ width: auto; /* Se adapta al tamaño del texto */ padding: 0 20px; /* Espacio a los lados para que respire */ flex-shrink: 0; /* Evita que se aplaste si hay poco sitio */ /* Alineación */ display: flex; justify-content: center; align-items: center; gap: 10px; /* Espacio entre el micrófono y el texto */ /* Apariencia (Igualando al botón Consultar pero en Gris) */ height: 44px; /* Misma altura que el input */ background-color: #333; /* Gris oscuro (como en tu captura) */ color: white; /* Texto blanco */ border: none; border-radius: 10px; cursor: pointer; /* Tipografía (Igual que el botón Consultar) */ font-family: Arial, sans-serif; font-size: 16px; transition: background-color 0.3s; } .voice-btn:hover { background-color: #555; } .voice-btn.recording { background-color: #e74c3c; } .chat { margin-top: 20px; background: #f0f0f0; padding: 15px; border-radius: 10px; font-size: 0.9em; } .item { background: #ffffff; border: 1px solid #ccc; margin-bottom: 15px; border-radius: 8px; text-align: left; overflow-wrap: break-word; padding: 0; overflow: hidden; transition: background-color 1s, box-shadow 1s; cursor: pointer; } .item-highlighted { background-color: #FFF9A5; box-shadow: 0 0 8px rgba(255, 165, 0, 0.6); } .item-header { background-color: #003df6; color: white; padding: 8px 12px; font-weight: bold; display: flex; justify-content: space-between; } .item-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: nowrap; } .item-actions { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; } .item-header .scroll-to-map { width: auto !important; height: 24px; padding: 0; background: transparent; border: none; color: inherit; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; } .item-header .scroll-to-map svg { display: block; } .item-content { padding: 12px; } /* Estilos seguros para contenido embebido */ .item-content iframe, .item-content video, .item-content object, .item-content embed { max-width: 100%; height: auto; position: relative; display: block; margin: 10px 0; border-radius: 8px; border: none; } .item strong { color: #003df6; } .filters { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; margin-top: 20px; } .filters label { background-color: #e0e0e0; padding: 8px; border-radius: 6px; text-align: center; cursor: pointer; transition: background-color 0.3s, color 0.3s; display: flex; flex-direction: column; justify-content: center; align-items: center; min-height: 50px; } .filters inputtypecheckbox { display: none; } .filters label:hover { background-color: #d0d0d0; } .filters inputtypecheckbox:checked + label { background-color: #003df6; color: #fff; } .distance-info { font-size: 0.8em; color: #555; margin-top: 4px; } inputtypecheckbox:checked + label .distance-info { color: #fff; } .map-link { display: block; margin-top: 8px; color: #003df6; text-decoration: none; font-weight: bold; } .map-link:hover { text-decoration: underline; } .results-info { text-align: center; margin-top: 20px; margin-bottom: 15px; font-weight: bold; color: #003df6; } #mapid { height: 400px; width: 100%; border-radius: 10px; margin-top: 20px; margin-bottom: 20px; border: 1px solid #ccc; display: none; } #map-title { font-weight: bold; color: #003df6; margin-top: 25px; margin-bottom: 15px; display: none; } .suggestions-container { position: absolute; top: 100%; left: 0; right: 0; background-color: white; border: 1px solid #ccc; border-top: none; border-radius: 0 0 10px 10px; z-index: 1000; max-height: 200px; overflow-y: auto; } .suggestion-item { padding: 10px; text-align: left; cursor: pointer; } .suggestion-item:hover { background-color: #f0f0f0; } .suggestion-item b { color: black; } .chat .error-message { color: red; font-weight: bold; background-color: #ffe0e0; padding: 10px; border-radius: 8px; margin-bottom: 10px; } .chat .warning-message { color: orange; font-weight: bold; background-color: #fff8e0; padding: 10px; border-radius: 8px; margin-bottom: 10px; } #input-hint { font-size: 0.8em; color: #003df6; height: 0; text-align: center; margin-bottom: 0; opacity: 0; transition: opacity 0.5s, height 0.5s, margin-bottom 0.5s; font-weight: bold; overflow: hidden; } #loader-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.8); z-index: 2000; display: none; justify-content: center; align-items: center; flex-direction: column; gap: 15px; } .spinner { border: 8px solid #f3f3f3; border-top: 8px solid #003df6; border-radius: 50%; width: 60px; height: 60px; animation: spin 1s linear infinite; } /* Estilos del Nuevo Footer */ .main-footer { background-color: #000000; color: white; padding: 30px 20px 15px 20px; margin-top: 40px; font-size: 0.9em; } .footer-columns { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; max-width: 1000px; margin: 0 auto; text-align: left; } .footer-col h3 { color: white; margin-top: 0; margin-bottom: 15px; font-size: 1.1em; text-transform: uppercase; border-bottom: 1px solid rgba(255,255,255,0.3); padding-bottom: 10px; } .footer-col p, .footer-col li { line-height: 1.5; } .footer-col ul { list-style: none; padding: 0; margin: 0; } .footer-col li { margin-bottom: 8px; } .footer-col a { color: white; text-decoration: none; transition: opacity 0.3s; } .footer-col a:hover { text-decoration: underline; opacity: 0.8; } .footer-bottom { text-align: center; margin-top: 30px; padding-top: 15px; border-top: 1px solid rgba(255,255,255,0.2); font-size: 0.8em; font-style: italic; opacity: 0.8; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @media (max-width: 768px) { .container { margin: 20px auto; padding: 15px; } .filters { grid-template-columns: repeat(2, 1fr); } /* Footer responsive */ .footer-columns { grid-template-columns: 1fr; text-align: center; } .footer-col h3 { margin-top: 20px; } } @media (max-width: 480px) { .input-group { flex-direction: column; gap: 5px; } .voice-btn { width: 100%; height: 38px; font-size: 1.3em; } .input-group inputtypetext { width: 100%; } } /style>/head>body> noscript> section stylemax-width:720px;margin:1rem auto;padding:.5rem 1rem;> h2>Buscador de servicios públicos y equipamientos municipales en Madrid/h2> p>AccesibiliMad es una herramienta de accesibilidad y geoinclusión que permite localizar en tiempo real los recursos del Ayuntamiento de Madrid más cercanos a tu ubicación./p> h3>Servicios disponibles en el mapa:/h3> ul> li>strong>Salud y Emergencias:/strong> Farmacias de guardia, desfibriladores (DEA) en vía pública, centros de salud y SAMUR./li> li>strong>Transporte y Movilidad:/strong> Próximas llegadas EMT, bocas de Metro de Madrid, estaciones de Cercanías Renfe, bases de BiciMAD disponibles, paradas de taxi y plazas de aparcamiento para personas con movilidad reducida (PMR)./li> li>strong>Seguridad ciudadana:/strong> Comisarías de Policía Municipal y Nacional, y parques de bomberos./li> li>strong>Servicios Sociales y Mayores:/strong> Centros de día, centros de mayores y atención social primaria./li> li>strong>Cultura, Deporte y Ocio:/strong> Bibliotecas municipales, centros culturales, polideportivos, piscinas cubiertas y de verano, áreas infantiles y zonas caninas./li> /ul> p>Consulta el callejero oficial y navega por los datos abiertos georreferenciados./p> /section> /noscript> div idloader-overlay> div classspinner>/div> p idloader-text stylecolor: #003df6; font-weight: bold;>Buscando servicios cercanos.../p> /div> div classcontainer> img srcfoto-marca-diario.png altLogo Ayuntamiento de Madrid classayuntamiento-logo /> img srcavatar_677.png altavatar classlogo /> h1>AccesibiliMad/h1> p styletext-align: center>¡Hola! Soy b>AccesibiliMad/b>, tu agente de b>Accesibilidad/b> y b>geoinclusión/b>. Utilizando los b>Datos Abiertos/b> del b>Ayuntamiento de Madrid/b>, te ayudo a encontrar los b>servicios públicos más próximos a tu ubicación/b>. div classseo-summary stylemargin-bottom: 20px; font-size: 1em; opacity: 1;> p stylemargin: 0; padding: 0;> span stylefont-weight:bold;>Localiza al instante:/span> Desfibriladores ⚡ · Farmacias 💊 · Metro y EMT 🚌 · Bibliotecas 📚 · Polideportivos ⚽ · Comisarías 👮 · Centros de Salud 🏥 · Piscinas 🏊 y mucho más. /p> /div> p styletext-align: center>Por favor, dime el b>nombre y número de la calle/b> de Madrid en la que te encuentras, para poder ayudarte./p> div classinput-group> input typetext iddireccion placeholderEscribe aquí la dirección, por ejemplo: Plaza de la Villa 1 autocompleteoff /> button idvoiceInputBtn classvoice-btn titleDictar dirección aria-labelDictar dirección> span stylefont-size: 1.2em;>🎙️/span> Dictar dirección/button> div idsuggestions classsuggestions-container>/div> /div> div idinput-hint>/div> button idconsultarBtn onclickconsultarServicios()>Consultar los servicios públicos más próximos/button> div idresultsInfo classresults-info>/div> div idfiltros classfilters>/div> div idmap-title>/div> div idmapid>/div> div idchat classchat aria-liveassertive aria-atomictrue>/div> /div> script srchttps://unpkg.com/leaflet@1.9.4/dist/leaflet.js integritysha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo crossorigin nonceoquuvXcFYBhYOlI4Era6oQ>/script> script nonceoquuvXcFYBhYOlI4Era6oQ> let datosServicios ; let categorias new Set(); let madridStreetsSet new Set(); let recognition; let loadingInterval; let selectedCategories new Set(); let mymap; let markerGroup; let markers ; let userLocation null; let searchCompleted false; // Función auxiliar para escapar HTML en JS (Prevención XSS) function escapeHtml(text) { if (!text) return ; const map { &: &, : <, >: >, : ", : ' }; return text.toString().replace(/&>/g, function(m) { return mapm; }); } const userIcon L.icon({ iconUrl: marcador_usuario.png, iconSize: 32, 42, iconAnchor: 16, 42, popupAnchor: 0, -42 }); function hslToHex(h, s, l) { let r, g, b; if (s 0) { r g b l; } else { const hue2rgb (p, q, t) > { if (t 0) t + 1; if (t > 1) t - 1; if (t 1 / 6) return p + (q - p) * 6 * t; if (t 1 / 2) return q; if (t 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; const q l 0.5 ? l * (1 + s) : l + s - l * s; const p 2 * l - q; r hue2rgb(p, q, h + 1 / 3); g hue2rgb(p, q, h); b hue2rgb(p, q, h - 1 / 3); } const toHex x > { const hex Math.round(x * 255).toString(16); return hex.length 1 ? 0 + hex : hex; }; return `#${toHex(r)}${toHex(g)}${toHex(b)}`; } function generateColorPalette(numColors) { const colors ; const saturation 0.8; const lightness 0.55; for (let i 0; i numColors; i++) { const hue i / numColors; colors.push(hslToHex(hue, saturation, lightness)); } return colors; } const light50Palette generateColorPalette(50); let categoryColorCache {}; function getHash(str) { let hash 0; for (let i 0; i str.length; i++) { hash str.charCodeAt(i) + ((hash 5) - hash); } return hash; } function darkenColor(hexColor) { let hex hexColor.startsWith(#) ? hexColor.slice(1) : hexColor; let r parseInt(hex.substring(0, 2), 16); let g parseInt(hex.substring(2, 4), 16); let b parseInt(hex.substring(4, 6), 16); r Math.floor(r * 0.6); g Math.floor(g * 0.6); b Math.floor(b * 0.6); const toHex c > (0 + c.toString(16)).slice(-2); return `#${toHex(r)}${toHex(g)}${toHex(b)}`; } function createColoredIcon(color, text) { const borderColor darkenColor(color); // HTML is hardcoded here and safe from user input const markerHtml ` div styleposition: relative; width: 32px; height: 32px;> div style background-color: ${color}; width: 100%; height: 100%; border-radius: 16px 16px 0; transform: rotate(45deg); border: 2px solid ${borderColor}; position: absolute; left: -16px; top: -16px; >/div> span style position: absolute; top: -8px; left: -8px; width: 16px; height: 16px; text-align: center; line-height: 16px; font-size: 11px; font-weight: bold; font-family: sans-serif; color: ${borderColor}; >${text}/span> /div> `; return L.divIcon({ className: my-custom-pin, iconAnchor: 0, 24, popupAnchor: 0, -36, html: markerHtml }); } function getCategoryShortCode(tipo) { if (!tipo) return ; const tipoUpper tipo.toUpperCase(); if (tipoUpper.includes(MOVILIDAD REDUCIDA)) return APAR; if (tipoUpper.includes(PARADAS DE AUTOBÚS)) return EMT; if (tipoUpper.includes(FARMACIA)) return FAR; if (tipoUpper.includes(ÁREAS INFANTILES)) return INF; if (tipoUpper.includes(EDUCATIVOS)) return EDU; if (tipoUpper.includes(DESFIBRILADOR)) return DEA; if (tipoUpper.includes(CAM CENTROS DE ATENCIÓN SOCIAL)) return SOC; if (tipoUpper.includes(ÁREAS CANINAS)) return ACA; if (tipoUpper.includes(BICIMAD)) return BIC; if (tipoUpper.includes(AYTO MADRID CENTROS DE ATENCIÓN SOCIAL)) return CAS; if (tipoUpper.includes(PISCINAS)) return PISC; if (tipoUpper.includes(POLIDEPORTIVOS)) return PVO; if (tipoUpper.includes(PARADAS DE TAXI)) return TAXI; if (tipoUpper.includes(BOMBEROS)) return BOM; if (tipoUpper.includes(ACCESOS A ESTACIONES DE METRO)) return METR; if (tipoUpper.includes(ÁREAS DE ACTIVIDADES DEPORTIVAS)) return DEP; if (tipoUpper.includes(DISCAPACIDAD)) return DISC; if (tipoUpper.includes(RENFE)) return RENF; if (tipoUpper.includes(CULTURALES MUNICIPALES EN LOS PRÓXIMOS 100 DÍAS)) return A100; if (tipoUpper.includes(GRATUITAS EN BIBLIOTECAS MUNICIPALES EN LOS PRÓXIMOS 60 DÍAS)) return A60; if (tipoUpper.includes(BIBLIOTECAS)) return BIB; if (tipoUpper.includes(COMISARÍAS)) return POLI; if (tipoUpper.includes(CENTROS DE DÍA)) return DIA; if (tipoUpper.includes(ESTABLECIMIENTOS Y MERCADOS)) return MER; if (tipoUpper.includes(MONUMENTOS)) return MON; if (tipoUpper.includes(TEMPLOS E IGLESIAS CATÓLICAS)) return CAT; if (tipoUpper.includes(TEMPLOS E IGLESIAS NO CATÓLICAS)) return NOC; if (tipoUpper.includes(RESTAURANTES DE INTERÉS)) return REST; if (tipoUpper.includes(PUNTOS DE INTERÉS)) return PDI; if (tipoUpper.includes(CENTROS CULTURALES MUNICIPALES)) return CCM; if (tipoUpper.includes(MUSEOS)) return MUS; if (tipoUpper.includes(ALOJAMIENTOS)) return ALOJ; if (tipoUpper.includes(AGENDA TURÍSTICA)) return AGT; if (tipoUpper.includes(DEPORTE Y TURISMO)) return DPT; if (tipoUpper.includes(AGENDA DE ACTIVIDADES Y EVENTOS)) return EVEN; if (tipoUpper.includes(DIVERSIÓN Y ENTRETENIMIENTO)) return DIV; if (tipoUpper.includes(EDIFICIOS DE CARÁCTER MONUMENTAL)) return EDMO; if (tipoUpper.includes(AGENDA ACTIVIDADES DEPORTIVAS)) return ACTD; return ; } function getFixedCategoryColor(tipo) { if (!tipo) return #888888; if (categoryColorCachetipo) { return categoryColorCachetipo; } const hash getHash(tipo); const colorIndex Math.abs(hash) % light50Palette.length; const color light50PalettecolorIndex; categoryColorCachetipo color; return color; } const direccionInput document.getElementById(direccion); const resultsInfoDiv document.getElementById(resultsInfo); const filtrosDiv document.getElementById(filtros); const chatDiv document.getElementById(chat); const voiceInputBtn document.getElementById(voiceInputBtn); const mapTitle document.getElementById(map-title); const consultarBtn document.getElementById(consultarBtn); const suggestionsContainer document.getElementById(suggestions); const inputHint document.getElementById(input-hint); const loaderOverlay document.getElementById(loader-overlay); const loaderText document.getElementById(loader-text); /* INICIO: CÓDIGO DE DIRECCIONES DE INDEX.PHP */ const VIA_TOKENS CALLE,CL,C,C/,AVENIDA,AVDA,AVD,AV,PLAZA,PZA,PZ; const STOP_TOKENS DE,DEL,LA,EL,LOS,LAS; const VIA_PRIORITY CALLE,AVENIDA,PLAZA; function baseNormalize(s) { if (!s) return ; let t String(s).toUpperCase() .replace(/Ñ/g, ##N_TILDE##) .normalize(NFD).replace(/\u0300-\u036f/g,) .replace(/##N_TILDE##/g, Ñ) .replace(/ª/g,A).replace(/º/g,O) .replace(/\s+/g, ) .trim(); return t; } function stripTrailingNumber(s) { return s.replace(/\s+\d+\wºª/-*\s*$/,).trim(); } function stripTokensForCompare(norm) { if (!norm) return ; const parts norm.split( ).filter(Boolean); const filtered parts.filter(p > !VIA_TOKENS.includes(p) && !STOP_TOKENS.includes(p)); return filtered.join( ); } function makeStreetKey(s) { return stripTokensForCompare(baseNormalize(s)); } function detectViaFromUserInput(s) { const norm baseNormalize(s); for (const v of VIA_TOKENS) { const rx new RegExp(^+v+\\b); if (rx.test(norm)) { if (v.startsWith(A)) return AVENIDA; if (v.startsWith(P)) return PLAZA; return CALLE; } } return null; } function choosePreferredCandidate(candidates, userInput) { if (!candidates || candidates.length 0) return null; const userVia detectViaFromUserInput(userInput); if (userVia) { const hit candidates.find(c > baseNormalize(c).startsWith(userVia+ )); if (hit) return hit; } for (const via of VIA_PRIORITY) { const hit candidates.find(c > baseNormalize(c).startsWith(via+ )); if (hit) return hit; } return candidates0; } function normalizeStreetNameForLookup(name) { return baseNormalize(name); } let streetByKey new Map(); async function loadCallejero() { try { const response await fetch(callejero.txt); if (!response.ok) { console.error(Error al cargar callejero.txt:, response.statusText); return; } const text await response.text(); const rawLines text.split(\n).map(l > l.trim()).filter(Boolean); const streetsArray rawLines.map(line > normalizeStreetNameForLookup(line)); madridStreetsSet new Set(streetsArray); streetByKey new Map(); rawLines.forEach(line > { const norm normalizeStreetNameForLookup(line); const key makeStreetKey(norm); if (!streetByKey.has(key)) streetByKey.set(key, ); streetByKey.get(key).push(norm); }); } catch (error) { console.error(Error al cargar el archivo callejero.txt:, error); } } /* FIN: CÓDIGO DE DIRECCIONES DE INDEX.PHP */ // Función segura para crear elementos DOM function createSecureElement(tag, properties {}, children ) { const element document.createElement(tag); Object.entries(properties).forEach((key, value) > { if (key textContent) { element.textContent value; } else if (key innerHTML) { // ADVERTENCIA: Usar innerHTML solo con contenido de confianza. // Aquí deberíamos usar un sanitizador si la fuente no es 100% segura. element.innerHTML value; } else if (key style) { element.style.cssText value; } else if (key.startsWith(on)) { // Event handlers elementkey value; } else { element.setAttribute(key, value); } }); children.forEach(child > { element.appendChild(typeof child string ? document.createTextNode(child) : child); }); return element; } function initMap() { if (mymap) mymap.remove(); // Aseguramos que Leaflet cargue mymap L.map(mapid).setView(40.416775, -3.703790, 13); L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png, { maxZoom: 19, attribution: © OpenStreetMap contributors }).addTo(mymap); markerGroup L.featureGroup().addTo(mymap); } function resetResultsUI() { chatDiv.innerHTML ; filtrosDiv.innerHTML ; resultsInfoDiv.innerHTML ; mapTitle.style.display none; document.getElementById(mapid).style.display none; } async function doQueryDireccion(direccionCanonica) { consultarBtn.disabled true; loaderOverlay.style.display flex; searchCompleted false; const startTime Date.now(); loadingInterval setInterval(() > { const dots ..repeat(Math.floor(Date.now() / 300) % 4); const secondsElapsed Math.floor((Date.now() - startTime) / 1000); loaderText.innerText `Buscando servicios cercanos${dots} (${secondsElapsed}s)`; }, 300); try { const response await fetch(proxy.php, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ solicitud: direccionCanonica }) }); if (!response.ok) { if(response.status 429) { throw new Error(Demasiadas peticiones.); } throw new Error(`Error de red o del servidor: ${response.status} ${response.statusText}`); } const data await response.json(); // Validación básica de la estructura de la respuesta if (!data || !Array.isArray(data) || data.length 2) { chatDiv.appendChild(createSecureElement(div, { class: warning-message, textContent: No se encontraron servicios cercanos. })); return; } userLocation { lat: parseFloat(data0.userLat), lng: parseFloat(data0.userLng) }; datosServicios data.slice(1); if (!userLocation.lat || !userLocation.lng) { chatDiv.appendChild(createSecureElement(div, { class: warning-message, textContent: No se pudo determinar la ubicación. })); return; } mapTitle.textContent `Ubicación introducida: ${direccionCanonica}`; mapTitle.style.display block; document.getElementById(mapid).style.display block; if (mymap) { mymap.invalidateSize(); } // Sanitización de categorías recibidas categorias new Set(datosServicios.map(item > item.TIPO ? escapeHtml(item.TIPO) : ).filter(Boolean)); const distanciasMinimas {}; datosServicios.forEach(item > { const tipo item.TIPO ? escapeHtml(item.TIPO) : ; const dist parseFloat(item.distancia); if (!isNaN(dist) && (!distanciasMinimastipo || dist distanciasMinimastipo)) { distanciasMinimastipo dist; } }); const categoriasOrdenadas Array.from(categorias).sort((a, b) > (distanciasMinimasa || 0) - (distanciasMinimasb || 0)); filtrosDiv.innerHTML ; const todosInput createSecureElement(input, { type: checkbox, name: categoria, id: cat_all, value: __ALL__}); todosInput.onchange (event) > { selectedCategories.clear(); document.querySelectorAll(.filters inputtypecheckbox:not(#cat_all)).forEach(cb > cb.checked false); if (event.target.checked) { selectedCategories.add(__ALL__); } updateFilteredResults(); mapTitle.scrollIntoView({ behavior: smooth, block: start }); }; filtrosDiv.appendChild(todosInput); const todosLabel createSecureElement(label, { for: cat_all, innerHTML: 🏆 Top 100 resultados }); filtrosDiv.appendChild(todosLabel); categoriasOrdenadas.forEach((cat) > { const id `cat_${cat.replace(/\s/g, _)}`; const input createSecureElement(input, { type: checkbox, id: id, value: cat }); input.onchange (event) > { if (event.target.checked) { selectedCategories.add(cat); document.getElementById(cat_all).checked false; selectedCategories.delete(__ALL__); } else { selectedCategories.delete(cat); } updateFilteredResults(); mapTitle.scrollIntoView({ behavior: smooth, block: start }); }; const label createSecureElement(label, { for: id, textContent: cat }); if (distanciasMinimascat) { label.appendChild(createSecureElement(div, { class: distance-info, textContent: `🚶 ${Math.round(distanciasMinimascat)} metros` })); } filtrosDiv.appendChild(input); filtrosDiv.appendChild(label); }); selectedCategories.clear(); selectedCategories.add(__ALL__); updateFilteredResults(true); searchCompleted true; } catch (error) { console.error(Error en doQueryDireccion:, error); chatDiv.appendChild(createSecureElement(div, { class: error-message, textContent: Se ha producido un error al realizar la búsqueda. Por favor, inténtalo de nuevo. })); } finally { clearInterval(loadingInterval); loaderOverlay.style.display none; consultarBtn.disabled false; } } async function consultarServicios() { resetResultsUI(); const raw direccionInput.value.trim(); if (!raw) { chatDiv.appendChild(createSecureElement(div, { class: error-message, textContent: Por favor, introduce una dirección válida. })); return; } const numMatch raw.match(/(\d+\wºª/-*)\s*$/); const streetPart baseNormalize(numMatch ? raw.replace(numMatch0, ).trim() : raw); const numberPart numMatch ? numMatch1 : null; const key makeStreetKey(streetPart); const candidates streetByKey.get(key) || ; if (!numberPart) { if (candidates.length > 0) { const preview candidates.slice(0, 5).join( · ); const warningDiv createSecureElement(div, { class: warning-message }); warningDiv.innerHTML `He encontrado b>${escapeHtml(candidates.length)}/b> posible(s) calle(s) para b>${escapeHtml(streetPart)}/b>:br>${escapeHtml(preview)}br>br>b>Indica el número/b> para continuar (ej.: b>${escapeHtml(streetPart)} 11/b>).`; chatDiv.appendChild(warningDiv); return; } chatDiv.appendChild(createSecureElement(div, { class: error-message, textContent: `No encuentro esa vía en el callejero. Selecciónala del desplegable o revisa la escritura.` })); return; } const normFullStreet baseNormalize(streetPart); if (madridStreetsSet.has(normFullStreet)) { const direccionCanonica `${normFullStreet} ${numberPart}`; await doQueryDireccion(direccionCanonica); return; } if (candidates.length > 0) { const chosen choosePreferredCandidate(candidates, raw); const direccionCanonica `${chosen} ${numberPart}`; direccionInput.value direccionCanonica; await doQueryDireccion(direccionCanonica); return; } chatDiv.appendChild(createSecureElement(div, { class: error-message, textContent: `No encuentro esa vía en el callejero. Selecciónala del desplegable o revisa la escritura.` })); } function updateFilteredResults(isInitialSearch false) { chatDiv.innerHTML ; markerGroup.clearLayers(); markers ; if (isInitialSearch) { document.getElementById(cat_all).checked true; } const userMarker L.marker(userLocation.lat, userLocation.lng, { icon: userIcon, zIndexOffset: 1000 }).bindPopup(b>📍 La ubicación que has indicado/b>); userMarker.on(mouseover, function () { this.openPopup(); }).on(mouseout, function () { this.closePopup(); }); markerGroup.addLayer(userMarker); const isTop100Mode selectedCategories.has(__ALL__); let filtrados ; if (isTop100Mode) { filtrados datosServicios.slice(0, 100); } else if (selectedCategories.size > 0) { filtrados datosServicios.filter(item > selectedCategories.has(item.TIPO)); } const count filtrados.length; resultsInfoDiv.innerHTML ; const p1 createSecureElement(p); // PREVENCIÓN DOM XSS: Usamos textContent/escapeHtml en lugar de innerHTML directo con input usuario const direccionBuscada escapeHtml(direccionInput.value.trim().toUpperCase()); const direccionTextPart direccionBuscada ? ` a b>${direccionBuscada}/b>` : ; let titleText ; let categoryTextPart ; if (isTop100Mode) { titleText `los b>${count} servicios públicos más cercanos/b>`; categoryTextPart todas las categorías; } else if (selectedCategories.size > 0) { const categoryNames `b>${Array.from(selectedCategories).join(, )}/b>`; titleText `los b>${count} servicios públicos de ${categoryNames} más cercanos/b>`; categoryTextPart Array.from(selectedCategories).join(, ); } else { titleText b>ningún servicio público/b>. Selecciona una o más categorías.; categoryTextPart ninguna categoría; } // Construcción segura del HTML informativo p1.innerHTML `Basándome en b>Datos Abiertos del Ayuntamiento de Madrid/b>, y otros, he encontrado ${titleText}${direccionTextPart}, ordenados por distancia.`; const p2 createSecureElement(p, { style: color: black; font-weight: bold; }); p2.textContent selectedCategories.size > 0 ? `Pulsa en los elementos de la lista para verlos en el mapa. Selecciona una o más categorías para filtrar los resultados.` : ``; resultsInfoDiv.appendChild(p1); resultsInfoDiv.appendChild(p2); mapTitle.innerHTML `Mostrando las b>${count}/b> ubicaciones de b>${categoryTextPart}/b> más próximas:`; if (count 0) { if (isInitialSearch) { resultsInfoDiv.scrollIntoView({ behavior: smooth, block: start }); } mymap.setView(userLocation.lat, userLocation.lng, 15); return; } let bounds L.latLngBounds(); bounds.extend(userLocation.lat, userLocation.lng); filtrados.forEach((item, index) > { // Sanitización de datos provenientes del proxy const nombre item.NOMBRE ? escapeHtml(item.NOMBRE) : Sin nombre; const tipo item.TIPO ? escapeHtml(item.TIPO) : Tipo desconocido; const direccion item.DIRECCION ? escapeHtml(item.DIRECCION) : Dirección no disponible; const distancia Math.round(parseFloat(item.distancia)) + metros; const url item.URL || #; // Observaciones contiene HTML rico (iframes, etc). No escapamos pero confiamos en la fuente. const observaciones item.OBSERVACIONES || No hay observaciones.; const itemDiv createSecureElement(div, { class: item, id: `item-${index}`}); itemDiv.onmouseover () > highlightMarker(index, true); itemDiv.onmouseout () > highlightMarker(index, false); const countSpan createSecureElement(span, { class: item-count, textContent: `${index + 1} de ${filtrados.length}` }); const upBtn createSecureElement(button, { class: scroll-to-map, title: Ir al resumen de resultados, ariaLabel: Ir al resumen de resultados }); upBtn.innerHTML `svg viewBox0 0 24 24 width18 height18 aria-hiddentrue>path dM7 14l5-5 5 5 fillnone strokecurrentColor stroke-width2 stroke-linecapround stroke-linejoinround/>path dM7 20l5-5 5 5 fillnone strokecurrentColor stroke-width2 stroke-linecapround stroke-linejoinround/>/svg>`; upBtn.addEventListener(click, (ev) > { ev.stopPropagation(); const mapTitle document.getElementById(map-title) || document.querySelector(data-map-title) || document.querySelector(h2); if (mapTitle) { mapTitle.setAttribute(tabindex, -1); mapTitle.focus({ preventScroll: true }); mapTitle.scrollIntoView({ behavior: smooth, block: start }); setTimeout(() > mapTitle.removeAttribute(tabindex), 1000); } }); const actions createSecureElement(div, { class: item-actions }, countSpan, upBtn); const header createSecureElement(div, { class: item-header }, createSecureElement(span, { textContent: tipo }), actions ); // -------- CONTENIDO DE LA TARJETA (con Horario opcional) -------- const content createSecureElement(div, { class: item-content }); // HTML Base seguro const baseHtml `📌 em>${nombre}/em>br>🏠 ${direccion}br>📏 Distancia: ${distancia}`; content.innerHTML baseHtml; const horarioVal (item.HORARIO ?? ).toString().trim(); if (horarioVal) { const horarioLine document.createElement(div); horarioLine.textContent `🕒 Horario: ${horarioVal}`; content.appendChild(horarioLine); } const obsLabel document.createElement(span); obsLabel.innerHTML br>ℹ️ Observaciones: ; content.appendChild(obsLabel); const obsContainer document.createElement(div); obsContainer.style.display inline; // Insertamos observaciones. // NOTA DE SEGURIDAD: Esto asume que el backend (proxy.php) devuelve HTML limpio. // Si el backend fuera comprometido, esto sería un vector de ataque. obsContainer.innerHTML observaciones; // Restringir estilos de iframes inyectados para evitar rotura de layout obsContainer.querySelectorAll(iframe, video, div, object, embed).forEach(el > { el.style.cssText `position: relative !important; width: 100% !important; max-width: 100% !important; height: auto !important; left: auto !important; top: auto !important; margin: 10px 0 !important; border-radius: 8px !important; border: none !important; z-index: 1 !important;`; }); content.appendChild(obsContainer); if (url && url ! #) { // Validación básica de URL para evitar javascript: alert(1) const safeUrl url.toLowerCase().startsWith(http) ? url : #; content.appendChild( createSecureElement(a, { href: safeUrl, target: _blank, class: map-link, innerHTML: br>🌐 Cómo llegar a este servicio con tu aplicación de mapas favorita }) ); } // ------------------------------------------------------------------ itemDiv.appendChild(header); itemDiv.appendChild(content); chatDiv.appendChild(itemDiv); if (item.latitud && item.longitud) { const lat parseFloat(item.latitud); const lon parseFloat(item.longitud); if (!isNaN(lat) && !isNaN(lon)) { const color getFixedCategoryColor(tipo); const shortCode getCategoryShortCode(tipo); const icon createColoredIcon(color, shortCode); const marker L.marker(lat, lon, {icon: icon}).addTo(markerGroup); // Popup contenido seguro marker.bindPopup(`b>${nombre}/b>br>em>${tipo}/em>br>${direccion}br>Distancia: ${distancia}`); marker.on(mouseover, function () { this.openPopup(); }); marker.on(mouseout, function () { this.closePopup(); }); marker.on(click, () > { highlightListItem(index); }); markersindex marker; bounds.extend(lat, lon); } } }); if (bounds.isValid() && markers.length > 0) { mymap.fitBounds(bounds, { padding: 50, 50 }); } if (isInitialSearch) { resultsInfoDiv.scrollIntoView({ behavior: smooth, block: start }); } } function highlightMarker(index, isHighlighted) { if (markersindex) { isHighlighted ? markersindex.setZIndexOffset(2000).openPopup() : markersindex.setZIndexOffset(0).closePopup(); } } function highlightListItem(index) { const item document.getElementById(`item-${index}`); if (item) { const header item.querySelector(.item-header) || item; header.setAttribute(tabindex, -1); header.focus({ preventScroll: true }); header.scrollIntoView({ behavior: smooth, block: start }); item.classList.add(item-highlighted); setTimeout(() > { item.classList.remove(item-highlighted); header.removeAttribute(tabindex); }, 10000); } } // Prevención de spam en correo (Obfuscación simple JS) function openMail(e) { e.preventDefault(); const user info; const domain accesibilimad.com; window.location.href mailto: + user + @ + domain; } document.addEventListener(DOMContentLoaded, () > { initMap(); loadCallejero(); direccionInput.addEventListener(focus, () > { if (searchCompleted) { direccionInput.value ; searchCompleted false; } }); // Activar listener de correo const mailLink document.getElementById(secure-mail-link); if(mailLink) { mailLink.addEventListener(click, openMail); } }); direccionInput.addEventListener(keypress, function(event) { if (event.key Enter) { event.preventDefault(); consultarServicios(); } }); direccionInput.addEventListener(input, () > { const queryOriginal direccionInput.value; const hasNumber /\b\d+\wºª/-*\b/.test(queryOriginal); suggestionsContainer.innerHTML ; suggestionsContainer.style.display none; if (hasNumber) { inputHint.style.opacity 0; inputHint.style.height 0; inputHint.style.marginBottom 0; return; } const qNoNum stripTrailingNumber(queryOriginal); const qKey makeStreetKey(qNoNum); if (qKey.length 2) { return; } const matches ; for (const key, streets of streetByKey.entries()) { if (key.includes(qKey)) { streets.forEach(s > matches.push(s)); } } if (matches.length > 0) { suggestionsContainer.style.display block; matches.sort(); matches.forEach(match > { const suggestionItem createSecureElement(div, { class: suggestion-item }); const mKey makeStreetKey(match); const idx mKey.indexOf(qKey); // Resaltado seguro (escapeHtml para prevenir inyeccion en sugerencias si el archivo txt es manipulado) if (idx > 0) { const pre escapeHtml(mKey.substring(0, idx)); const mid escapeHtml(mKey.substring(idx, idx + qKey.length)); const post escapeHtml(mKey.substring(idx + qKey.length)); suggestionItem.innerHTML `${escapeHtml(match)}br>small>${pre}b>${mid}/b>${post}/small>`; } else { suggestionItem.textContent match; } suggestionItem.addEventListener(click, () > { direccionInput.value match + ; suggestionsContainer.innerHTML ; suggestionsContainer.style.display none; direccionInput.focus(); inputHint.style.height 1.2em; inputHint.style.marginBottom 10px; inputHint.innerHTML ¡Genial! Ahora, añade el número y pulsa Consultar.; inputHint.style.opacity 1; setTimeout(() > { inputHint.style.opacity 0; inputHint.style.height 0; inputHint.style.marginBottom 0; }, 4000); }); suggestionsContainer.appendChild(suggestionItem); }); } }); const SpeechRecognition window.SpeechRecognition || window.webkitSpeechRecognition; if (SpeechRecognition) { const recognition new SpeechRecognition(); recognition.lang es-ES; recognition.continuous false; recognition.interimResults false; voiceInputBtn.addEventListener(click, () > { recognition.start(); }); recognition.onstart () > { voiceInputBtn.classList.add(recording); voiceInputBtn.title Escuchando...; }; recognition.onend () > { voiceInputBtn.classList.remove(recording); voiceInputBtn.title Dictar dirección; }; recognition.onresult (event) > { // Validación básica del input de voz let transcript event.results00.transcript; transcript transcript.replace(/>/g, ); // Eliminar caracteres peligrosos direccionInput.value transcript; consultarServicios(); }; recognition.onerror (event) > { console.error(Error en el reconocimiento de voz:, event.error); alert(`Error en el micrófono: ${event.error}`); }; } else { console.warn(El reconocimiento de voz no es compatible con este navegador.); voiceInputBtn.disabled true; voiceInputBtn.title Función no compatible en este navegador; } /script> footer classmain-footer> div classfooter-columns> div classfooter-col> h3>AccesibiliMad/h3> div styledisplay: flex; gap: 12px; align-items: flex-start; margin-bottom: 15px;> span stylefont-size: 1.6em; line-height: 1; flex-shrink: 0;>🏆/span> div styleline-height: 1.5;> Galardonada con el segundo premio en la categoría de Visualizaciones de los strong>Premios a la Reutilización de Datos Abiertos 2025/strong> del Ayuntamiento de Madrid. /div> /div> p> a hrefhttps://www.madrid.es/portales/munimadrid/es/Inicio/Actualidad/Noticias/El-Ayuntamiento-de-Madrid-entrega-los-I-Premios-a-la-Reutilizacion-de-Datos-Abiertos-2025/?vgnextfmtdefault&vgnextoid95a0712a35eda910VgnVCM200000f921e388RCRD&vgnextchannela12149fa40ec9410VgnVCM100000171f5a0aRCRD target_blank relnoopener noreferrer> Ver noticia oficial » /a> /p> /div> div classfooter-col> h3>Enlaces útiles/h3> ul> li>a hrefhttps://datos.madrid.es/portal/site/egob target_blank relnoopener noreferrer>Datos abiertos del Ayuntamiento de Madrid/a>/li> li>a hrefhttps://datos.emtmadrid.es/ target_blank relnoopener noreferrer>Datos abiertos EMT Madrid/a>/li> li>a hrefhttps://www.comunidad.madrid/gobierno/datos-abiertos target_blank relnoopener noreferrer>Datos abiertos Comunidad de Madrid/a>/li> li>a hrefhttps://data-crtm.opendata.arcgis.com/ target_blank relnoopener noreferrer>Datos abiertos Consorcio de Transportes/a>/li> li>a hrefhttps://data.renfe.com/ target_blank relnoopener noreferrer>Datos abiertos RENFE/a>/li> /ul> /div> div classfooter-col> h3>Información/h3> p> ¿Tienes sugerencias o dudas? /p> p> a href# idsecure-mail-link>📧 Contacto/a> /p> /div> /div> div classfooter-bottom> Última actualización: 01/03/2026 a las 00:00h GMT+1 /div>/footer>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]