{"id":15167,"date":"2025-07-08T08:46:59","date_gmt":"2025-07-08T06:46:59","guid":{"rendered":"https:\/\/www.investincastellon.com\/interactive-map\/"},"modified":"2026-02-09T11:44:28","modified_gmt":"2026-02-09T09:44:28","slug":"interactive-map","status":"publish","type":"page","link":"https:\/\/www.investincastellon.com\/en\/interactive-map\/","title":{"rendered":"Interactive Map"},"content":{"rendered":"<p>[et_pb_section fb_built=&#8221;1&#8243; theme_builder_area=&#8221;post_content&#8221; _builder_version=&#8221;4.27.4&#8243; _module_preset=&#8221;default&#8221; custom_margin=&#8221;0px||0px||false|false&#8221; custom_padding=&#8221;0px||0px||false|false&#8221; hover_enabled=&#8221;0&#8243; sticky_enabled=&#8221;0&#8243;][et_pb_row _builder_version=&#8221;4.27.4&#8243; _module_preset=&#8221;default&#8221; theme_builder_area=&#8221;post_content&#8221; custom_margin=&#8221;0px||0px||false|false&#8221; custom_padding=&#8221;0px||0px||false|false&#8221; width=&#8221;100%&#8221; max_width=&#8221;100%&#8221; make_equal=&#8221;on&#8221; hover_enabled=&#8221;0&#8243; sticky_enabled=&#8221;0&#8243;][et_pb_column _builder_version=&#8221;4.27.4&#8243; _module_preset=&#8221;default&#8221; type=&#8221;4_4&#8243; theme_builder_area=&#8221;post_content&#8221;][et_pb_text _builder_version=&#8221;4.27.4&#8243; _module_preset=&#8221;default&#8221; theme_builder_area=&#8221;post_content&#8221; hover_enabled=&#8221;0&#8243; sticky_enabled=&#8221;0&#8243;]<\/p>\n<div id=\"mapaempresas\">\n  <div id=\"info\">\n    <div id=\"filtros-sectores\"><\/div>\n    <div class=\"divider horizontal\"><\/div>\n    <div id=\"lista-empresas\"><\/div>\n    <div id=\"paginacion\"><\/div>\n  <\/div>\n\n  <div class=\"divider vertical\"><\/div>\n\n  <div id=\"mapa\"><\/div>\n\n  <div id=\"mapa-loading\">\n    <div class=\"spinner\"><\/div>\n    <span>Loading companies...<\/span>\n  <\/div>\n<\/div>\n\n<!-- Google Maps (Advanced Markers) -->\n<script src=\"https:\/\/maps.googleapis.com\/maps\/api\/js?key=AIzaSyCn34R5hYrnNEKyDYcbB8K5BzeUoM69yfk&libraries=marker\"><\/script>\n\n<!-- MarkerClusterer v2 -->\n<script src=\"https:\/\/unpkg.com\/@googlemaps\/markerclusterer\/dist\/index.min.js\"><\/script>\n\n<script>\ngoogle.maps.importLibrary(\"marker\").then(() => {\n  document.addEventListener(\"DOMContentLoaded\", async () => {\n\n    \/* =========================\n       MAPA\n    ========================== *\/\n    const mapa = new google.maps.Map(document.getElementById(\"mapa\"), {\n      center: { lat: 39.986, lng: -0.039 },\n      zoom: 8,\n      maxZoom: 17,\n      gestureHandling: \"greedy\",\n      mapId: \"Clave de API 3\" \/\/ \u26a0\ufe0f Map ID real, NO la API key\n    });\n\n    const sectorColors = {\n      \"Agricultura y Ganaderia\": \"#89DC65\",\n      \"Construcci\u00f3n\": \"#FFE46A\",\n      \"Distribuci\u00f3n comercial\": \"#00AED3\",\n      \"Industria\": \"#004868\",\n      \"Servicios\": \"#FF666F\",\n\t  \"Agriculture and Livestock\": \"#89DC65\",\n      \"Construction\": \"#FFE46A\",\n      \"Commercial distribution\": \"#00AED3\",\n      \"Industry\": \"#004868\",\n      \"Services\": \"#FF666F\"\n    };\n    const colorSector = (s) => sectorColors[s] || \"#999\";\n\n    \/* =========================\n       DOM \/ ESTADO\n    ========================== *\/\n    const filtrosContainer = document.getElementById(\"filtros-sectores\");\n    const listaContainer   = document.getElementById(\"lista-empresas\");\n    const paginacion       = document.getElementById(\"paginacion\");\n\n    const empresasPorPagina = 12;\n    let paginaActual = 1;\n\n    \/\/ filtros:\n    \/\/ - sector___subsector -> boolean\n    \/\/ - tipo_xxx -> boolean\n    \/\/ - tam_xxx -> boolean\n    \/\/ - comarca_xxx -> boolean\n    \/\/ - fac_xxx -> boolean   \u2705 NUEVO\n    \/\/ - _texto -> string\n    const filtros = {\n\t  _texto: \"\",\n\t  facturacion_min: null,\n\t  facturacion_max: null\n\t};\n\n    const markers = [];\n    let clusterer = null;\n\tlet infoWindowActiva = null;\n\n\n    \/* =========================\n       COMARCAS: mapping exacto tuyo\n    ========================== *\/\n    const mapaComarcas = {\n      \"El Alto Palancia\": \"alto_palancia.geojson\",\n      \"El Alto Mijares\": \"el_alto_mijares.geojson\",\n      \"El Baix Maestrat\": \"el_baix_maestrat.geojson\",\n      \"Els Ports\": \"els_ports.geojson\",\n      \"L\u2019Alcalat\u00e9n\": \"l_alcalaten.geojson\",\n      \"L\u2019Alt Maestrat\": \"l_alt_maestrat.geojson\",\n      \"La Plana Alta\": \"la_plana_alta.geojson\",\n      \"La Plana Baixa\": \"la_plana_baixa.geojson\"\n    };\n\n    \/\/ capas: nombre comarca -> google.maps.Data\n    const capasComarcas = {};\n\n    \/* =========================\n       HELPERS\n    ========================== *\/\n    function crearIcono(sector) {\n      const div = document.createElement(\"div\");\n      div.style.width = \"18px\";\n      div.style.height = \"18px\";\n      div.style.borderRadius = \"50%\";\n      div.style.background = colorSector(sector);\n      div.style.border = \"2px solid #fff\";\n      return div;\n    }\n\n    function claveSubsector(sector, subsector) {\n      return `${sector}___${subsector || \"\u2014\"}`;\n    }\n\n    function hayFiltrosActivosSubsectores() {\n      return Object.keys(filtros).some(k => k.includes(\"___\") && filtros[k] === true);\n    }\n\n    function hayFiltrosActivosTipo() {\n      return Object.keys(filtros).some(k => k.startsWith(\"tipo_\") && filtros[k] === true);\n    }\n\n    function hayFiltrosActivosTamano() {\n      return Object.keys(filtros).some(k => k.startsWith(\"tam_\") && filtros[k] === true);\n    }\n\n    function hayFiltrosActivosComarca() {\n      return Object.keys(filtros).some(k => k.startsWith(\"comarca_\") && filtros[k] === true);\n    }\n\n    function hayFiltrosActivosFacturacion() { \/\/ \u2705 NUEVO\n      return Object.keys(filtros).some(k => k.startsWith(\"fac_\") && filtros[k] === true);\n    }\n\n    function hayTextoActivo() {\n      return typeof filtros._texto === \"string\" && filtros._texto.trim().length > 0;\n    }\n\n    function obtenerClaveTamano(n) {\n      if (n === null) return \"tam_sin_dato\";\n      if (n >= 1 && n <= 9) return \"tam_micro\";\n      if (n >= 10 && n <= 49) return \"tam_pequena\";\n      if (n >= 50 && n <= 249) return \"tam_mediana\";\n      if (n >= 250) return \"tam_grande\";\n      return \"tam_sin_dato\";\n    }\n\t\n\t\/\/ Diccionario de tipos (traducci\u00f3n v\u00eda PHP -> WPML String Translation)\n\tconst TIPO_LABELS = {\n\t  importador: i18nMapa.importador,\n\t  exportador: i18nMapa.exportador,\n\t  importador_exportador: i18nMapa.importadorExportador,\n\t  no_realiza_actividad_exterior: i18nMapa.noRealizaActividadExterior,\n\t  sin_especificar: i18nMapa.sinEspecificar\n\t};\n\n\tfunction traducirTipo(slug) {\n\t  const key = (slug && String(slug).trim()) ? slug : \"sin_especificar\";\n\t  return TIPO_LABELS[key] || TIPO_LABELS.sin_especificar;\n\t}\n\n\n    \/\/ \u2705 NUEVO: normalizar para usar keys seguras en filtros (accents, espacios, etc.)\n    function slugify(str) {\n      return String(str || \"\")\n        .trim()\n        .toLowerCase()\n        .normalize(\"NFD\").replace(\/[\\u0300-\\u036f]\/g, \"\") \/\/ quita tildes\n        .replace(\/[\u2019'\"]\/g, \"\")                           \/\/ quita comillas\/ap\u00f3strofos\n        .replace(\/[^a-z0-9]+\/g, \"_\")                      \/\/ todo a _\n        .replace(\/^_+|_+$\/g, \"\");\n    }\n\t\n\tfunction formatearEmpleados(n) {\n\t  if (n === null) return i18nMapa.sinDato;\n\t  if (n <= 9) return i18nMapa.micro;\n\t  if (n <= 49) return i18nMapa.pequena;\n\t  if (n <= 249) return i18nMapa.mediana;\n\t  return i18nMapa.grande;\n\t}\n\n\tfunction formatearFacturacion(n) {\n\t  if (!n) return \"Sin dato\";\n\t  if (n >= 1e6) return `${(n \/ 1e6).toFixed(1)} M\u20ac`;\n\t  if (n >= 1e3) return `${(n \/ 1e3).toFixed(0)} K\u20ac`;\n\t  return `${n} \u20ac`;\n\t}\n\n\n    \/* =========================\n       LOADING\n    ========================== *\/\n    const loadingEl = document.getElementById(\"mapa-loading\");\n    if (loadingEl) loadingEl.style.display = \"flex\";\n\n    \/* =========================\n       FETCH DATOS\n    ========================== *\/\n    \/*const empresas = await fetch(\"\/wp-json\/miapi\/v1\/empresas\/\").then(r => r.json());*\/\n\tconst lang = document.documentElement.lang.startsWith(\"en\") ? \"en\" : \"es\";\n\tconst url_lang = document.documentElement.lang.startsWith(\"en\") ? \"\/en\/wp-json\/miapi\/v1\/empresas\/\" : \"\/wp-json\/miapi\/v1\/empresas\/\";\n\tconst empresas = await fetch(\n\t  `${url_lang}`\n\t).then(r => r.json());\n\n    \/* =========================\n       CARGA DE CAPAS GEOJSON (como lo ten\u00edas)\n       - se cargan una vez, pero NO se muestran hasta marcar\n    ========================== *\/\n    Object.entries(mapaComarcas).forEach(([nombre, archivo]) => {\n      const capa = new google.maps.Data({ map: null });\n      capasComarcas[nombre] = capa;\n\n      capa.setStyle({\n        fillColor: \"#004868\",\n        fillOpacity: 0.08,\n        strokeColor: \"#004868\",\n        strokeOpacity: 0.9,\n        strokeWeight: 1.5\n      });\n\n      capa.loadGeoJson(`\/wp-content\/themes\/Divi-Child\/geodata\/${archivo}`);\n    });\n\n    \/* =========================\n       CREAR MARCADORES\n    ========================== *\/\n    const bounds = new google.maps.LatLngBounds();\n\n    empresas.forEach(emp => {\n      const sector = emp.sector || \"Otros\";\n      const subsector = emp.subsector || \"\u2014\";\n\n      const lat = +emp.lat;\n      const lng = +emp.lng;\n      if (!isFinite(lat) || !isFinite(lng)) return;\n\n      \/\/ \u2705 NUMERO DE EMPLEADOS\n      let empleados = parseInt(emp.numero_de_empleados, 10);\n      if (isNaN(empleados) || empleados <= 0) empleados = null;\n\n      \/\/ \u2705 FACTURACI\u00d3N (guardar tal cual venga)\n      let facturacion = parseFloat(emp.facturacion);\n\t  if (isNaN(facturacion) || facturacion <= 0) facturacion = null;\n\n      const marker = new google.maps.marker.AdvancedMarkerElement({\n        map: mapa,\n        position: { lat, lng },\n        content: crearIcono(sector)\n      });\n\n      marker._data = {\n        nombre: emp.nombre || \"\",\n        sector,\n        subsector,\n        tipo_slug: emp.tipo_slug || \"sin_especificar\",\n        tipo_label: emp.tipo_label || \"Sin especificar\",\n        web: emp.web || \"\",\n        empleados,\n        facturacion, \/\/ \u2705 NUEVO\n        comarcas: Array.isArray(emp.comarcas) ? emp.comarcas : []\n      };\n\n      const d = marker._data;\n\n\t\tconst info = `\n\t\t  <div class=\"infowindow-empresa\">\n\t\t\t<strong class=\"iw-titulo\">${d.nombre}<\/strong>\n\n\t\t\t<div class=\"iw-bloque\">\n\t\t\t  <em>${d.sector}${d.subsector && d.subsector !== \"\u2014\" ? \" \u00b7 \" + d.subsector : \"\"}<\/em>\n\t\t\t<\/div>\n\n\t\t\t<div class=\"iw-bloque\">\n\t\t\t  <strong>` + i18nMapa.tipoEmpresa + `:<\/strong> ${traducirTipo(d.tipo_slug)}\n\t\t\t<\/div>\n\n\t\t\t<div class=\"iw-bloque\">\n\t\t\t  <strong>` + i18nMapa.tamanoEmpresa + `:<\/strong> ${formatearEmpleados(d.empleados)}\n\t\t\t<\/div>\n\n\t\t\t<div class=\"iw-bloque\">\n\t\t\t  <strong>` + i18nMapa.facturacion + ` (\u20ac):<\/strong> ${formatearFacturacion(d.facturacion)}\n\t\t\t<\/div>\n\n\t\t\t${\n\t\t\t  d.comarcas && d.comarcas.length\n\t\t\t\t? `<div class=\"iw-bloque\">\n\t\t\t\t\t <strong>` + i18nMapa.comarcas + `${d.comarcas.length > 1 ? \"s\" : \"\"}:<\/strong>\n\t\t\t\t\t ${d.comarcas.join(\", \")}\n\t\t\t\t   <\/div>`\n\t\t\t\t: \"\"\n\t\t\t}\n\n\t\t\t${\n\t\t\t  d.web\n\t\t\t\t? `<div class=\"iw-bloque\">\n\t\t\t\t\t <a href=\"${d.web}\" target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t\t\t\t   ` + i18nMapa.visitarWeb + `\n\t\t\t\t\t <\/a>\n\t\t\t\t   <\/div>`\n\t\t\t\t: \"\"\n\t\t\t}\n\t\t  <\/div>\n\t\t`;\n\n      marker._infoWindow = new google.maps.InfoWindow({ content: info });\n\n\t\tmarker.addListener(\"click\", () => {\n\n\t\t  \/\/ \ud83d\udd12 cerrar la anterior si existe\n\t\t  if (infoWindowActiva) {\n\t\t\tinfoWindowActiva.close();\n\t\t  }\n\n\t\t  \/\/ abrir la actual\n\t\t  marker._infoWindow.open({\n\t\t\tmap: mapa,\n\t\t\tanchor: marker\n\t\t  });\n\n\t\t  \/\/ guardar referencia\n\t\t  infoWindowActiva = marker._infoWindow;\n\t\t});\n\n\n\n      markers.push(marker);\n      bounds.extend({ lat, lng });\n    });\n\n    if (!bounds.isEmpty()) mapa.fitBounds(bounds);\n\n    \/* =========================\n       CLUSTER (COLOR POR SECTOR DOMINANTE)\n    ========================== *\/\n    clusterer = new markerClusterer.MarkerClusterer({\n      map: mapa,\n      markers: [],\n      algorithm: new markerClusterer.SuperClusterAlgorithm({ maxZoom: 11 }),\n      renderer: {\n        render: ({ count, markers: clusterMarkers, position }) => {\n          const sectorCounts = {};\n          clusterMarkers.forEach(m => {\n            const s = m._data?.sector || \"Otros\";\n            sectorCounts[s] = (sectorCounts[s] || 0) + 1;\n          });\n\n          let mainSector = \"Otros\";\n          let max = 0;\n          Object.entries(sectorCounts).forEach(([s, n]) => {\n            if (n > max) { max = n; mainSector = s; }\n          });\n\n          const color = colorSector(mainSector);\n\n          const svg = window.btoa(`\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"48\" height=\"48\">\n              <circle cx=\"24\" cy=\"24\" r=\"20\" fill=\"${color}\" stroke=\"#fff\" stroke-width=\"3\"\/>\n              <text x=\"24\" y=\"28\" text-anchor=\"middle\" font-size=\"16\" font-family=\"Arial\" fill=\"#fff\">${count}<\/text>\n            <\/svg>\n          `);\n\n          const div = document.createElement(\"div\");\n          div.style.width = \"48px\";\n          div.style.height = \"48px\";\n          div.style.backgroundImage = \"url(data:image\/svg+xml;base64,\" + svg + \")\";\n          div.style.backgroundSize = \"48px 48px\";\n          div.style.cursor = \"pointer\";\n\n          return new google.maps.marker.AdvancedMarkerElement({\n            position,\n            content: div\n          });\n        }\n      }\n    });\n\n    \/* =========================\n       UI: BUSCADOR\n    ========================== *\/\n    function crearBloqueBuscador() {\n      const cont = document.createElement(\"div\");\n      cont.id = \"filtro-busqueda\";\n      cont.style.padding = \"0 0 1em 0\";\n      cont.style.borderBottom = \"1px solid #ddd\";\n      cont.style.marginBottom = \"1em\";\n\n      const label = document.createElement(\"label\");\n      label.textContent = i18nMapa.buscarEmpresa;\n      label.style.display = \"block\";\n      label.style.fontWeight = \"600\";\n      label.style.marginBottom = \"0.4em\";\n\n      const input = document.createElement(\"input\");\n      input.type = \"text\";\n      input.placeholder = i18nMapa.escribeNombre;\n      input.style.width = \"100%\";\n      input.style.padding = \"8px 10px\";\n      input.style.border = \"1px solid #ccc\";\n      input.style.borderRadius = \"6px\";\n\n      input.addEventListener(\"input\", () => {\n        filtros._texto = input.value.trim().toLowerCase();\n        actualizar();\n      });\n\n      cont.append(label, input);\n      return cont;\n    }\n\n    \/* =========================\n       UI: TIPO DE EMPRESA\n    ========================== *\/\n    function crearBloqueTipoEmpresa() {\n      const detalles = document.createElement(\"details\");\n      detalles.open = false;\n\n      const summary = document.createElement(\"summary\");\n      summary.textContent = i18nMapa.tipoEmpresa;\n      detalles.appendChild(summary);\n\n      const cont = document.createElement(\"div\");\n      cont.style.padding = \"0.5em 0 0.5em 0.5em\";\n\n      const canonicas = [\n        [\"importador\", i18nMapa.importador],\n        [\"exportador\", i18nMapa.exportador],\n        [\"importador_exportador\", i18nMapa.importadorExportador],\n        [\"no_realiza_actividad_exterior\", i18nMapa.noRealizaActividadExterior],\n        [\"sin_especificar\", i18nMapa.sinEspecificar]\n      ];\n\n      canonicas.forEach(([slug, label]) => {\n        const key = `tipo_${slug}`;\n        if (typeof filtros[key] === \"undefined\") filtros[key] = false;\n\n        const lab = document.createElement(\"label\");\n        lab.style.display = \"block\";\n        lab.style.marginBottom = \"0.2em\";\n        lab.style.cursor = \"pointer\";\n\n        const cb = document.createElement(\"input\");\n        cb.type = \"checkbox\";\n        cb.checked = false;\n        cb.dataset.tipo = slug;\n\n        cb.addEventListener(\"change\", () => {\n          filtros[key] = cb.checked;\n          actualizar();\n        });\n\n        lab.append(cb, \" \" + label);\n        cont.appendChild(lab);\n      });\n\n      detalles.appendChild(cont);\n      return detalles;\n    }\n\n    \/* =========================\n       UI: TAMA\u00d1O DE EMPRESA\n    ========================== *\/\n    function crearBloqueTamanoEmpresa() {\n      const detalles = document.createElement(\"details\");\n      detalles.open = false;\n\n      const summary = document.createElement(\"summary\");\n      summary.textContent = i18nMapa.tamanoEmpresa;\n      detalles.appendChild(summary);\n\n      const cont = document.createElement(\"div\");\n      cont.style.padding = \"0.5em 0 0.5em 0.5em\";\n\n      const rangos = [\n        [\"tam_micro\", i18nMapa.micro],\n        [\"tam_pequena\", i18nMapa.pequena],\n        [\"tam_mediana\", i18nMapa.mediana],\n        [\"tam_grande\", i18nMapa.grande],\n        [\"tam_sin_dato\", i18nMapa.sinDato]\n      ];\n\n      rangos.forEach(([key, label]) => {\n        if (typeof filtros[key] === \"undefined\") filtros[key] = false;\n\n        const lab = document.createElement(\"label\");\n        lab.style.display = \"block\";\n        lab.style.marginBottom = \"0.2em\";\n        lab.style.cursor = \"pointer\";\n\n        const cb = document.createElement(\"input\");\n        cb.type = \"checkbox\";\n        cb.checked = false;\n        cb.dataset.tamano = key;\n\n        cb.addEventListener(\"change\", () => {\n          filtros[key] = cb.checked;\n          actualizar();\n        });\n\n        lab.append(cb, \" \" + label);\n        cont.appendChild(lab);\n      });\n\n      detalles.appendChild(cont);\n      return detalles;\n    }\n\n    \/* =========================\n       UI: FACTURACI\u00d3N (NUEVO)\n       - se construye con los valores reales presentes en empresas[].facturacion\n    ========================== *\/\n    function crearBloqueFacturacion() {\n\t  const detalles = document.createElement(\"details\");\n\t  detalles.open = false;\n\n\t  const summary = document.createElement(\"summary\");\n\t  summary.textContent = i18nMapa.facturacion + \" (\u20ac)\";\n\t  detalles.appendChild(summary);\n\n\t  const cont = document.createElement(\"div\");\n\t  cont.style.padding = \"0.5em 0.5em 0.8em\";\n\n\t  \/\/ obtener valores reales\n\t  const valores = empresas\n\t\t.map(e => parseFloat(e.facturacion))\n\t\t.filter(v => isFinite(v) && v > 0);\n\n\t  if (!valores.length) {\n\t\tcont.textContent = i18nMapa.noDatosFacturacion;\n\t\tdetalles.appendChild(cont);\n\t\treturn detalles;\n\t  }\n\n\t  const min = Math.min(...valores);\n\t  const max = Math.max(...valores);\n\n\t  filtros.facturacion_min = min;\n\t  filtros.facturacion_max = max;\n\n\t  const label = document.createElement(\"div\");\n\t  label.style.fontSize = \"13px\";\n\t  label.style.marginBottom = \"0.4em\";\n\n\t  const updateLabel = () => {\n\t\tlabel.textContent =\n\t\t  i18nMapa.entre + ` ${(filtros.facturacion_min \/ 1e6).toFixed(1)}M \u20ac ` + i18nMapa.yAnd + \n\t\t  ` ${(filtros.facturacion_max \/ 1e6).toFixed(1)}M \u20ac`;\n\n\t  };\n\n\t  const minWrap = document.createElement(\"div\");\n\t\tminWrap.className = \"facturacion-range min\";\n\n\t\tconst minLabel = document.createElement(\"span\");\n\t\tminLabel.textContent = \"M\u00edn.\";\n\n\t\tconst inputMin = document.createElement(\"input\");\n\t\tinputMin.type = \"range\";\n\t\tinputMin.className = \"range-input\";\n\n\t\tminWrap.append(minLabel, inputMin);\n\n\t  inputMin.min = min;\n\t  inputMin.max = max;\n\t  inputMin.value = min;\n\t  inputMin.step = (max - min) \/ 100;\n\n\t  const maxWrap = document.createElement(\"div\");\n\t\tmaxWrap.className = \"facturacion-range max\";\n\n\t\tconst maxLabel = document.createElement(\"span\");\n\t\tmaxLabel.textContent = \"M\u00e1x.\";\n\n\t\tconst inputMax = document.createElement(\"input\");\n\t\tinputMax.type = \"range\";\n\t\tinputMax.className = \"range-input\";\n\n\t\tmaxWrap.append(maxLabel, inputMax);\n\n\t  inputMax.min = min;\n\t  inputMax.max = max;\n\t  inputMax.value = max;\n\t  inputMax.step = (max - min) \/ 100;\n\n\t  inputMin.addEventListener(\"input\", () => {\n\t\tfiltros.facturacion_min = Math.min(+inputMin.value, filtros.facturacion_max);\n\t\tupdateLabel();\n\t\tactualizar();\n\t  });\n\n\t  inputMax.addEventListener(\"input\", () => {\n\t\tfiltros.facturacion_max = Math.max(+inputMax.value, filtros.facturacion_min);\n\t\tupdateLabel();\n\t\tactualizar();\n\t  });\n\t  \n\t  \/\/ guardar referencias globales para reset\n\t\tfiltros._facturacion_inputs = {\n\t\t  min: inputMin,\n\t\t  max: inputMax,\n\t\t  minValor: min,\n\t\t  maxValor: max\n\t\t};\n\n\t  updateLabel();\n\n\t  cont.append(label, minWrap, maxWrap);\n\t  detalles.appendChild(cont);\n\t  return detalles;\n\t}\n\n\n    \/* =========================\n       UI: COMARCAS\n    ========================== *\/\n    function crearBloqueComarcas() {\n      const detalles = document.createElement(\"details\");\n      detalles.open = false;\n\n      const summary = document.createElement(\"summary\");\n      summary.textContent = i18nMapa.comarcas;\n      detalles.appendChild(summary);\n\n      const cont = document.createElement(\"div\");\n      cont.style.padding = \"0.5em 0 0.5em 0.5em\";\n\n      const set = new Set();\n      empresas.forEach(e => {\n        const arr = Array.isArray(e.comarcas) ? e.comarcas : [];\n        arr.forEach(c => set.add(c));\n      });\n\n      const disponibles = Array.from(set)\n        .filter(nombre => !!mapaComarcas[nombre])\n        .sort((a,b)=>a.localeCompare(b,\"es\"));\n\n      disponibles.forEach(nombre => {\n        const key = `comarca_${nombre}`;\n        if (typeof filtros[key] === \"undefined\") filtros[key] = false;\n\n        const lab = document.createElement(\"label\");\n        lab.style.display = \"block\";\n        lab.style.marginBottom = \"0.2em\";\n        lab.style.cursor = \"pointer\";\n\n        const cb = document.createElement(\"input\");\n        cb.type = \"checkbox\";\n        cb.checked = false;\n        cb.dataset.comarca = nombre;\n\n        cb.addEventListener(\"change\", () => {\n          filtros[key] = cb.checked;\n\n          const capa = capasComarcas[nombre];\n          if (capa) capa.setMap(cb.checked ? mapa : null);\n\n          actualizar();\n        });\n\n        lab.append(cb, \" \" + nombre);\n        cont.appendChild(lab);\n      });\n\n      if (!disponibles.length) {\n        const p = document.createElement(\"div\");\n        p.style.opacity = \"0.7\";\n        p.style.fontSize = \"13px\";\n        p.textContent = i18nMapa.noComarcasDisponibles;\n        cont.appendChild(p);\n      }\n\n      detalles.appendChild(cont);\n      return detalles;\n    }\n\n    \/* =========================\n       UI: SECTORES \/ SUBSECTORES\n    ========================== *\/\n    function renderizarFiltrosSectoresJerarquico() {\n      filtrosContainer.innerHTML = \"\";\n\n      \/\/ 1) Buscador\n      filtrosContainer.appendChild(crearBloqueBuscador());\n\n      \/\/ 2) Bot\u00f3n reset\n      const btn = document.createElement(\"button\");\n      btn.id = \"btn-reset-filtros\";\n      btn.type = \"button\";\n      btn.textContent = i18nMapa.limpiarFiltros;\n      btn.addEventListener(\"click\", () => {\n\n        \/\/ desmarcar subsectores\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-filtro]').forEach(cb => cb.checked = false);\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-sector]').forEach(cb => {\n          cb.checked = false;\n          cb.indeterminate = false;\n        });\n\n        \/\/ desmarcar tipos\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-tipo]').forEach(cb => cb.checked = false);\n\n        \/\/ desmarcar tama\u00f1os\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-tamano]').forEach(cb => cb.checked = false);\n\n        \/\/ desmarcar facturaci\u00f3n \u2705 NUEVO\n        \/\/ reset facturaci\u00f3n (slider)\n\t\tif (filtros._facturacion_inputs) {\n\t\t  const f = filtros._facturacion_inputs;\n\n\t\t  f.min.value = f.minValor;\n\t\t  f.max.value = f.maxValor;\n\n\t\t  filtros.facturacion_min = f.minValor;\n\t\t  filtros.facturacion_max = f.maxValor;\n\t\t}\n\n\n\n        \/\/ desmarcar comarcas + ocultar capas\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-comarca]').forEach(cb => cb.checked = false);\n        Object.values(capasComarcas).forEach(capa => capa.setMap(null));\n\n        \/\/ colapsar subsectores\n        filtrosContainer.querySelectorAll(\".subsectores\").forEach(el => el.style.display = \"none\");\n\n        \/\/ reset texto + input\n        filtros._texto = \"\";\n        const inputBusqueda = filtrosContainer.querySelector(\"#filtro-busqueda input\");\n        if (inputBusqueda) inputBusqueda.value = \"\";\n\n        \/\/ reset keys (no tocar _texto)\n        Object.keys(filtros).forEach(k => {\n          if (k.includes(\"___\")) filtros[k] = false;\n          if (k.startsWith(\"tipo_\")) filtros[k] = false;\n          if (k.startsWith(\"tam_\")) filtros[k] = false;\n          if (k.startsWith(\"comarca_\")) filtros[k] = false;\n          if (k.startsWith(\"fac_\")) filtros[k] = false; \/\/ \u2705 NUEVO\n        });\n\n        actualizar();\n      });\n      filtrosContainer.appendChild(btn);\n\n      \/\/ 3) Tipo empresa\n      filtrosContainer.appendChild(crearBloqueTipoEmpresa());\n\n      \/\/ 4) Tama\u00f1o empresa\n      filtrosContainer.appendChild(crearBloqueTamanoEmpresa());\n\n      \/\/ 5) Facturaci\u00f3n \u2705 NUEVO\n\t  filtrosContainer.appendChild(crearBloqueFacturacion());\n\n\n      \/\/ 6) Comarcas\n      filtrosContainer.appendChild(crearBloqueComarcas());\n\n      \/\/ 7) Sectores\/subsectores\n\t\tconst detallesSectores = document.createElement(\"details\");\n\t\tdetallesSectores.open = false;\n\n\t\tconst summarySectores = document.createElement(\"summary\");\n\t\tsummarySectores.textContent = i18nMapa.sectores;\n\n\t\tdetallesSectores.appendChild(summarySectores);\n\n\t\tconst contSectores = document.createElement(\"div\");\n\t\tcontSectores.style.padding = \"0.5em 0 0.5em 0.5em\";\n\n\t\tdetallesSectores.appendChild(contSectores);\n\n\n      const estructura = {};\n      empresas.forEach(e => {\n        const s = e.sector || \"Otros\";\n        const ss = e.subsector || \"\u2014\";\n        if (!estructura[s]) estructura[s] = new Set();\n        estructura[s].add(ss);\n      });\n\n      const sectoresOrdenados = Object.keys(estructura).sort((a,b) => a.localeCompare(b, \"es\"));\n      const frag = document.createDocumentFragment();\n\n      sectoresOrdenados.forEach(sector => {\n        const subs = Array.from(estructura[sector]).sort((a,b) => a.localeCompare(b, \"es\"));\n        const color = colorSector(sector);\n\n        const labSector = document.createElement(\"label\");\n        labSector.style.borderLeft = `4px solid ${color}`;\n        labSector.style.paddingLeft = \"0.5em\";\n        labSector.style.display = \"block\";\n        labSector.style.margin = \"0.35em 0\";\n\n        const cbSector = document.createElement(\"input\");\n        cbSector.type = \"checkbox\";\n        cbSector.checked = false;\n        cbSector.dataset.sector = sector;\n\n        labSector.append(cbSector, \" \" + sector);\n        frag.appendChild(labSector);\n\n        const contSubs = document.createElement(\"div\");\n        contSubs.className = \"subsectores\";\n        contSubs.style.display = \"none\";\n        frag.appendChild(contSubs);\n\n        cbSector.addEventListener(\"change\", () => {\n          const checked = cbSector.checked;\n\n          subs.forEach(ss => {\n            const key = claveSubsector(sector, ss);\n            filtros[key] = checked;\n\n            const cbHijo = contSubs.querySelector(`input[data-filtro=\"${CSS.escape(key)}\"]`);\n            if (cbHijo) cbHijo.checked = checked;\n          });\n\n          contSubs.style.display = checked ? \"block\" : \"none\";\n          cbSector.indeterminate = false;\n\n          actualizar();\n        });\n\n        subs.forEach(ss => {\n          const key = claveSubsector(sector, ss);\n          if (typeof filtros[key] === \"undefined\") filtros[key] = false;\n\n          const lab = document.createElement(\"label\");\n          lab.className = \"subsector\";\n\n          const cb = document.createElement(\"input\");\n          cb.type = \"checkbox\";\n          cb.checked = false;\n          cb.dataset.filtro = key;\n\n          cb.addEventListener(\"change\", () => {\n            filtros[key] = cb.checked;\n\n            const anyOn = subs.some(s => filtros[claveSubsector(sector, s)] === true);\n            const allOn = subs.every(s => filtros[claveSubsector(sector, s)] === true);\n\n            cbSector.checked = allOn;\n            cbSector.indeterminate = anyOn && !allOn;\n\n            contSubs.style.display = anyOn ? \"block\" : \"none\";\n            actualizar();\n          });\n\n          lab.append(cb, \" \" + ss);\n          contSubs.appendChild(lab);\n        });\n      });\n\n      contSectores.appendChild(frag);\n\t  filtrosContainer.appendChild(detallesSectores);\n\n    }\n\n    \/* =========================\n       LISTA + PAGINACI\u00d3N\n    ========================== *\/\n    function renderizarLista(marks) {\n      listaContainer.innerHTML = \"\";\n      paginacion.innerHTML = \"\";\n\n      const total = marks.length;\n      const paginas = Math.ceil(total \/ empresasPorPagina) || 1;\n      paginaActual = Math.min(Math.max(1, paginaActual), paginas);\n\n      const slice = marks.slice(\n        (paginaActual - 1) * empresasPorPagina,\n        paginaActual * empresasPorPagina\n      );\n\n      slice.forEach(m => {\n        const d = m._data;\n        const div = document.createElement(\"div\");\n        div.className = \"empresa\";\n        div.innerHTML = `\n\t\t  <div class=\"empresa-card\">\n\t\t\t<span class=\"empresa-sector-dot\" style=\"background:${colorSector(d.sector)}\"><\/span>\n\n\t\t\t<div class=\"empresa-content\">\n\t\t\t  <strong class=\"empresa-nombre\">${d.nombre}<\/strong>\n\n\t\t\t  <div class=\"empresa-linea\">\n\t\t\t\t<em>${d.sector}${d.subsector && d.subsector !== \"\u2014\" ? \" \u00b7 \" + d.subsector : \"\"}<\/em>\n\t\t\t  <\/div>\n\n\t\t\t  <div class=\"empresa-linea\">\n\t\t\t\t<strong>Type:<\/strong> ${traducirTipo(d.tipo_slug)}\n\t\t\t  <\/div>\n\n\t\t\t  <div class=\"empresa-linea\">\n\t\t\t\t<strong>Size:<\/strong> ${formatearEmpleados(d.empleados)}\n\t\t\t  <\/div>\n\n\t\t\t  <div class=\"empresa-linea\">\n\t\t\t\t<strong>Invoicing:<\/strong> ${formatearFacturacion(d.facturacion)}\n\t\t\t  <\/div>\n\n\t\t\t  ${\n\t\t\t\td.comarcas && d.comarcas.length\n\t\t\t\t  ? `<div class=\"empresa-linea\">\n\t\t\t\t\t   <strong>` + i18nMapa.comarcas + `${d.comarcas.length > 1 ? \"s\" : \"\"}:<\/strong>\n\t\t\t\t\t   ${d.comarcas.join(\", \")}\n\t\t\t\t\t <\/div>`\n\t\t\t\t  : \"\"\n\t\t\t  }\n\n\t\t\t  ${\n\t\t\t\td.web\n\t\t\t\t  ? `<div class=\"empresa-linea empresa-web\">\n\t\t\t\t\t   <a href=\"${d.web}\" target=\"_blank\" rel=\"noopener noreferrer\">Visit website<\/a>\n\t\t\t\t\t <\/div>`\n\t\t\t\t  : \"\"\n\t\t\t  }\n\t\t\t<\/div>\n\t\t  <\/div>\n\t\t`;\n\n\n        div.addEventListener(\"click\", () => {\n\t\t  mapa.panTo(m.position);\n\t\t  mapa.setZoom(14);\n\n\t\t  \/\/ \ud83d\udd25 dispara exactamente el mismo click que en el mapa\n\t\t  google.maps.event.trigger(m, \"click\");\n\t\t});\n\n\n        listaContainer.appendChild(div);\n      });\n\n      if (paginas > 1) {\n        const prev = document.createElement(\"button\");\n        prev.textContent = i18nMapa.anterior;\n        prev.disabled = paginaActual === 1;\n        prev.onclick = () => { paginaActual--; renderizarLista(marks); };\n\n        const next = document.createElement(\"button\");\n        next.textContent = i18nMapa.siguiente;\n        next.disabled = paginaActual === paginas;\n        next.onclick = () => { paginaActual++; renderizarLista(marks); };\n\n        paginacion.append(prev, ` ${paginaActual}\/${paginas} `, next);\n      }\n    }\n\n    \/* =========================\n       ACTUALIZAR VISIBILIDAD\n    ========================== *\/\n    function actualizar() {\n      const activosSub    = hayFiltrosActivosSubsectores();\n      const activosTipo   = hayFiltrosActivosTipo();\n      const activosTam    = hayFiltrosActivosTamano();\n      const activosCom    = hayFiltrosActivosComarca();\n      const activosFac    = hayFiltrosActivosFacturacion(); \/\/ \u2705 NUEVO\n      const textoActivo   = hayTextoActivo();\n      const texto         = textoActivo ? filtros._texto.toLowerCase() : \"\";\n\t  \n\t  const hayFiltroFacturacion =\n\t\t  filtros.facturacion_min !== null &&\n\t\t  filtros.facturacion_max !== null;\n\n\n      const visibles = [];\n\n      markers.forEach(m => {\n        const d = m._data;\n\n        const keySub  = claveSubsector(d.sector, d.subsector);\n        const keyTipo = `tipo_${d.tipo_slug || \"sin_especificar\"}`;\n        const tamKey  = obtenerClaveTamano(d.empleados);\n\n        const okSub   = !activosSub  ? true : (filtros[keySub] === true);\n        const okTipo  = !activosTipo ? true : (filtros[keyTipo] === true);\n        const okTam   = !activosTam  ? true : (filtros[tamKey] === true);\n        const okTexto = !textoActivo ? true : (d.nombre || \"\").toLowerCase().includes(texto);\n\n        const okComarca = !activosCom\n          ? true\n          : (d.comarcas || []).some(nombre => filtros[`comarca_${nombre}`] === true);\n\n        \/\/ \u2705 NUEVO: facturaci\u00f3n\n        const okFacturacion = !hayFiltroFacturacion ? true : ( d.facturacion !== null && d.facturacion >= filtros.facturacion_min && d.facturacion <= filtros.facturacion_max  );\n\n\n        const visible =\n\t\t  okSub &&\n\t\t  okTipo &&\n\t\t  okTam &&\n\t\t  okTexto &&\n\t\t  okComarca &&\n\t\t  okFacturacion;\n\n\n        m.map = visible ? mapa : null;\n        if (visible) visibles.push(m);\n      });\n\n      clusterer.clearMarkers();\n      clusterer.addMarkers(visibles);\n\n      paginaActual = 1;\n      renderizarLista(visibles);\n    }\n\n    \/* =========================\n       INIT\n    ========================== *\/\n    renderizarFiltrosSectoresJerarquico();\n    actualizar();\n\n    if (loadingEl) {\n      setTimeout(() => { loadingEl.style.display = \"none\"; }, 150);\n    }\n\n  });\n});\n<\/script>\n\n<style>\n\/* =========================\n   LOADING MAPA\n   ========================= *\/\n#mapaempresas { position: relative; } \/* overlay seguro *\/\n\n#mapa-loading {\n  position: absolute;\n  inset: 0;\n  z-index: 20;\n\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n\n  background: rgba(255,255,255,0.75);\n  backdrop-filter: blur(2px);\n\n  font-weight: 600;\n  color: #004868;\n  pointer-events: none;\n}\n\n#mapa-loading .spinner {\n  width: 42px;\n  height: 42px;\n  border: 4px solid rgba(0,72,104,0.2);\n  border-top-color: #004868;\n  border-radius: 50%;\n  animation: spin 0.8s linear infinite;\n  margin-bottom: 12px;\n}\n@keyframes spin { to { transform: rotate(360deg); } }\n\n\/* =========================\n   LAYOUT (tus estilos)\n   ========================= *\/\n#mapaempresas {\n  display: flex;\n  height: calc(100vh - 90px);\n  width: 100%;\n  overflow: hidden;\n  margin: 0;\n}\n\n#info {\n  width: 40%;\n  min-width: 250px;\n  max-width: 70vw;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n}\n\n#mapa {\n  flex: 1;\n  min-width: 200px;\n  height: 100%;\n}\n\n\/* Separadores *\/\n.divider {\n  background: #ccc;\n  opacity: 0.8;\n  z-index: 5;\n}\n.divider.vertical { width: 6px; cursor: col-resize; }\n.divider.horizontal { height: 6px; cursor: row-resize; }\n.divider:hover { background: #999; }\n\n\/* Filtros *\/\n#filtros-sectores {\n  position: relative;\n  padding: 1em;\n  overflow-y: auto;\n  border-bottom: 1px solid #ccc;\n  max-height: 50%;\n}\n\n\/* Bot\u00f3n reset fijo *\/\n#btn-reset-filtros {\n  position: sticky;\n  top: 0;\n  z-index: 10;\n\n  background: rgba(0, 71, 104, 0.9);\n  color: #fff;\n  border: none;\n  padding: 10px 14px;\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  width: 100%;\n  font-weight: 700;\n  transition: background 0.2s;\n  backdrop-filter: blur(4px);\n  margin: 10px 0 10px 0;\n}\n#btn-reset-filtros:hover {\n  background: rgba(0, 57, 85, 0.95);\n}\n\n\/* Lista *\/\n#lista-empresas {\n  flex: 1;\n  overflow-y: auto;\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1%;\n  padding: 1em;\n  justify-content: space-evenly;\n  align-content: flex-start;\n  max-height: 50%;\n}\n\n#lista-empresas .empresa {\n  background-color: #efefef;\n  padding: 10px;\n  width: 32%;\n  box-sizing: border-box;\n  border-radius: 6px;\n  cursor: pointer;\n  transition: background 0.2s;\n  margin-bottom: 10px;\n}\n#lista-empresas .empresa:hover { background-color: #ddd; }\n\n\/* Paginaci\u00f3n *\/\n#paginacion {\n  text-align: center;\n  padding: 0.5em;\n  border-top: 1px solid #ccc;\n  background: #f9f9f9;\n}\n#paginacion button {\n  padding: 6px 12px;\n  margin: 0 5px;\n  background: #004868;\n  color: white;\n  border: none;\n  border-radius: 4px;\n  cursor: pointer;\n}\n#paginacion button:disabled {\n  background: #ccc;\n  cursor: not-allowed;\n}\n\n.subsectores {\n  margin-left: 1.5em;\n  padding-left: 0.5em;\n  border-left: 1px solid rgba(0,0,0,.15);\n}\n.subsectores .subsector {\n  display: block;\n  margin-bottom: 0.2em;\n}\n\n\/* =========================\n   FACTURACI\u00d3N \u2013 SLIDER\n   ========================= *\/\n\n.facturacion-range {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  margin-bottom: 8px;\n}\n\n.facturacion-range span {\n  font-size: 12px;\n  font-weight: 600;\n  color: #004868;\n  width: 38px;\n  text-align: right;\n}\n\n.range-input {\n  -webkit-appearance: none;\n  appearance: none;\n  width: 100%;\n  height: 6px;\n  border-radius: 3px;\n  background: linear-gradient(\n    to right,\n    #cfd8dc,\n    #004868\n  );\n  outline: none;\n  cursor: pointer;\n}\n\n\/* Thumb *\/\n.range-input::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  appearance: none;\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  background: #004868;\n  border: 2px solid #fff;\n  box-shadow: 0 0 0 1px rgba(0,0,0,.15);\n  cursor: pointer;\n}\n\n.range-input::-moz-range-thumb {\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  background: #004868;\n  border: 2px solid #fff;\n  cursor: pointer;\n}\n\n\/* Diferenciar m\u00ednimo \/ m\u00e1ximo *\/\n.facturacion-range.min .range-input {\n  background: linear-gradient(to right, #004868, #cfd8dc);\n}\n\n.facturacion-range.max .range-input {\n  background: linear-gradient(to right, #cfd8dc, #004868);\n}\n\n\n.infowindow-empresa {\n  font-size: 13px;\n  line-height: 1.35;\n  color: #222;\n  max-width: 260px;\n}\n\n.infowindow-empresa .iw-titulo {\n  display: block;\n  font-size: 15px;\n  margin-bottom: 4px;\n  color: #004868;\n}\n\n.infowindow-empresa .iw-bloque {\n  margin-top: 4px;\n}\n\n.infowindow-empresa a {\n  color: #004868;\n  font-weight: 600;\n  text-decoration: none;\n}\n\n.infowindow-empresa a:hover {\n  text-decoration: underline;\n}\n\n\n\n.empresa-card {\n  font-size: 13px;\n  line-height: 1.35;\n  color: #222;\n}\n\n.empresa-nombre {\n  display: block;\n  font-size: 14px;\n  margin-bottom: 4px;\n  color: #004868;\n}\n\n.empresa-linea {\n  margin-top: 3px;\n}\n\n.empresa-web a {\n  font-weight: 600;\n  color: #004868;\n  text-decoration: none;\n}\n\n.empresa-web a:hover {\n  text-decoration: underline;\n}\n\n.empresa-card {\n  display: flex;\n  gap: 10px;\n  font-size: 13px;\n  line-height: 1.35;\n  color: #222;\n}\n\n.empresa-sector-dot {\n  width: 10px;\n  height: 10px;\n  border-radius: 50%;\n  margin-top: 6px;\n  flex-shrink: 0;\n}\n\n.empresa-content {\n  flex: 1;\n}\n\n.empresa-nombre {\n  display: block;\n  font-size: 14px;\n  margin-bottom: 4px;\n  color: #004868;\n}\n\n\n\n\/* Responsive *\/\n@media (max-width: 1024px) {\n  #mapaempresas { flex-direction: column; height: auto; }\n  #info { width: 100% !important; max-width: 100% !important; min-width: 100% !important; height: auto; }\n  #mapa { width: 100% !important; height: 45vh !important; min-height: 300px; }\n  .divider.horizontal, .divider.vertical { display: none; }\n  #filtros-sectores, #lista-empresas { max-height: none !important; height: auto !important; }\n}\n\n@media (max-width: 480px) {\n  #lista-empresas .empresa { width: 100% !important; font-size: 14px; }\n}\n<\/style>\n\n<p>[\/et_pb_text][\/et_pb_column][\/et_pb_row][\/et_pb_section]<\/p>\n","protected":false},"excerpt":{"rendered":"<div id=\"mapaempresas\">\n  <div id=\"info\">\n    <div id=\"filtros-sectores\"><\/div>\n    <div class=\"divider horizontal\"><\/div>\n    <div id=\"lista-empresas\"><\/div>\n    <div id=\"paginacion\"><\/div>\n  <\/div>\n\n  <div class=\"divider vertical\"><\/div>\n\n  <div id=\"mapa\"><\/div>\n\n  <div id=\"mapa-loading\">\n    <div class=\"spinner\"><\/div>\n    <span>Loading companies...<\/span>\n  <\/div>\n<\/div>\n\n<!-- Google Maps (Advanced Markers) -->\n<script src=\"https:\/\/maps.googleapis.com\/maps\/api\/js?key=AIzaSyCn34R5hYrnNEKyDYcbB8K5BzeUoM69yfk&libraries=marker\"><\/script>\n\n<!-- MarkerClusterer v2 -->\n<script src=\"https:\/\/unpkg.com\/@googlemaps\/markerclusterer\/dist\/index.min.js\"><\/script>\n\n<script>\ngoogle.maps.importLibrary(\"marker\").then(() => {\n  document.addEventListener(\"DOMContentLoaded\", async () => {\n\n    \/* =========================\n       MAPA\n    ========================== *\/\n    const mapa = new google.maps.Map(document.getElementById(\"mapa\"), {\n      center: { lat: 39.986, lng: -0.039 },\n      zoom: 8,\n      maxZoom: 17,\n      gestureHandling: \"greedy\",\n      mapId: \"Clave de API 3\" \/\/ \u26a0\ufe0f Map ID real, NO la API key\n    });\n\n    const sectorColors = {\n      \"Agricultura y Ganaderia\": \"#89DC65\",\n      \"Construcci\u00f3n\": \"#FFE46A\",\n      \"Distribuci\u00f3n comercial\": \"#00AED3\",\n      \"Industria\": \"#004868\",\n      \"Servicios\": \"#FF666F\",\n\t  \"Agriculture and Livestock\": \"#89DC65\",\n      \"Construction\": \"#FFE46A\",\n      \"Commercial distribution\": \"#00AED3\",\n      \"Industry\": \"#004868\",\n      \"Services\": \"#FF666F\"\n    };\n    const colorSector = (s) => sectorColors[s] || \"#999\";\n\n    \/* =========================\n       DOM \/ ESTADO\n    ========================== *\/\n    const filtrosContainer = document.getElementById(\"filtros-sectores\");\n    const listaContainer   = document.getElementById(\"lista-empresas\");\n    const paginacion       = document.getElementById(\"paginacion\");\n\n    const empresasPorPagina = 12;\n    let paginaActual = 1;\n\n    \/\/ filtros:\n    \/\/ - sector___subsector -> boolean\n    \/\/ - tipo_xxx -> boolean\n    \/\/ - tam_xxx -> boolean\n    \/\/ - comarca_xxx -> boolean\n    \/\/ - fac_xxx -> boolean   \u2705 NUEVO\n    \/\/ - _texto -> string\n    const filtros = {\n\t  _texto: \"\",\n\t  facturacion_min: null,\n\t  facturacion_max: null\n\t};\n\n    const markers = [];\n    let clusterer = null;\n\tlet infoWindowActiva = null;\n\n\n    \/* =========================\n       COMARCAS: mapping exacto tuyo\n    ========================== *\/\n    const mapaComarcas = {\n      \"El Alto Palancia\": \"alto_palancia.geojson\",\n      \"El Alto Mijares\": \"el_alto_mijares.geojson\",\n      \"El Baix Maestrat\": \"el_baix_maestrat.geojson\",\n      \"Els Ports\": \"els_ports.geojson\",\n      \"L\u2019Alcalat\u00e9n\": \"l_alcalaten.geojson\",\n      \"L\u2019Alt Maestrat\": \"l_alt_maestrat.geojson\",\n      \"La Plana Alta\": \"la_plana_alta.geojson\",\n      \"La Plana Baixa\": \"la_plana_baixa.geojson\"\n    };\n\n    \/\/ capas: nombre comarca -> google.maps.Data\n    const capasComarcas = {};\n\n    \/* =========================\n       HELPERS\n    ========================== *\/\n    function crearIcono(sector) {\n      const div = document.createElement(\"div\");\n      div.style.width = \"18px\";\n      div.style.height = \"18px\";\n      div.style.borderRadius = \"50%\";\n      div.style.background = colorSector(sector);\n      div.style.border = \"2px solid #fff\";\n      return div;\n    }\n\n    function claveSubsector(sector, subsector) {\n      return `${sector}___${subsector || \"\u2014\"}`;\n    }\n\n    function hayFiltrosActivosSubsectores() {\n      return Object.keys(filtros).some(k => k.includes(\"___\") && filtros[k] === true);\n    }\n\n    function hayFiltrosActivosTipo() {\n      return Object.keys(filtros).some(k => k.startsWith(\"tipo_\") && filtros[k] === true);\n    }\n\n    function hayFiltrosActivosTamano() {\n      return Object.keys(filtros).some(k => k.startsWith(\"tam_\") && filtros[k] === true);\n    }\n\n    function hayFiltrosActivosComarca() {\n      return Object.keys(filtros).some(k => k.startsWith(\"comarca_\") && filtros[k] === true);\n    }\n\n    function hayFiltrosActivosFacturacion() { \/\/ \u2705 NUEVO\n      return Object.keys(filtros).some(k => k.startsWith(\"fac_\") && filtros[k] === true);\n    }\n\n    function hayTextoActivo() {\n      return typeof filtros._texto === \"string\" && filtros._texto.trim().length > 0;\n    }\n\n    function obtenerClaveTamano(n) {\n      if (n === null) return \"tam_sin_dato\";\n      if (n >= 1 && n <= 9) return \"tam_micro\";\n      if (n >= 10 && n <= 49) return \"tam_pequena\";\n      if (n >= 50 && n <= 249) return \"tam_mediana\";\n      if (n >= 250) return \"tam_grande\";\n      return \"tam_sin_dato\";\n    }\n\t\n\t\/\/ Diccionario de tipos (traducci\u00f3n v\u00eda PHP -> WPML String Translation)\n\tconst TIPO_LABELS = {\n\t  importador: i18nMapa.importador,\n\t  exportador: i18nMapa.exportador,\n\t  importador_exportador: i18nMapa.importadorExportador,\n\t  no_realiza_actividad_exterior: i18nMapa.noRealizaActividadExterior,\n\t  sin_especificar: i18nMapa.sinEspecificar\n\t};\n\n\tfunction traducirTipo(slug) {\n\t  const key = (slug && String(slug).trim()) ? slug : \"sin_especificar\";\n\t  return TIPO_LABELS[key] || TIPO_LABELS.sin_especificar;\n\t}\n\n\n    \/\/ \u2705 NUEVO: normalizar para usar keys seguras en filtros (accents, espacios, etc.)\n    function slugify(str) {\n      return String(str || \"\")\n        .trim()\n        .toLowerCase()\n        .normalize(\"NFD\").replace(\/[\\u0300-\\u036f]\/g, \"\") \/\/ quita tildes\n        .replace(\/[\u2019'\"]\/g, \"\")                           \/\/ quita comillas\/ap\u00f3strofos\n        .replace(\/[^a-z0-9]+\/g, \"_\")                      \/\/ todo a _\n        .replace(\/^_+|_+$\/g, \"\");\n    }\n\t\n\tfunction formatearEmpleados(n) {\n\t  if (n === null) return i18nMapa.sinDato;\n\t  if (n <= 9) return i18nMapa.micro;\n\t  if (n <= 49) return i18nMapa.pequena;\n\t  if (n <= 249) return i18nMapa.mediana;\n\t  return i18nMapa.grande;\n\t}\n\n\tfunction formatearFacturacion(n) {\n\t  if (!n) return \"Sin dato\";\n\t  if (n >= 1e6) return `${(n \/ 1e6).toFixed(1)} M\u20ac`;\n\t  if (n >= 1e3) return `${(n \/ 1e3).toFixed(0)} K\u20ac`;\n\t  return `${n} \u20ac`;\n\t}\n\n\n    \/* =========================\n       LOADING\n    ========================== *\/\n    const loadingEl = document.getElementById(\"mapa-loading\");\n    if (loadingEl) loadingEl.style.display = \"flex\";\n\n    \/* =========================\n       FETCH DATOS\n    ========================== *\/\n    \/*const empresas = await fetch(\"\/wp-json\/miapi\/v1\/empresas\/\").then(r => r.json());*\/\n\tconst lang = document.documentElement.lang.startsWith(\"en\") ? \"en\" : \"es\";\n\tconst url_lang = document.documentElement.lang.startsWith(\"en\") ? \"\/en\/wp-json\/miapi\/v1\/empresas\/\" : \"\/wp-json\/miapi\/v1\/empresas\/\";\n\tconst empresas = await fetch(\n\t  `${url_lang}`\n\t).then(r => r.json());\n\n    \/* =========================\n       CARGA DE CAPAS GEOJSON (como lo ten\u00edas)\n       - se cargan una vez, pero NO se muestran hasta marcar\n    ========================== *\/\n    Object.entries(mapaComarcas).forEach(([nombre, archivo]) => {\n      const capa = new google.maps.Data({ map: null });\n      capasComarcas[nombre] = capa;\n\n      capa.setStyle({\n        fillColor: \"#004868\",\n        fillOpacity: 0.08,\n        strokeColor: \"#004868\",\n        strokeOpacity: 0.9,\n        strokeWeight: 1.5\n      });\n\n      capa.loadGeoJson(`\/wp-content\/themes\/Divi-Child\/geodata\/${archivo}`);\n    });\n\n    \/* =========================\n       CREAR MARCADORES\n    ========================== *\/\n    const bounds = new google.maps.LatLngBounds();\n\n    empresas.forEach(emp => {\n      const sector = emp.sector || \"Otros\";\n      const subsector = emp.subsector || \"\u2014\";\n\n      const lat = +emp.lat;\n      const lng = +emp.lng;\n      if (!isFinite(lat) || !isFinite(lng)) return;\n\n      \/\/ \u2705 NUMERO DE EMPLEADOS\n      let empleados = parseInt(emp.numero_de_empleados, 10);\n      if (isNaN(empleados) || empleados <= 0) empleados = null;\n\n      \/\/ \u2705 FACTURACI\u00d3N (guardar tal cual venga)\n      let facturacion = parseFloat(emp.facturacion);\n\t  if (isNaN(facturacion) || facturacion <= 0) facturacion = null;\n\n      const marker = new google.maps.marker.AdvancedMarkerElement({\n        map: mapa,\n        position: { lat, lng },\n        content: crearIcono(sector)\n      });\n\n      marker._data = {\n        nombre: emp.nombre || \"\",\n        sector,\n        subsector,\n        tipo_slug: emp.tipo_slug || \"sin_especificar\",\n        tipo_label: emp.tipo_label || \"Sin especificar\",\n        web: emp.web || \"\",\n        empleados,\n        facturacion, \/\/ \u2705 NUEVO\n        comarcas: Array.isArray(emp.comarcas) ? emp.comarcas : []\n      };\n\n      const d = marker._data;\n\n\t\tconst info = `\n\t\t  <div class=\"infowindow-empresa\">\n\t\t\t<strong class=\"iw-titulo\">${d.nombre}<\/strong>\n\n\t\t\t<div class=\"iw-bloque\">\n\t\t\t  <em>${d.sector}${d.subsector && d.subsector !== \"\u2014\" ? \" \u00b7 \" + d.subsector : \"\"}<\/em>\n\t\t\t<\/div>\n\n\t\t\t<div class=\"iw-bloque\">\n\t\t\t  <strong>` + i18nMapa.tipoEmpresa + `:<\/strong> ${traducirTipo(d.tipo_slug)}\n\t\t\t<\/div>\n\n\t\t\t<div class=\"iw-bloque\">\n\t\t\t  <strong>` + i18nMapa.tamanoEmpresa + `:<\/strong> ${formatearEmpleados(d.empleados)}\n\t\t\t<\/div>\n\n\t\t\t<div class=\"iw-bloque\">\n\t\t\t  <strong>` + i18nMapa.facturacion + ` (\u20ac):<\/strong> ${formatearFacturacion(d.facturacion)}\n\t\t\t<\/div>\n\n\t\t\t${\n\t\t\t  d.comarcas && d.comarcas.length\n\t\t\t\t? `<div class=\"iw-bloque\">\n\t\t\t\t\t <strong>` + i18nMapa.comarcas + `${d.comarcas.length > 1 ? \"s\" : \"\"}:<\/strong>\n\t\t\t\t\t ${d.comarcas.join(\", \")}\n\t\t\t\t   <\/div>`\n\t\t\t\t: \"\"\n\t\t\t}\n\n\t\t\t${\n\t\t\t  d.web\n\t\t\t\t? `<div class=\"iw-bloque\">\n\t\t\t\t\t <a href=\"${d.web}\" target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t\t\t\t   ` + i18nMapa.visitarWeb + `\n\t\t\t\t\t <\/a>\n\t\t\t\t   <\/div>`\n\t\t\t\t: \"\"\n\t\t\t}\n\t\t  <\/div>\n\t\t`;\n\n      marker._infoWindow = new google.maps.InfoWindow({ content: info });\n\n\t\tmarker.addListener(\"click\", () => {\n\n\t\t  \/\/ \ud83d\udd12 cerrar la anterior si existe\n\t\t  if (infoWindowActiva) {\n\t\t\tinfoWindowActiva.close();\n\t\t  }\n\n\t\t  \/\/ abrir la actual\n\t\t  marker._infoWindow.open({\n\t\t\tmap: mapa,\n\t\t\tanchor: marker\n\t\t  });\n\n\t\t  \/\/ guardar referencia\n\t\t  infoWindowActiva = marker._infoWindow;\n\t\t});\n\n\n\n      markers.push(marker);\n      bounds.extend({ lat, lng });\n    });\n\n    if (!bounds.isEmpty()) mapa.fitBounds(bounds);\n\n    \/* =========================\n       CLUSTER (COLOR POR SECTOR DOMINANTE)\n    ========================== *\/\n    clusterer = new markerClusterer.MarkerClusterer({\n      map: mapa,\n      markers: [],\n      algorithm: new markerClusterer.SuperClusterAlgorithm({ maxZoom: 11 }),\n      renderer: {\n        render: ({ count, markers: clusterMarkers, position }) => {\n          const sectorCounts = {};\n          clusterMarkers.forEach(m => {\n            const s = m._data?.sector || \"Otros\";\n            sectorCounts[s] = (sectorCounts[s] || 0) + 1;\n          });\n\n          let mainSector = \"Otros\";\n          let max = 0;\n          Object.entries(sectorCounts).forEach(([s, n]) => {\n            if (n > max) { max = n; mainSector = s; }\n          });\n\n          const color = colorSector(mainSector);\n\n          const svg = window.btoa(`\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"48\" height=\"48\">\n              <circle cx=\"24\" cy=\"24\" r=\"20\" fill=\"${color}\" stroke=\"#fff\" stroke-width=\"3\"\/>\n              <text x=\"24\" y=\"28\" text-anchor=\"middle\" font-size=\"16\" font-family=\"Arial\" fill=\"#fff\">${count}<\/text>\n            <\/svg>\n          `);\n\n          const div = document.createElement(\"div\");\n          div.style.width = \"48px\";\n          div.style.height = \"48px\";\n          div.style.backgroundImage = \"url(data:image\/svg+xml;base64,\" + svg + \")\";\n          div.style.backgroundSize = \"48px 48px\";\n          div.style.cursor = \"pointer\";\n\n          return new google.maps.marker.AdvancedMarkerElement({\n            position,\n            content: div\n          });\n        }\n      }\n    });\n\n    \/* =========================\n       UI: BUSCADOR\n    ========================== *\/\n    function crearBloqueBuscador() {\n      const cont = document.createElement(\"div\");\n      cont.id = \"filtro-busqueda\";\n      cont.style.padding = \"0 0 1em 0\";\n      cont.style.borderBottom = \"1px solid #ddd\";\n      cont.style.marginBottom = \"1em\";\n\n      const label = document.createElement(\"label\");\n      label.textContent = i18nMapa.buscarEmpresa;\n      label.style.display = \"block\";\n      label.style.fontWeight = \"600\";\n      label.style.marginBottom = \"0.4em\";\n\n      const input = document.createElement(\"input\");\n      input.type = \"text\";\n      input.placeholder = i18nMapa.escribeNombre;\n      input.style.width = \"100%\";\n      input.style.padding = \"8px 10px\";\n      input.style.border = \"1px solid #ccc\";\n      input.style.borderRadius = \"6px\";\n\n      input.addEventListener(\"input\", () => {\n        filtros._texto = input.value.trim().toLowerCase();\n        actualizar();\n      });\n\n      cont.append(label, input);\n      return cont;\n    }\n\n    \/* =========================\n       UI: TIPO DE EMPRESA\n    ========================== *\/\n    function crearBloqueTipoEmpresa() {\n      const detalles = document.createElement(\"details\");\n      detalles.open = false;\n\n      const summary = document.createElement(\"summary\");\n      summary.textContent = i18nMapa.tipoEmpresa;\n      detalles.appendChild(summary);\n\n      const cont = document.createElement(\"div\");\n      cont.style.padding = \"0.5em 0 0.5em 0.5em\";\n\n      const canonicas = [\n        [\"importador\", i18nMapa.importador],\n        [\"exportador\", i18nMapa.exportador],\n        [\"importador_exportador\", i18nMapa.importadorExportador],\n        [\"no_realiza_actividad_exterior\", i18nMapa.noRealizaActividadExterior],\n        [\"sin_especificar\", i18nMapa.sinEspecificar]\n      ];\n\n      canonicas.forEach(([slug, label]) => {\n        const key = `tipo_${slug}`;\n        if (typeof filtros[key] === \"undefined\") filtros[key] = false;\n\n        const lab = document.createElement(\"label\");\n        lab.style.display = \"block\";\n        lab.style.marginBottom = \"0.2em\";\n        lab.style.cursor = \"pointer\";\n\n        const cb = document.createElement(\"input\");\n        cb.type = \"checkbox\";\n        cb.checked = false;\n        cb.dataset.tipo = slug;\n\n        cb.addEventListener(\"change\", () => {\n          filtros[key] = cb.checked;\n          actualizar();\n        });\n\n        lab.append(cb, \" \" + label);\n        cont.appendChild(lab);\n      });\n\n      detalles.appendChild(cont);\n      return detalles;\n    }\n\n    \/* =========================\n       UI: TAMA\u00d1O DE EMPRESA\n    ========================== *\/\n    function crearBloqueTamanoEmpresa() {\n      const detalles = document.createElement(\"details\");\n      detalles.open = false;\n\n      const summary = document.createElement(\"summary\");\n      summary.textContent = i18nMapa.tamanoEmpresa;\n      detalles.appendChild(summary);\n\n      const cont = document.createElement(\"div\");\n      cont.style.padding = \"0.5em 0 0.5em 0.5em\";\n\n      const rangos = [\n        [\"tam_micro\", i18nMapa.micro],\n        [\"tam_pequena\", i18nMapa.pequena],\n        [\"tam_mediana\", i18nMapa.mediana],\n        [\"tam_grande\", i18nMapa.grande],\n        [\"tam_sin_dato\", i18nMapa.sinDato]\n      ];\n\n      rangos.forEach(([key, label]) => {\n        if (typeof filtros[key] === \"undefined\") filtros[key] = false;\n\n        const lab = document.createElement(\"label\");\n        lab.style.display = \"block\";\n        lab.style.marginBottom = \"0.2em\";\n        lab.style.cursor = \"pointer\";\n\n        const cb = document.createElement(\"input\");\n        cb.type = \"checkbox\";\n        cb.checked = false;\n        cb.dataset.tamano = key;\n\n        cb.addEventListener(\"change\", () => {\n          filtros[key] = cb.checked;\n          actualizar();\n        });\n\n        lab.append(cb, \" \" + label);\n        cont.appendChild(lab);\n      });\n\n      detalles.appendChild(cont);\n      return detalles;\n    }\n\n    \/* =========================\n       UI: FACTURACI\u00d3N (NUEVO)\n       - se construye con los valores reales presentes en empresas[].facturacion\n    ========================== *\/\n    function crearBloqueFacturacion() {\n\t  const detalles = document.createElement(\"details\");\n\t  detalles.open = false;\n\n\t  const summary = document.createElement(\"summary\");\n\t  summary.textContent = i18nMapa.facturacion + \" (\u20ac)\";\n\t  detalles.appendChild(summary);\n\n\t  const cont = document.createElement(\"div\");\n\t  cont.style.padding = \"0.5em 0.5em 0.8em\";\n\n\t  \/\/ obtener valores reales\n\t  const valores = empresas\n\t\t.map(e => parseFloat(e.facturacion))\n\t\t.filter(v => isFinite(v) && v > 0);\n\n\t  if (!valores.length) {\n\t\tcont.textContent = i18nMapa.noDatosFacturacion;\n\t\tdetalles.appendChild(cont);\n\t\treturn detalles;\n\t  }\n\n\t  const min = Math.min(...valores);\n\t  const max = Math.max(...valores);\n\n\t  filtros.facturacion_min = min;\n\t  filtros.facturacion_max = max;\n\n\t  const label = document.createElement(\"div\");\n\t  label.style.fontSize = \"13px\";\n\t  label.style.marginBottom = \"0.4em\";\n\n\t  const updateLabel = () => {\n\t\tlabel.textContent =\n\t\t  i18nMapa.entre + ` ${(filtros.facturacion_min \/ 1e6).toFixed(1)}M \u20ac ` + i18nMapa.yAnd + \n\t\t  ` ${(filtros.facturacion_max \/ 1e6).toFixed(1)}M \u20ac`;\n\n\t  };\n\n\t  const minWrap = document.createElement(\"div\");\n\t\tminWrap.className = \"facturacion-range min\";\n\n\t\tconst minLabel = document.createElement(\"span\");\n\t\tminLabel.textContent = \"M\u00edn.\";\n\n\t\tconst inputMin = document.createElement(\"input\");\n\t\tinputMin.type = \"range\";\n\t\tinputMin.className = \"range-input\";\n\n\t\tminWrap.append(minLabel, inputMin);\n\n\t  inputMin.min = min;\n\t  inputMin.max = max;\n\t  inputMin.value = min;\n\t  inputMin.step = (max - min) \/ 100;\n\n\t  const maxWrap = document.createElement(\"div\");\n\t\tmaxWrap.className = \"facturacion-range max\";\n\n\t\tconst maxLabel = document.createElement(\"span\");\n\t\tmaxLabel.textContent = \"M\u00e1x.\";\n\n\t\tconst inputMax = document.createElement(\"input\");\n\t\tinputMax.type = \"range\";\n\t\tinputMax.className = \"range-input\";\n\n\t\tmaxWrap.append(maxLabel, inputMax);\n\n\t  inputMax.min = min;\n\t  inputMax.max = max;\n\t  inputMax.value = max;\n\t  inputMax.step = (max - min) \/ 100;\n\n\t  inputMin.addEventListener(\"input\", () => {\n\t\tfiltros.facturacion_min = Math.min(+inputMin.value, filtros.facturacion_max);\n\t\tupdateLabel();\n\t\tactualizar();\n\t  });\n\n\t  inputMax.addEventListener(\"input\", () => {\n\t\tfiltros.facturacion_max = Math.max(+inputMax.value, filtros.facturacion_min);\n\t\tupdateLabel();\n\t\tactualizar();\n\t  });\n\t  \n\t  \/\/ guardar referencias globales para reset\n\t\tfiltros._facturacion_inputs = {\n\t\t  min: inputMin,\n\t\t  max: inputMax,\n\t\t  minValor: min,\n\t\t  maxValor: max\n\t\t};\n\n\t  updateLabel();\n\n\t  cont.append(label, minWrap, maxWrap);\n\t  detalles.appendChild(cont);\n\t  return detalles;\n\t}\n\n\n    \/* =========================\n       UI: COMARCAS\n    ========================== *\/\n    function crearBloqueComarcas() {\n      const detalles = document.createElement(\"details\");\n      detalles.open = false;\n\n      const summary = document.createElement(\"summary\");\n      summary.textContent = i18nMapa.comarcas;\n      detalles.appendChild(summary);\n\n      const cont = document.createElement(\"div\");\n      cont.style.padding = \"0.5em 0 0.5em 0.5em\";\n\n      const set = new Set();\n      empresas.forEach(e => {\n        const arr = Array.isArray(e.comarcas) ? e.comarcas : [];\n        arr.forEach(c => set.add(c));\n      });\n\n      const disponibles = Array.from(set)\n        .filter(nombre => !!mapaComarcas[nombre])\n        .sort((a,b)=>a.localeCompare(b,\"es\"));\n\n      disponibles.forEach(nombre => {\n        const key = `comarca_${nombre}`;\n        if (typeof filtros[key] === \"undefined\") filtros[key] = false;\n\n        const lab = document.createElement(\"label\");\n        lab.style.display = \"block\";\n        lab.style.marginBottom = \"0.2em\";\n        lab.style.cursor = \"pointer\";\n\n        const cb = document.createElement(\"input\");\n        cb.type = \"checkbox\";\n        cb.checked = false;\n        cb.dataset.comarca = nombre;\n\n        cb.addEventListener(\"change\", () => {\n          filtros[key] = cb.checked;\n\n          const capa = capasComarcas[nombre];\n          if (capa) capa.setMap(cb.checked ? mapa : null);\n\n          actualizar();\n        });\n\n        lab.append(cb, \" \" + nombre);\n        cont.appendChild(lab);\n      });\n\n      if (!disponibles.length) {\n        const p = document.createElement(\"div\");\n        p.style.opacity = \"0.7\";\n        p.style.fontSize = \"13px\";\n        p.textContent = i18nMapa.noComarcasDisponibles;\n        cont.appendChild(p);\n      }\n\n      detalles.appendChild(cont);\n      return detalles;\n    }\n\n    \/* =========================\n       UI: SECTORES \/ SUBSECTORES\n    ========================== *\/\n    function renderizarFiltrosSectoresJerarquico() {\n      filtrosContainer.innerHTML = \"\";\n\n      \/\/ 1) Buscador\n      filtrosContainer.appendChild(crearBloqueBuscador());\n\n      \/\/ 2) Bot\u00f3n reset\n      const btn = document.createElement(\"button\");\n      btn.id = \"btn-reset-filtros\";\n      btn.type = \"button\";\n      btn.textContent = i18nMapa.limpiarFiltros;\n      btn.addEventListener(\"click\", () => {\n\n        \/\/ desmarcar subsectores\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-filtro]').forEach(cb => cb.checked = false);\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-sector]').forEach(cb => {\n          cb.checked = false;\n          cb.indeterminate = false;\n        });\n\n        \/\/ desmarcar tipos\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-tipo]').forEach(cb => cb.checked = false);\n\n        \/\/ desmarcar tama\u00f1os\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-tamano]').forEach(cb => cb.checked = false);\n\n        \/\/ desmarcar facturaci\u00f3n \u2705 NUEVO\n        \/\/ reset facturaci\u00f3n (slider)\n\t\tif (filtros._facturacion_inputs) {\n\t\t  const f = filtros._facturacion_inputs;\n\n\t\t  f.min.value = f.minValor;\n\t\t  f.max.value = f.maxValor;\n\n\t\t  filtros.facturacion_min = f.minValor;\n\t\t  filtros.facturacion_max = f.maxValor;\n\t\t}\n\n\n\n        \/\/ desmarcar comarcas + ocultar capas\n        filtrosContainer.querySelectorAll('input[type=\"checkbox\"][data-comarca]').forEach(cb => cb.checked = false);\n        Object.values(capasComarcas).forEach(capa => capa.setMap(null));\n\n        \/\/ colapsar subsectores\n        filtrosContainer.querySelectorAll(\".subsectores\").forEach(el => el.style.display = \"none\");\n\n        \/\/ reset texto + input\n        filtros._texto = \"\";\n        const inputBusqueda = filtrosContainer.querySelector(\"#filtro-busqueda input\");\n        if (inputBusqueda) inputBusqueda.value = \"\";\n\n        \/\/ reset keys (no tocar _texto)\n        Object.keys(filtros).forEach(k => {\n          if (k.includes(\"___\")) filtros[k] = false;\n          if (k.startsWith(\"tipo_\")) filtros[k] = false;\n          if (k.startsWith(\"tam_\")) filtros[k] = false;\n          if (k.startsWith(\"comarca_\")) filtros[k] = false;\n          if (k.startsWith(\"fac_\")) filtros[k] = false; \/\/ \u2705 NUEVO\n        });\n\n        actualizar();\n      });\n      filtrosContainer.appendChild(btn);\n\n      \/\/ 3) Tipo empresa\n      filtrosContainer.appendChild(crearBloqueTipoEmpresa());\n\n      \/\/ 4) Tama\u00f1o empresa\n      filtrosContainer.appendChild(crearBloqueTamanoEmpresa());\n\n      \/\/ 5) Facturaci\u00f3n \u2705 NUEVO\n\t  filtrosContainer.appendChild(crearBloqueFacturacion());\n\n\n      \/\/ 6) Comarcas\n      filtrosContainer.appendChild(crearBloqueComarcas());\n\n      \/\/ 7) Sectores\/subsectores\n\t\tconst detallesSectores = document.createElement(\"details\");\n\t\tdetallesSectores.open = false;\n\n\t\tconst summarySectores = document.createElement(\"summary\");\n\t\tsummarySectores.textContent = i18nMapa.sectores;\n\n\t\tdetallesSectores.appendChild(summarySectores);\n\n\t\tconst contSectores = document.createElement(\"div\");\n\t\tcontSectores.style.padding = \"0.5em 0 0.5em 0.5em\";\n\n\t\tdetallesSectores.appendChild(contSectores);\n\n\n      const estructura = {};\n      empresas.forEach(e => {\n        const s = e.sector || \"Otros\";\n        const ss = e.subsector || \"\u2014\";\n        if (!estructura[s]) estructura[s] = new Set();\n        estructura[s].add(ss);\n      });\n\n      const sectoresOrdenados = Object.keys(estructura).sort((a,b) => a.localeCompare(b, \"es\"));\n      const frag = document.createDocumentFragment();\n\n      sectoresOrdenados.forEach(sector => {\n        const subs = Array.from(estructura[sector]).sort((a,b) => a.localeCompare(b, \"es\"));\n        const color = colorSector(sector);\n\n        const labSector = document.createElement(\"label\");\n        labSector.style.borderLeft = `4px solid ${color}`;\n        labSector.style.paddingLeft = \"0.5em\";\n        labSector.style.display = \"block\";\n        labSector.style.margin = \"0.35em 0\";\n\n        const cbSector = document.createElement(\"input\");\n        cbSector.type = \"checkbox\";\n        cbSector.checked = false;\n        cbSector.dataset.sector = sector;\n\n        labSector.append(cbSector, \" \" + sector);\n        frag.appendChild(labSector);\n\n        const contSubs = document.createElement(\"div\");\n        contSubs.className = \"subsectores\";\n        contSubs.style.display = \"none\";\n        frag.appendChild(contSubs);\n\n        cbSector.addEventListener(\"change\", () => {\n          const checked = cbSector.checked;\n\n          subs.forEach(ss => {\n            const key = claveSubsector(sector, ss);\n            filtros[key] = checked;\n\n            const cbHijo = contSubs.querySelector(`input[data-filtro=\"${CSS.escape(key)}\"]`);\n            if (cbHijo) cbHijo.checked = checked;\n          });\n\n          contSubs.style.display = checked ? \"block\" : \"none\";\n          cbSector.indeterminate = false;\n\n          actualizar();\n        });\n\n        subs.forEach(ss => {\n          const key = claveSubsector(sector, ss);\n          if (typeof filtros[key] === \"undefined\") filtros[key] = false;\n\n          const lab = document.createElement(\"label\");\n          lab.className = \"subsector\";\n\n          const cb = document.createElement(\"input\");\n          cb.type = \"checkbox\";\n          cb.checked = false;\n          cb.dataset.filtro = key;\n\n          cb.addEventListener(\"change\", () => {\n            filtros[key] = cb.checked;\n\n            const anyOn = subs.some(s => filtros[claveSubsector(sector, s)] === true);\n            const allOn = subs.every(s => filtros[claveSubsector(sector, s)] === true);\n\n            cbSector.checked = allOn;\n            cbSector.indeterminate = anyOn && !allOn;\n\n            contSubs.style.display = anyOn ? \"block\" : \"none\";\n            actualizar();\n          });\n\n          lab.append(cb, \" \" + ss);\n          contSubs.appendChild(lab);\n        });\n      });\n\n      contSectores.appendChild(frag);\n\t  filtrosContainer.appendChild(detallesSectores);\n\n    }\n\n    \/* =========================\n       LISTA + PAGINACI\u00d3N\n    ========================== *\/\n    function renderizarLista(marks) {\n      listaContainer.innerHTML = \"\";\n      paginacion.innerHTML = \"\";\n\n      const total = marks.length;\n      const paginas = Math.ceil(total \/ empresasPorPagina) || 1;\n      paginaActual = Math.min(Math.max(1, paginaActual), paginas);\n\n      const slice = marks.slice(\n        (paginaActual - 1) * empresasPorPagina,\n        paginaActual * empresasPorPagina\n      );\n\n      slice.forEach(m => {\n        const d = m._data;\n        const div = document.createElement(\"div\");\n        div.className = \"empresa\";\n        div.innerHTML = `\n\t\t  <div class=\"empresa-card\">\n\t\t\t<span class=\"empresa-sector-dot\" style=\"background:${colorSector(d.sector)}\"><\/span>\n\n\t\t\t<div class=\"empresa-content\">\n\t\t\t  <strong class=\"empresa-nombre\">${d.nombre}<\/strong>\n\n\t\t\t  <div class=\"empresa-linea\">\n\t\t\t\t<em>${d.sector}${d.subsector && d.subsector !== \"\u2014\" ? \" \u00b7 \" + d.subsector : \"\"}<\/em>\n\t\t\t  <\/div>\n\n\t\t\t  <div class=\"empresa-linea\">\n\t\t\t\t<strong>Type:<\/strong> ${traducirTipo(d.tipo_slug)}\n\t\t\t  <\/div>\n\n\t\t\t  <div class=\"empresa-linea\">\n\t\t\t\t<strong>Size:<\/strong> ${formatearEmpleados(d.empleados)}\n\t\t\t  <\/div>\n\n\t\t\t  <div class=\"empresa-linea\">\n\t\t\t\t<strong>Invoicing:<\/strong> ${formatearFacturacion(d.facturacion)}\n\t\t\t  <\/div>\n\n\t\t\t  ${\n\t\t\t\td.comarcas && d.comarcas.length\n\t\t\t\t  ? `<div class=\"empresa-linea\">\n\t\t\t\t\t   <strong>` + i18nMapa.comarcas + `${d.comarcas.length > 1 ? \"s\" : \"\"}:<\/strong>\n\t\t\t\t\t   ${d.comarcas.join(\", \")}\n\t\t\t\t\t <\/div>`\n\t\t\t\t  : \"\"\n\t\t\t  }\n\n\t\t\t  ${\n\t\t\t\td.web\n\t\t\t\t  ? `<div class=\"empresa-linea empresa-web\">\n\t\t\t\t\t   <a href=\"${d.web}\" target=\"_blank\" rel=\"noopener noreferrer\">Visit website<\/a>\n\t\t\t\t\t <\/div>`\n\t\t\t\t  : \"\"\n\t\t\t  }\n\t\t\t<\/div>\n\t\t  <\/div>\n\t\t`;\n\n\n        div.addEventListener(\"click\", () => {\n\t\t  mapa.panTo(m.position);\n\t\t  mapa.setZoom(14);\n\n\t\t  \/\/ \ud83d\udd25 dispara exactamente el mismo click que en el mapa\n\t\t  google.maps.event.trigger(m, \"click\");\n\t\t});\n\n\n        listaContainer.appendChild(div);\n      });\n\n      if (paginas > 1) {\n        const prev = document.createElement(\"button\");\n        prev.textContent = i18nMapa.anterior;\n        prev.disabled = paginaActual === 1;\n        prev.onclick = () => { paginaActual--; renderizarLista(marks); };\n\n        const next = document.createElement(\"button\");\n        next.textContent = i18nMapa.siguiente;\n        next.disabled = paginaActual === paginas;\n        next.onclick = () => { paginaActual++; renderizarLista(marks); };\n\n        paginacion.append(prev, ` ${paginaActual}\/${paginas} `, next);\n      }\n    }\n\n    \/* =========================\n       ACTUALIZAR VISIBILIDAD\n    ========================== *\/\n    function actualizar() {\n      const activosSub    = hayFiltrosActivosSubsectores();\n      const activosTipo   = hayFiltrosActivosTipo();\n      const activosTam    = hayFiltrosActivosTamano();\n      const activosCom    = hayFiltrosActivosComarca();\n      const activosFac    = hayFiltrosActivosFacturacion(); \/\/ \u2705 NUEVO\n      const textoActivo   = hayTextoActivo();\n      const texto         = textoActivo ? filtros._texto.toLowerCase() : \"\";\n\t  \n\t  const hayFiltroFacturacion =\n\t\t  filtros.facturacion_min !== null &&\n\t\t  filtros.facturacion_max !== null;\n\n\n      const visibles = [];\n\n      markers.forEach(m => {\n        const d = m._data;\n\n        const keySub  = claveSubsector(d.sector, d.subsector);\n        const keyTipo = `tipo_${d.tipo_slug || \"sin_especificar\"}`;\n        const tamKey  = obtenerClaveTamano(d.empleados);\n\n        const okSub   = !activosSub  ? true : (filtros[keySub] === true);\n        const okTipo  = !activosTipo ? true : (filtros[keyTipo] === true);\n        const okTam   = !activosTam  ? true : (filtros[tamKey] === true);\n        const okTexto = !textoActivo ? true : (d.nombre || \"\").toLowerCase().includes(texto);\n\n        const okComarca = !activosCom\n          ? true\n          : (d.comarcas || []).some(nombre => filtros[`comarca_${nombre}`] === true);\n\n        \/\/ \u2705 NUEVO: facturaci\u00f3n\n        const okFacturacion = !hayFiltroFacturacion ? true : ( d.facturacion !== null && d.facturacion >= filtros.facturacion_min && d.facturacion <= filtros.facturacion_max  );\n\n\n        const visible =\n\t\t  okSub &&\n\t\t  okTipo &&\n\t\t  okTam &&\n\t\t  okTexto &&\n\t\t  okComarca &&\n\t\t  okFacturacion;\n\n\n        m.map = visible ? mapa : null;\n        if (visible) visibles.push(m);\n      });\n\n      clusterer.clearMarkers();\n      clusterer.addMarkers(visibles);\n\n      paginaActual = 1;\n      renderizarLista(visibles);\n    }\n\n    \/* =========================\n       INIT\n    ========================== *\/\n    renderizarFiltrosSectoresJerarquico();\n    actualizar();\n\n    if (loadingEl) {\n      setTimeout(() => { loadingEl.style.display = \"none\"; }, 150);\n    }\n\n  });\n});\n<\/script>\n\n<style>\n\/* =========================\n   LOADING MAPA\n   ========================= *\/\n#mapaempresas { position: relative; } \/* overlay seguro *\/\n\n#mapa-loading {\n  position: absolute;\n  inset: 0;\n  z-index: 20;\n\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n\n  background: rgba(255,255,255,0.75);\n  backdrop-filter: blur(2px);\n\n  font-weight: 600;\n  color: #004868;\n  pointer-events: none;\n}\n\n#mapa-loading .spinner {\n  width: 42px;\n  height: 42px;\n  border: 4px solid rgba(0,72,104,0.2);\n  border-top-color: #004868;\n  border-radius: 50%;\n  animation: spin 0.8s linear infinite;\n  margin-bottom: 12px;\n}\n@keyframes spin { to { transform: rotate(360deg); } }\n\n\/* =========================\n   LAYOUT (tus estilos)\n   ========================= *\/\n#mapaempresas {\n  display: flex;\n  height: calc(100vh - 90px);\n  width: 100%;\n  overflow: hidden;\n  margin: 0;\n}\n\n#info {\n  width: 40%;\n  min-width: 250px;\n  max-width: 70vw;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n}\n\n#mapa {\n  flex: 1;\n  min-width: 200px;\n  height: 100%;\n}\n\n\/* Separadores *\/\n.divider {\n  background: #ccc;\n  opacity: 0.8;\n  z-index: 5;\n}\n.divider.vertical { width: 6px; cursor: col-resize; }\n.divider.horizontal { height: 6px; cursor: row-resize; }\n.divider:hover { background: #999; }\n\n\/* Filtros *\/\n#filtros-sectores {\n  position: relative;\n  padding: 1em;\n  overflow-y: auto;\n  border-bottom: 1px solid #ccc;\n  max-height: 50%;\n}\n\n\/* Bot\u00f3n reset fijo *\/\n#btn-reset-filtros {\n  position: sticky;\n  top: 0;\n  z-index: 10;\n\n  background: rgba(0, 71, 104, 0.9);\n  color: #fff;\n  border: none;\n  padding: 10px 14px;\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  width: 100%;\n  font-weight: 700;\n  transition: background 0.2s;\n  backdrop-filter: blur(4px);\n  margin: 10px 0 10px 0;\n}\n#btn-reset-filtros:hover {\n  background: rgba(0, 57, 85, 0.95);\n}\n\n\/* Lista *\/\n#lista-empresas {\n  flex: 1;\n  overflow-y: auto;\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1%;\n  padding: 1em;\n  justify-content: space-evenly;\n  align-content: flex-start;\n  max-height: 50%;\n}\n\n#lista-empresas .empresa {\n  background-color: #efefef;\n  padding: 10px;\n  width: 32%;\n  box-sizing: border-box;\n  border-radius: 6px;\n  cursor: pointer;\n  transition: background 0.2s;\n  margin-bottom: 10px;\n}\n#lista-empresas .empresa:hover { background-color: #ddd; }\n\n\/* Paginaci\u00f3n *\/\n#paginacion {\n  text-align: center;\n  padding: 0.5em;\n  border-top: 1px solid #ccc;\n  background: #f9f9f9;\n}\n#paginacion button {\n  padding: 6px 12px;\n  margin: 0 5px;\n  background: #004868;\n  color: white;\n  border: none;\n  border-radius: 4px;\n  cursor: pointer;\n}\n#paginacion button:disabled {\n  background: #ccc;\n  cursor: not-allowed;\n}\n\n.subsectores {\n  margin-left: 1.5em;\n  padding-left: 0.5em;\n  border-left: 1px solid rgba(0,0,0,.15);\n}\n.subsectores .subsector {\n  display: block;\n  margin-bottom: 0.2em;\n}\n\n\/* =========================\n   FACTURACI\u00d3N \u2013 SLIDER\n   ========================= *\/\n\n.facturacion-range {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  margin-bottom: 8px;\n}\n\n.facturacion-range span {\n  font-size: 12px;\n  font-weight: 600;\n  color: #004868;\n  width: 38px;\n  text-align: right;\n}\n\n.range-input {\n  -webkit-appearance: none;\n  appearance: none;\n  width: 100%;\n  height: 6px;\n  border-radius: 3px;\n  background: linear-gradient(\n    to right,\n    #cfd8dc,\n    #004868\n  );\n  outline: none;\n  cursor: pointer;\n}\n\n\/* Thumb *\/\n.range-input::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  appearance: none;\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  background: #004868;\n  border: 2px solid #fff;\n  box-shadow: 0 0 0 1px rgba(0,0,0,.15);\n  cursor: pointer;\n}\n\n.range-input::-moz-range-thumb {\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  background: #004868;\n  border: 2px solid #fff;\n  cursor: pointer;\n}\n\n\/* Diferenciar m\u00ednimo \/ m\u00e1ximo *\/\n.facturacion-range.min .range-input {\n  background: linear-gradient(to right, #004868, #cfd8dc);\n}\n\n.facturacion-range.max .range-input {\n  background: linear-gradient(to right, #cfd8dc, #004868);\n}\n\n\n.infowindow-empresa {\n  font-size: 13px;\n  line-height: 1.35;\n  color: #222;\n  max-width: 260px;\n}\n\n.infowindow-empresa .iw-titulo {\n  display: block;\n  font-size: 15px;\n  margin-bottom: 4px;\n  color: #004868;\n}\n\n.infowindow-empresa .iw-bloque {\n  margin-top: 4px;\n}\n\n.infowindow-empresa a {\n  color: #004868;\n  font-weight: 600;\n  text-decoration: none;\n}\n\n.infowindow-empresa a:hover {\n  text-decoration: underline;\n}\n\n\n\n.empresa-card {\n  font-size: 13px;\n  line-height: 1.35;\n  color: #222;\n}\n\n.empresa-nombre {\n  display: block;\n  font-size: 14px;\n  margin-bottom: 4px;\n  color: #004868;\n}\n\n.empresa-linea {\n  margin-top: 3px;\n}\n\n.empresa-web a {\n  font-weight: 600;\n  color: #004868;\n  text-decoration: none;\n}\n\n.empresa-web a:hover {\n  text-decoration: underline;\n}\n\n.empresa-card {\n  display: flex;\n  gap: 10px;\n  font-size: 13px;\n  line-height: 1.35;\n  color: #222;\n}\n\n.empresa-sector-dot {\n  width: 10px;\n  height: 10px;\n  border-radius: 50%;\n  margin-top: 6px;\n  flex-shrink: 0;\n}\n\n.empresa-content {\n  flex: 1;\n}\n\n.empresa-nombre {\n  display: block;\n  font-size: 14px;\n  margin-bottom: 4px;\n  color: #004868;\n}\n\n\n\n\/* Responsive *\/\n@media (max-width: 1024px) {\n  #mapaempresas { flex-direction: column; height: auto; }\n  #info { width: 100% !important; max-width: 100% !important; min-width: 100% !important; height: auto; }\n  #mapa { width: 100% !important; height: 45vh !important; min-height: 300px; }\n  .divider.horizontal, .divider.vertical { display: none; }\n  #filtros-sectores, #lista-empresas { max-height: none !important; height: auto !important; }\n}\n\n@media (max-width: 480px) {\n  #lista-empresas .empresa { width: 100% !important; font-size: 14px; }\n}\n<\/style>\n\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"_et_pb_use_builder":"on","_et_pb_old_content":"","_et_gb_content_width":"","footnotes":""},"class_list":["post-15167","page","type-page","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.investincastellon.com\/en\/wp-json\/wp\/v2\/pages\/15167","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.investincastellon.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.investincastellon.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.investincastellon.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.investincastellon.com\/en\/wp-json\/wp\/v2\/comments?post=15167"}],"version-history":[{"count":2,"href":"https:\/\/www.investincastellon.com\/en\/wp-json\/wp\/v2\/pages\/15167\/revisions"}],"predecessor-version":[{"id":15169,"href":"https:\/\/www.investincastellon.com\/en\/wp-json\/wp\/v2\/pages\/15167\/revisions\/15169"}],"wp:attachment":[{"href":"https:\/\/www.investincastellon.com\/en\/wp-json\/wp\/v2\/media?parent=15167"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}