zenit-sdk 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -298,8 +298,122 @@ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, op
298
298
  var import_react = require("react");
299
299
  var import_react_leaflet = require("react-leaflet");
300
300
  var import_leaflet = __toESM(require("leaflet"));
301
+
302
+ // src/react/map/geojson-sanitize.ts
303
+ var warnedKeys = /* @__PURE__ */ new Set();
304
+ function isFiniteNumber(value) {
305
+ if (typeof value === "number") return Number.isFinite(value);
306
+ if (typeof value === "string") return Number.isFinite(Number(value));
307
+ return false;
308
+ }
309
+ function isValidLonLat(lon, lat) {
310
+ const lonN = typeof lon === "string" ? Number(lon) : lon;
311
+ const latN = typeof lat === "string" ? Number(lat) : lat;
312
+ if (!isFiniteNumber(lonN) || !isFiniteNumber(latN)) return false;
313
+ if (typeof lonN !== "number" || typeof latN !== "number") return false;
314
+ return lonN >= -180 && lonN <= 180 && latN >= -90 && latN <= 90;
315
+ }
316
+ function asFeatureCollection(input) {
317
+ const raw = input && typeof input === "object" && "data" in input ? input.data : input;
318
+ if (!raw || typeof raw !== "object") {
319
+ return { type: "FeatureCollection", features: [] };
320
+ }
321
+ if (raw.type === "FeatureCollection") {
322
+ const collection = raw;
323
+ const features = Array.isArray(collection.features) ? collection.features : [];
324
+ return { ...collection, features };
325
+ }
326
+ if (raw.type === "Feature") {
327
+ return {
328
+ type: "FeatureCollection",
329
+ features: [raw]
330
+ };
331
+ }
332
+ return { type: "FeatureCollection", features: [] };
333
+ }
334
+ function sanitizeFeature(feature) {
335
+ if (!feature || typeof feature !== "object") return null;
336
+ const geometry = feature.geometry;
337
+ if (!geometry || typeof geometry !== "object") return null;
338
+ const geometryType = geometry.type;
339
+ const coordinates = geometry.coordinates;
340
+ if (geometryType === "Point") {
341
+ if (!Array.isArray(coordinates) || coordinates.length < 2) return null;
342
+ return isValidLonLat(coordinates[0], coordinates[1]) ? feature : null;
343
+ }
344
+ if (geometryType === "MultiPoint") {
345
+ if (!Array.isArray(coordinates)) return null;
346
+ const validCoordinates = coordinates.filter((coordinate) => {
347
+ if (!Array.isArray(coordinate) || coordinate.length < 2) return false;
348
+ return isValidLonLat(coordinate[0], coordinate[1]);
349
+ });
350
+ if (validCoordinates.length === 0) return null;
351
+ if (validCoordinates.length === coordinates.length) {
352
+ return feature;
353
+ }
354
+ return {
355
+ ...feature,
356
+ geometry: {
357
+ ...geometry,
358
+ coordinates: validCoordinates
359
+ }
360
+ };
361
+ }
362
+ if (geometryType === "LineString" || geometryType === "Polygon" || geometryType === "MultiPolygon" || geometryType === "MultiLineString") {
363
+ return Array.isArray(coordinates) && coordinates.length > 0 ? feature : null;
364
+ }
365
+ return feature;
366
+ }
367
+ function logSanitizeStats(stats, debugKey) {
368
+ if (process.env.NODE_ENV === "production") return;
369
+ if (stats.droppedFeatures === 0 && stats.droppedPointsFromMultiPoint === 0) return;
370
+ const resolvedKey = debugKey ?? "__default__";
371
+ if (warnedKeys.has(resolvedKey)) return;
372
+ warnedKeys.add(resolvedKey);
373
+ console.warn("[zenit-sdk] GeoJSON sanitization dropped invalid geometries.", {
374
+ layerId: debugKey,
375
+ ...stats
376
+ });
377
+ }
378
+ function sanitizeGeoJson(input, debugKey) {
379
+ const featureCollection = asFeatureCollection(input);
380
+ const safeFeatures = [];
381
+ const stats = {
382
+ droppedFeatures: 0,
383
+ droppedPointsFromMultiPoint: 0
384
+ };
385
+ const rawFeatures = Array.isArray(featureCollection.features) ? featureCollection.features : [];
386
+ rawFeatures.forEach((rawFeature) => {
387
+ const feature = rawFeature;
388
+ const geometryType = feature?.geometry?.type;
389
+ const originalCoords = feature?.geometry?.coordinates;
390
+ const sanitized = sanitizeFeature(feature);
391
+ if (!sanitized) {
392
+ stats.droppedFeatures += 1;
393
+ if (geometryType === "MultiPoint" && Array.isArray(originalCoords)) {
394
+ stats.droppedPointsFromMultiPoint += originalCoords.length;
395
+ }
396
+ return;
397
+ }
398
+ if (geometryType === "MultiPoint" && Array.isArray(originalCoords)) {
399
+ const safeCoords = sanitized.geometry?.coordinates;
400
+ const safeCount = Array.isArray(safeCoords) ? safeCoords.length : 0;
401
+ stats.droppedPointsFromMultiPoint += Math.max(0, originalCoords.length - safeCount);
402
+ }
403
+ safeFeatures.push(sanitized);
404
+ });
405
+ logSanitizeStats(stats, debugKey);
406
+ return {
407
+ ...featureCollection,
408
+ type: "FeatureCollection",
409
+ features: safeFeatures
410
+ };
411
+ }
412
+
413
+ // src/react/map/layer-geojson.tsx
301
414
  var import_jsx_runtime = require("react/jsx-runtime");
302
415
  var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
416
+ var DEV_MODE = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
303
417
  function normalizeBboxFromData(data) {
304
418
  const bboxCandidate = data.bbox;
305
419
  if (!Array.isArray(bboxCandidate) || bboxCandidate.length < 4) return null;
@@ -332,6 +446,45 @@ function isNonPointGeometry(feature) {
332
446
  const geometryType = getGeometryType(feature);
333
447
  return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
334
448
  }
449
+ function isValidLatLng(latlng) {
450
+ const candidate = latlng;
451
+ return Boolean(
452
+ candidate && Number.isFinite(candidate.lat) && Number.isFinite(candidate.lng)
453
+ );
454
+ }
455
+ function createInvisibleFallbackMarker(latlng) {
456
+ return import_leaflet.default.circleMarker(latlng ?? [0, 0], {
457
+ radius: 0,
458
+ opacity: 0,
459
+ fillOpacity: 0,
460
+ interactive: false
461
+ });
462
+ }
463
+ function createInvisibleFallbackClusterMarker(latlng) {
464
+ return import_leaflet.default.marker(latlng ?? [0, 0], {
465
+ opacity: 0,
466
+ interactive: false
467
+ });
468
+ }
469
+ function ensurePaneName(mapInstance, paneName, fallbackPaneName) {
470
+ if (!mapInstance) return fallbackPaneName;
471
+ const existingPane = mapInstance.getPane(paneName) ?? mapInstance.createPane(paneName);
472
+ if (!existingPane && DEV_MODE) {
473
+ console.warn("[LayerGeoJson] pane unavailable, using fallback", { paneName, fallbackPaneName });
474
+ }
475
+ return mapInstance.getPane(paneName) ? paneName : fallbackPaneName;
476
+ }
477
+ function createPointDivIcon(style, isMobile) {
478
+ const size = isMobile ? 18 : 14;
479
+ const backgroundColor = style.fillColor ?? style.color ?? "#3388ff";
480
+ const borderColor = style.color ?? style.fillColor ?? "#3388ff";
481
+ return import_leaflet.default.divIcon({
482
+ className: "zenit-point-marker",
483
+ html: `<div style="width:${size}px;height:${size}px;border-radius:50%;background:${backgroundColor};border:2px solid ${borderColor};"></div>`,
484
+ iconSize: [size, size],
485
+ iconAnchor: [size / 2, size / 2]
486
+ });
487
+ }
335
488
  function buildFeatureCollection(features) {
336
489
  return {
337
490
  type: "FeatureCollection",
@@ -352,14 +505,15 @@ var LayerGeoJson = ({
352
505
  onEachFeature,
353
506
  onPolygonLabel
354
507
  }) => {
355
- const features = data.features ?? [];
356
- const fillFeatures = features.filter(isNonPointGeometry);
357
- const pointFeatures = features.filter(isPointGeometry);
508
+ const safeData = (0, import_react.useMemo)(() => sanitizeGeoJson(data, String(layerId)), [data, layerId]);
509
+ const features = (0, import_react.useMemo)(() => safeData.features ?? [], [safeData]);
510
+ const fillFeatures = (0, import_react.useMemo)(() => features.filter(isNonPointGeometry), [features]);
511
+ const pointFeatures = (0, import_react.useMemo)(() => features.filter(isPointGeometry), [features]);
358
512
  const dataVersionRef = (0, import_react.useRef)(0);
359
513
  const prevSignatureRef = (0, import_react.useRef)("");
360
514
  const firstId = features.length > 0 ? String(features[0]?.id ?? "") : "";
361
515
  const lastId = features.length > 0 ? String(features[features.length - 1]?.id ?? "") : "";
362
- const bbox = normalizeBboxFromData(data);
516
+ const bbox = normalizeBboxFromData(safeData);
363
517
  const idsSample = buildIdsSample(features);
364
518
  const signature = bbox ? `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}|${bbox[0]}|${bbox[1]}|${bbox[2]}|${bbox[3]}` : `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}`;
365
519
  const signatureToken = signature.replace(/[^a-zA-Z0-9_-]/g, "_");
@@ -367,25 +521,40 @@ var LayerGeoJson = ({
367
521
  dataVersionRef.current += 1;
368
522
  prevSignatureRef.current = signature;
369
523
  }
370
- const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
371
- const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
524
+ const fillData = (0, import_react.useMemo)(() => fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null, [fillFeatures]);
525
+ const pointsData = (0, import_react.useMemo)(() => pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null, [pointFeatures]);
372
526
  const clusterLayerRef = (0, import_react.useRef)(null);
373
527
  const canCluster = typeof import_leaflet.default.markerClusterGroup === "function";
528
+ const resolvedFillPane = (0, import_react.useMemo)(
529
+ () => ensurePaneName(mapInstance, fillPaneName, "overlayPane"),
530
+ [fillPaneName, mapInstance]
531
+ );
532
+ const resolvedPointsPane = (0, import_react.useMemo)(
533
+ () => ensurePaneName(mapInstance, pointsPaneName, "markerPane"),
534
+ [mapInstance, pointsPaneName]
535
+ );
374
536
  (0, import_react.useEffect)(() => {
375
537
  if (!mapInstance || !panesReady || !pointsData || !canCluster) return;
376
538
  const markerClusterGroup = import_leaflet.default.markerClusterGroup;
377
- const clusterLayer = clusterLayerRef.current ?? markerClusterGroup();
539
+ const clusterPaneName = resolvedPointsPane;
540
+ const clusterLayer = clusterLayerRef.current ?? markerClusterGroup({ pane: clusterPaneName, clusterPane: clusterPaneName });
378
541
  clusterLayerRef.current = clusterLayer;
379
542
  if (!mapInstance.hasLayer(clusterLayer)) {
380
543
  mapInstance.addLayer(clusterLayer);
381
544
  }
382
545
  clusterLayer.clearLayers();
383
546
  const geoJsonLayer = import_leaflet.default.geoJSON(pointsData, {
384
- pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
385
- radius: isMobile ? 8 : 6,
386
- pane: mapInstance.getPane(pointsPaneName) ? pointsPaneName : void 0,
387
- ...styleFn(feature, layerType, baseOpacity)
388
- }),
547
+ pointToLayer: (feature, latlng) => {
548
+ if (!isValidLatLng(latlng)) {
549
+ return createInvisibleFallbackClusterMarker();
550
+ }
551
+ const style = styleFn(feature, layerType, baseOpacity);
552
+ return import_leaflet.default.marker(latlng, {
553
+ icon: createPointDivIcon(style, isMobile),
554
+ pane: clusterPaneName,
555
+ interactive: true
556
+ });
557
+ },
389
558
  onEachFeature
390
559
  });
391
560
  clusterLayer.addLayer(geoJsonLayer);
@@ -404,7 +573,7 @@ var LayerGeoJson = ({
404
573
  onEachFeature,
405
574
  panesReady,
406
575
  pointsData,
407
- pointsPaneName,
576
+ resolvedPointsPane,
408
577
  styleFn
409
578
  ]);
410
579
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -412,7 +581,7 @@ var LayerGeoJson = ({
412
581
  import_react_leaflet.GeoJSON,
413
582
  {
414
583
  data: fillData,
415
- pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
584
+ pane: resolvedFillPane,
416
585
  style: (feature) => styleFn(feature, layerType, baseOpacity),
417
586
  onEachFeature: (feature, layer) => {
418
587
  onEachFeature(feature, layer);
@@ -425,11 +594,16 @@ var LayerGeoJson = ({
425
594
  import_react_leaflet.GeoJSON,
426
595
  {
427
596
  data: pointsData,
428
- pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
429
- pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
430
- radius: isMobile ? 8 : 6,
431
- ...styleFn(feature, layerType, baseOpacity)
432
- }),
597
+ pane: resolvedPointsPane,
598
+ pointToLayer: (feature, latlng) => {
599
+ if (!isValidLatLng(latlng)) {
600
+ return createInvisibleFallbackMarker();
601
+ }
602
+ return import_leaflet.default.circleMarker(latlng, {
603
+ radius: isMobile ? 8 : 6,
604
+ ...styleFn(feature, layerType, baseOpacity)
605
+ });
606
+ },
433
607
  onEachFeature
434
608
  },
435
609
  `points-${layerId}-${signatureToken}-v${dataVersionRef.current}`
@@ -511,7 +685,22 @@ function useGeolocation(options) {
511
685
  var import_leaflet2 = __toESM(require("leaflet"));
512
686
  var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
513
687
  var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry", "_private"]);
514
- var POPUP_HEADER_KEYS = ["nombre", "name", "title", "titulo"];
688
+ var POPUP_TITLE_KEYS = ["id", "nombre", "name", "title", "titulo", "cluster"];
689
+ var POPUP_BADGE_KEYS = ["tipo", "type", "category", "categoria", "estado", "status"];
690
+ var POPUP_DESCRIPTION_KEYS = ["descripcion", "description", "desc"];
691
+ var CURRENCY_KEYWORDS = [
692
+ "capital",
693
+ "monto",
694
+ "precio",
695
+ "costo",
696
+ "valor",
697
+ "ingreso",
698
+ "egreso",
699
+ "saldo",
700
+ "subtotal",
701
+ "venta",
702
+ "compra"
703
+ ];
515
704
  var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 360, minWidth: 280, maxHeight: 520 };
516
705
  var MOBILE_POPUP_DIMENSIONS = { maxWidth: 300, minWidth: 240, maxHeight: 420 };
517
706
  var ZENIT_LEAFLET_POPUP_STYLES = `
@@ -629,6 +818,68 @@ function escapeHtml(value) {
629
818
  function formatLabel(key) {
630
819
  return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
631
820
  }
821
+ function isHexColor(value) {
822
+ return /^#([0-9A-F]{3}){1,2}$/i.test(value.trim());
823
+ }
824
+ function isCurrencyField(key) {
825
+ const normalized = key.trim().toLowerCase();
826
+ return CURRENCY_KEYWORDS.some((keyword) => normalized.includes(keyword));
827
+ }
828
+ function formatCurrency(value) {
829
+ return new Intl.NumberFormat("es-GT", { style: "currency", currency: "GTQ" }).format(value);
830
+ }
831
+ function getBadgeForProperty(key, value) {
832
+ if (value === null || value === void 0) return null;
833
+ const normalizedKey = key.trim().toLowerCase();
834
+ const normalizedValue = String(value).trim().toLowerCase();
835
+ const byType = {
836
+ mora: { label: "Mora", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" },
837
+ castigo: { label: "Castigo", bg: "#ffedd5", color: "#c2410c", border: "#fed7aa" },
838
+ colocacion: { label: "Colocaci\xF3n", bg: "#dbeafe", color: "#1d4ed8", border: "#bfdbfe" },
839
+ sano: { label: "Sano", bg: "#dcfce7", color: "#15803d", border: "#bbf7d0" }
840
+ };
841
+ const byStatus = {
842
+ activo: { label: "Activo", bg: "#dcfce7", color: "#15803d", border: "#bbf7d0" },
843
+ inactivo: { label: "Inactivo", bg: "#fef9c3", color: "#a16207", border: "#fde68a" },
844
+ pendiente: { label: "Pendiente", bg: "#fef9c3", color: "#a16207", border: "#fde68a" },
845
+ cancelado: { label: "Cancelado", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" },
846
+ rechazado: { label: "Rechazado", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" }
847
+ };
848
+ if (["tipo", "type", "category", "categoria"].includes(normalizedKey)) {
849
+ return byType[normalizedValue] ?? {
850
+ label: String(value),
851
+ bg: "#e0e7ff",
852
+ color: "#4338ca",
853
+ border: "#c7d2fe"
854
+ };
855
+ }
856
+ if (["estado", "status"].includes(normalizedKey)) {
857
+ return byStatus[normalizedValue] ?? {
858
+ label: String(value),
859
+ bg: "#f1f5f9",
860
+ color: "#475569",
861
+ border: "#cbd5e1"
862
+ };
863
+ }
864
+ return null;
865
+ }
866
+ function findHeaderProperties(properties) {
867
+ const entries = Object.entries(properties);
868
+ const titleEntry = entries.find(
869
+ ([key, value]) => POPUP_TITLE_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
870
+ );
871
+ const badgeEntry = entries.find(
872
+ ([key, value]) => POPUP_BADGE_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
873
+ );
874
+ const descriptionEntry = entries.find(
875
+ ([key, value]) => POPUP_DESCRIPTION_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
876
+ );
877
+ return {
878
+ title: titleEntry ? { key: titleEntry[0], value: String(titleEntry[1]).trim() } : void 0,
879
+ badge: badgeEntry ? { key: badgeEntry[0], value: badgeEntry[1] } : void 0,
880
+ description: descriptionEntry ? { key: descriptionEntry[0], value: String(descriptionEntry[1]).trim() } : void 0
881
+ };
882
+ }
632
883
  function renderPopupValue(value) {
633
884
  if (value === null || value === void 0) return '<span style="color:#94a3b8;">Sin datos</span>';
634
885
  if (value instanceof Date) {
@@ -654,12 +905,8 @@ function renderPopupValue(value) {
654
905
  return escapeHtml(String(value));
655
906
  }
656
907
  function extractPopupHeader(properties) {
657
- const entry = Object.entries(properties).find(([key, value]) => {
658
- if (typeof value !== "string") return false;
659
- const normalized = key.trim().toLowerCase();
660
- return POPUP_HEADER_KEYS.includes(normalized) && value.trim().length > 0;
661
- });
662
- return entry ? entry[1].trim() : null;
908
+ const header = findHeaderProperties(properties);
909
+ return header.title?.value ?? null;
663
910
  }
664
911
  function shouldIncludePopupEntry(key, value) {
665
912
  if (!key) return false;
@@ -672,21 +919,36 @@ function shouldIncludePopupEntry(key, value) {
672
919
  return true;
673
920
  }
674
921
  function createPopupContent(properties) {
675
- const headerText = extractPopupHeader(properties);
922
+ const header = findHeaderProperties(properties);
923
+ const headerText = header.title?.value ?? extractPopupHeader(properties);
924
+ const usedKeys = new Set([header.title?.key, header.badge?.key, header.description?.key].filter(Boolean));
925
+ const badge = header.badge ? getBadgeForProperty(header.badge.key, header.badge.value) : null;
926
+ const colorValue = properties.color;
927
+ const colorBar = typeof colorValue === "string" && isHexColor(colorValue) ? `<div style="height:6px; border-radius:8px; margin:-2px -2px 10px; background:linear-gradient(90deg, ${colorValue}, rgba(255,255,255,0.95));"></div>` : "";
676
928
  const entries = Object.entries(properties).filter(([key, value]) => {
677
929
  if (!shouldIncludePopupEntry(key, value)) return false;
678
- if (headerText && POPUP_HEADER_KEYS.includes(key.trim().toLowerCase())) return false;
930
+ if (usedKeys.has(key)) return false;
679
931
  return true;
680
932
  });
681
933
  if (entries.length === 0) {
682
934
  return '<div style="padding:8px 0; color:#64748b; text-align:center;">Sin datos disponibles</div>';
683
935
  }
684
- const headerHtml = headerText ? `<div style="font-weight:700; font-size:14px; margin-bottom:8px; color:#0f172a;">${escapeHtml(
685
- headerText
936
+ const headerHtml = headerText || badge ? `<div style="display:flex; justify-content:space-between; gap:8px; align-items:flex-start; margin-bottom:8px;">
937
+ <div style="font-weight:700; font-size:14px; color:#0f172a;">${escapeHtml(headerText ?? "Detalle")}</div>
938
+ ${badge ? `<span style="display:inline-flex; padding:2px 8px; border-radius:999px; font-size:11px; font-weight:700; background:${badge.bg}; color:${badge.color}; border:1px solid ${badge.border}; white-space:nowrap;">${escapeHtml(
939
+ badge.label
940
+ )}</span>` : ""}
941
+ </div>` : "";
942
+ const descriptionHtml = header.description ? `<div style="margin-bottom:10px; padding:8px 10px; background:#f8fafc; border-left:3px solid #38bdf8; border-radius:6px; color:#334155; font-size:12px;">${escapeHtml(
943
+ header.description.value
686
944
  )}</div>` : "";
687
945
  const rowsHtml = entries.map(([key, value]) => {
688
946
  const label = escapeHtml(formatLabel(key));
689
- const valueHtml = renderPopupValue(value);
947
+ const normalizedKey = key.trim().toLowerCase();
948
+ let valueHtml = renderPopupValue(value);
949
+ if (typeof value === "number") {
950
+ valueHtml = isCurrencyField(normalizedKey) ? `<span style="color:#15803d; font-weight:700;">${escapeHtml(formatCurrency(value))}</span>` : `<span style="color:#0369a1; font-weight:600;">${escapeHtml(value.toLocaleString("es-GT"))}</span>`;
951
+ }
690
952
  return `
691
953
  <div style="display:grid; grid-template-columns:minmax(90px, 35%) 1fr; gap:8px; padding:6px 0; border-bottom:1px solid #e2e8f0;">
692
954
  <div style="font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:0.04em; color:#64748b;">${label}</div>
@@ -694,7 +956,7 @@ function createPopupContent(properties) {
694
956
  </div>
695
957
  `;
696
958
  }).join("");
697
- return `<div>${headerHtml}${rowsHtml}</div>`;
959
+ return `<div>${colorBar}${headerHtml}${descriptionHtml}${rowsHtml}</div>`;
698
960
  }
699
961
  function isPolygonType(layerType, geometryType) {
700
962
  const candidate = (layerType ?? geometryType ?? "").toLowerCase();
@@ -704,21 +966,27 @@ function calculateZoomBasedOpacity(zoom, baseOpacity, layerType, geometryType) {
704
966
  if (!isPolygonType(layerType, geometryType)) return clampOpacity4(baseOpacity);
705
967
  const minZoom = 11;
706
968
  const maxZoom = 15;
707
- const minFactor = 0.3;
969
+ const minFactor = 0.5;
708
970
  if (maxZoom <= minZoom) return clampOpacity4(baseOpacity * minFactor);
709
971
  const t = clampNumber2((zoom - minZoom) / (maxZoom - minZoom), 0, 1);
710
972
  const factor = 1 - (1 - minFactor) * t;
711
973
  return clampOpacity4(baseOpacity * factor);
712
974
  }
713
975
  function layerStyleToLeaflet(options) {
714
- const { baseOpacity, zoom, layerStyle, geometryType, layerType } = options;
976
+ const { baseOpacity, zoom, layerStyle, geometryType, layerType, properties } = options;
715
977
  const sanitizedOpacity = clampOpacity4(baseOpacity);
716
978
  const zoomOpacity = calculateZoomBasedOpacity(zoom, sanitizedOpacity, layerType, geometryType);
717
- const styleFillOpacity = typeof layerStyle?.fillOpacity === "number" ? clampOpacity4(layerStyle.fillOpacity) : 0.8;
979
+ const badgeForType = getBadgeForProperty("type", properties?.type ?? properties?.tipo ?? properties?.category ?? properties?.categoria);
980
+ const badgeForStatus = getBadgeForProperty("status", properties?.status ?? properties?.estado);
981
+ const featureColor = (typeof properties?.color === "string" && properties.color.trim().length > 0 ? properties.color : void 0) ?? badgeForType?.color ?? badgeForStatus?.color;
982
+ const styleFillOpacity = typeof layerStyle?.fillOpacity === "number" ? clampOpacity4(layerStyle.fillOpacity) : 0.65;
983
+ const fallbackColor = "#2563eb";
984
+ const resolvedColor = featureColor ?? layerStyle?.color ?? layerStyle?.fillColor ?? fallbackColor;
985
+ const resolvedFillColor = featureColor ?? layerStyle?.fillColor ?? layerStyle?.color ?? fallbackColor;
718
986
  return {
719
- color: layerStyle?.color ?? layerStyle?.fillColor ?? "#2563eb",
987
+ color: resolvedColor,
720
988
  weight: layerStyle?.weight ?? 2,
721
- fillColor: layerStyle?.fillColor ?? layerStyle?.color ?? "#2563eb",
989
+ fillColor: resolvedFillColor,
722
990
  opacity: clampOpacity4(Math.max(0.35, zoomOpacity * 0.9)),
723
991
  fillOpacity: clampOpacity4(zoomOpacity * styleFillOpacity)
724
992
  };
@@ -1116,6 +1384,7 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
1116
1384
  var DEFAULT_CENTER = [0, 0];
1117
1385
  var DEFAULT_ZOOM = 3;
1118
1386
  var LABELS_PANE_NAME = "zenit-labels-pane";
1387
+ var DEV_MODE2 = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
1119
1388
  function isRecord(value) {
1120
1389
  return typeof value === "object" && value !== null;
1121
1390
  }
@@ -1139,6 +1408,8 @@ function getFeatureLayerId(feature) {
1139
1408
  return layerId;
1140
1409
  }
1141
1410
  var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
1411
+ var POINT_GEOMETRY_TYPES2 = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
1412
+ var POLYGON_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Polygon", "MultiPolygon"]);
1142
1413
  function normalizeDescriptionValue(value) {
1143
1414
  if (value === void 0 || value === null) return null;
1144
1415
  if (typeof value === "string") {
@@ -1158,6 +1429,31 @@ function extractDescriptionValue(properties) {
1158
1429
  if (!matches) return null;
1159
1430
  return normalizeDescriptionValue(matches[1]);
1160
1431
  }
1432
+ function normalizeLayerId(value) {
1433
+ if (value === null || value === void 0) return null;
1434
+ const normalized = String(value).trim();
1435
+ return normalized ? normalized : null;
1436
+ }
1437
+ function isLayerIdMatch(a, b) {
1438
+ const normalizedA = normalizeLayerId(a);
1439
+ const normalizedB = normalizeLayerId(b);
1440
+ return normalizedA !== null && normalizedB !== null && normalizedA === normalizedB;
1441
+ }
1442
+ function getClickIntent(params) {
1443
+ const geometryType = params.feature?.geometry?.type;
1444
+ if (geometryType && POINT_GEOMETRY_TYPES2.has(geometryType)) return "point";
1445
+ if (params.leafletLayer instanceof import_leaflet4.default.Marker) return "point";
1446
+ if (geometryType && POLYGON_GEOMETRY_TYPES.has(geometryType)) return "polygon";
1447
+ if (params.leafletLayer instanceof import_leaflet4.default.Path) return "polygon";
1448
+ return "unknown";
1449
+ }
1450
+ function candidateLayerId(candidate) {
1451
+ return getFeatureLayerId(candidate);
1452
+ }
1453
+ function isCandidateGeometryType(candidate, allowedTypes) {
1454
+ const geometryType = candidate?.geometry?.type;
1455
+ return Boolean(geometryType && allowedTypes.has(geometryType));
1456
+ }
1161
1457
  function getFeatureStyleOverrides(feature) {
1162
1458
  const candidate = feature?.properties?._style;
1163
1459
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return null;
@@ -1175,17 +1471,50 @@ function buildFeaturePopupHtml(feature) {
1175
1471
  const rendered = createPopupContent(properties);
1176
1472
  return rendered ? rendered : null;
1177
1473
  }
1178
- function pickIntersectFeature(baseFeature, candidates) {
1474
+ function pickIntersectFeature(params) {
1475
+ const { baseFeature, candidates, expectedLayerId, clickIntent } = params;
1179
1476
  if (!Array.isArray(candidates) || candidates.length === 0) return null;
1477
+ const pickByGeometry = (pool, intent) => {
1478
+ const preferredTypes = intent === "point" ? POINT_GEOMETRY_TYPES2 : intent === "polygon" ? POLYGON_GEOMETRY_TYPES : null;
1479
+ if (!preferredTypes) return null;
1480
+ const found = pool.find((candidate) => isCandidateGeometryType(candidate, preferredTypes));
1481
+ if (!found) return null;
1482
+ return { feature: found, reason: `geometry:${intent}` };
1483
+ };
1484
+ const getResult = (feature, reason) => ({
1485
+ feature,
1486
+ selectedIdx: candidates.findIndex((candidate) => candidate === feature),
1487
+ reason
1488
+ });
1489
+ const sameLayer = candidates.filter(
1490
+ (candidate) => isLayerIdMatch(candidateLayerId(candidate), expectedLayerId)
1491
+ );
1492
+ const geometryFromSameLayer = pickByGeometry(sameLayer, clickIntent);
1493
+ if (geometryFromSameLayer?.feature) {
1494
+ return getResult(geometryFromSameLayer.feature, `sameLayer>${geometryFromSameLayer.reason}`);
1495
+ }
1496
+ if (sameLayer.length > 0) {
1497
+ const sameLayerWithDescription = sameLayer.find(
1498
+ (candidate) => extractDescriptionValue(candidate?.properties)
1499
+ );
1500
+ if (sameLayerWithDescription) {
1501
+ return getResult(sameLayerWithDescription, "sameLayer>description");
1502
+ }
1503
+ }
1504
+ const geometryFallback = pickByGeometry(candidates, clickIntent);
1505
+ if (geometryFallback?.feature) {
1506
+ return getResult(geometryFallback.feature, `anyLayer>${geometryFallback.reason}`);
1507
+ }
1180
1508
  const baseId = baseFeature?.id;
1181
1509
  if (baseId !== void 0 && baseId !== null) {
1182
1510
  const matchById = candidates.find((candidate) => candidate?.id === baseId);
1183
- if (matchById) return matchById;
1511
+ if (matchById) return getResult(matchById, "fallback>baseId");
1184
1512
  }
1185
1513
  const matchWithDescription = candidates.find(
1186
1514
  (candidate) => extractDescriptionValue(candidate?.properties)
1187
1515
  );
1188
- return matchWithDescription ?? candidates[0];
1516
+ if (matchWithDescription) return getResult(matchWithDescription, "fallback>description");
1517
+ return getResult(candidates[0], "fallback>first");
1189
1518
  }
1190
1519
  function normalizeCenterTuple(center) {
1191
1520
  if (!center) return null;
@@ -1592,7 +1921,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
1592
1921
  }, [currentZoom, decoratedLayers, labelKeyIndex, layerMetaIndex, resolveLayerStyle]);
1593
1922
  const ensureLayerPanes = (0, import_react5.useCallback)(
1594
1923
  (targetMap, targetLayers) => {
1595
- const baseZIndex = 400;
1924
+ const fillBaseZIndex = 400;
1925
+ const pointsBaseZIndex = 700;
1596
1926
  targetLayers.forEach((layer) => {
1597
1927
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
1598
1928
  const orderOffset = Math.max(0, Math.min(order, 150));
@@ -1600,8 +1930,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
1600
1930
  const pointPaneName = `zenit-layer-${layer.layerId}-points`;
1601
1931
  const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
1602
1932
  const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
1603
- fillPane.style.zIndex = String(baseZIndex + orderOffset);
1604
- pointPane.style.zIndex = String(baseZIndex + orderOffset + 100);
1933
+ fillPane.style.zIndex = String(fillBaseZIndex + orderOffset);
1934
+ pointPane.style.zIndex = String(pointsBaseZIndex + orderOffset);
1605
1935
  });
1606
1936
  },
1607
1937
  []
@@ -1619,114 +1949,149 @@ var ZenitMap = (0, import_react5.forwardRef)(({
1619
1949
  setPanesReady(false);
1620
1950
  return;
1621
1951
  }
1622
- if (orderedLayers.length === 0) {
1952
+ if (orderedLayers.length === 0 && !overlayGeojson) {
1623
1953
  return;
1624
1954
  }
1625
1955
  const layerTargets = orderedLayers.map((layer) => ({
1626
1956
  layerId: layer.mapLayer.layerId,
1627
1957
  displayOrder: layer.displayOrder
1628
1958
  }));
1959
+ if (overlayGeojson) {
1960
+ layerTargets.push({ layerId: "overlay-geojson", displayOrder: 999 });
1961
+ }
1629
1962
  ensureLayerPanes(mapInstance, layerTargets);
1630
1963
  const first = layerTargets[0];
1631
- const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-fill`);
1964
+ const testPane = first ? mapInstance.getPane(`zenit-layer-${first.layerId}-fill`) : null;
1632
1965
  const labelsPane = mapInstance.getPane(LABELS_PANE_NAME);
1633
1966
  if (testPane && labelsPane) {
1634
1967
  setPanesReady(true);
1635
1968
  }
1636
- }, [mapInstance, orderedLayers, ensureLayerPanes]);
1637
- const overlayOnEachFeature = (0, import_react5.useMemo)(() => {
1638
- return (feature, layer) => {
1639
- const layerId = getFeatureLayerId(feature) ?? void 0;
1640
- const geometryType = feature?.geometry?.type;
1641
- const isPointFeature = geometryType === "Point" || geometryType === "MultiPoint" || layer instanceof import_leaflet4.default.CircleMarker;
1642
- const originalStyle = layer instanceof import_leaflet4.default.Path ? {
1643
- color: layer.options.color,
1644
- weight: layer.options.weight,
1645
- fillColor: layer.options.fillColor,
1646
- opacity: layer.options.opacity,
1647
- fillOpacity: layer.options.fillOpacity
1648
- } : null;
1649
- const originalRadius = layer instanceof import_leaflet4.default.CircleMarker ? layer.getRadius() : null;
1650
- if (featureInfoMode === "popup") {
1651
- const content = buildFeaturePopupHtml(feature);
1652
- if (content) {
1653
- const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
1654
- layer.bindPopup(content, {
1655
- maxWidth,
1656
- minWidth,
1657
- maxHeight,
1658
- className: "custom-leaflet-popup",
1659
- autoPan: true,
1660
- closeButton: true,
1661
- keepInView: true
1662
- });
1663
- }
1664
- }
1665
- if (isPointFeature && layer.bindTooltip) {
1666
- layer.bindTooltip("Click para ver detalle", {
1667
- sticky: true,
1668
- direction: "top",
1669
- opacity: 0.9,
1670
- className: "zenit-map-tooltip"
1969
+ }, [mapInstance, orderedLayers, overlayGeojson, ensureLayerPanes]);
1970
+ const overlayOnEachFeature = (0, import_react5.useCallback)((feature, layer) => {
1971
+ const layerId = getFeatureLayerId(feature) ?? void 0;
1972
+ const geometryType = feature?.geometry?.type;
1973
+ const clickIntent = getClickIntent({ feature, leafletLayer: layer });
1974
+ const isPointFeature = clickIntent === "point";
1975
+ const originalStyle = layer instanceof import_leaflet4.default.Path ? {
1976
+ color: layer.options.color,
1977
+ weight: layer.options.weight,
1978
+ fillColor: layer.options.fillColor,
1979
+ opacity: layer.options.opacity,
1980
+ fillOpacity: layer.options.fillOpacity
1981
+ } : null;
1982
+ const originalRadius = layer instanceof import_leaflet4.default.CircleMarker ? layer.getRadius() : null;
1983
+ if (featureInfoMode === "popup") {
1984
+ const content = buildFeaturePopupHtml(feature);
1985
+ if (content) {
1986
+ const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
1987
+ layer.bindPopup(content, {
1988
+ maxWidth,
1989
+ minWidth,
1990
+ maxHeight,
1991
+ className: "custom-leaflet-popup",
1992
+ autoClose: true,
1993
+ closeOnClick: true,
1994
+ autoPan: true,
1995
+ closeButton: true,
1996
+ keepInView: true,
1997
+ autoPanPadding: [50, 50]
1671
1998
  });
1672
1999
  }
1673
- layer.on("click", () => {
1674
- if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
1675
- const trackedFeature = feature;
1676
- if (!trackedFeature.__zenit_popup_loaded) {
1677
- trackedFeature.__zenit_popup_loaded = true;
1678
- client.layers.getLayerGeoJsonIntersect({
1679
- id: layerId,
1680
- geometry: feature.geometry
1681
- }).then((response) => {
1682
- const geo = extractGeoJsonFeatureCollection(response);
1683
- const candidates = geo?.features ?? [];
1684
- const resolved = pickIntersectFeature(feature, candidates);
1685
- if (!resolved?.properties) return;
1686
- const mergedProperties = {
1687
- ...trackedFeature.properties ?? {},
1688
- ...resolved.properties
1689
- };
1690
- trackedFeature.properties = mergedProperties;
1691
- const updatedHtml = buildFeaturePopupHtml({
1692
- ...feature,
1693
- properties: mergedProperties
1694
- });
1695
- if (updatedHtml && layer.setPopupContent) {
1696
- layer.setPopupContent(updatedHtml);
1697
- }
1698
- }).catch(() => {
1699
- trackedFeature.__zenit_popup_loaded = false;
1700
- });
1701
- }
1702
- }
1703
- onFeatureClick?.(feature, layerId);
2000
+ }
2001
+ if (isPointFeature && layer.bindTooltip) {
2002
+ layer.bindTooltip("Click para ver detalle", {
2003
+ sticky: true,
2004
+ direction: "top",
2005
+ opacity: 0.9,
2006
+ className: "zenit-map-tooltip"
1704
2007
  });
1705
- layer.on("mouseover", () => {
1706
- if (layer instanceof import_leaflet4.default.Path && originalStyle) {
1707
- layer.setStyle({
1708
- ...originalStyle,
1709
- weight: (originalStyle.weight ?? 2) + 1,
1710
- opacity: Math.min(1, (originalStyle.opacity ?? 1) + 0.2),
1711
- fillOpacity: Math.min(1, (originalStyle.fillOpacity ?? 0.8) + 0.1)
2008
+ }
2009
+ layer.on("click", () => {
2010
+ if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
2011
+ if (DEV_MODE2) {
2012
+ console.debug("[ZenitMap] click/intersect:start", {
2013
+ expectedLayerId: layerId,
2014
+ geometryType,
2015
+ clickIntent,
2016
+ leafletLayerType: layer?.constructor?.name,
2017
+ pane: layer?.options?.pane
1712
2018
  });
1713
2019
  }
1714
- if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
1715
- layer.setRadius(originalRadius + 1);
1716
- }
1717
- onFeatureHover?.(feature, layerId);
1718
- });
1719
- layer.on("mouseout", () => {
1720
- if (layer instanceof import_leaflet4.default.Path && originalStyle) {
1721
- layer.setStyle(originalStyle);
1722
- }
1723
- if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
1724
- layer.setRadius(originalRadius);
2020
+ const trackedFeature = feature;
2021
+ if (!trackedFeature.__zenit_popup_loaded) {
2022
+ trackedFeature.__zenit_popup_loaded = true;
2023
+ client.layers.getLayerGeoJsonIntersect({
2024
+ id: layerId,
2025
+ geometry: feature.geometry
2026
+ }).then((response) => {
2027
+ const geo = extractGeoJsonFeatureCollection(response);
2028
+ const candidates = geo?.features ?? [];
2029
+ const selection = pickIntersectFeature({
2030
+ baseFeature: feature,
2031
+ candidates,
2032
+ expectedLayerId: layerId,
2033
+ clickIntent
2034
+ });
2035
+ const resolved = selection?.feature;
2036
+ if (DEV_MODE2) {
2037
+ console.debug("[ZenitMap] click/intersect:result", {
2038
+ expectedLayerId: layerId,
2039
+ clickIntent,
2040
+ candidatesCount: candidates.length,
2041
+ candidates: candidates.map((candidate, idx) => ({
2042
+ idx,
2043
+ geomType: candidate?.geometry?.type,
2044
+ candidateLayerId: candidateLayerId(candidate),
2045
+ hasDescription: Boolean(extractDescriptionValue(candidate?.properties))
2046
+ })),
2047
+ selectedIdx: selection?.selectedIdx ?? -1,
2048
+ selectionRule: selection?.reason ?? "none"
2049
+ });
2050
+ }
2051
+ if (!resolved?.properties) return;
2052
+ const mergedProperties = {
2053
+ ...trackedFeature.properties ?? {},
2054
+ ...resolved.properties
2055
+ };
2056
+ trackedFeature.properties = mergedProperties;
2057
+ const updatedHtml = buildFeaturePopupHtml({
2058
+ ...feature,
2059
+ properties: mergedProperties
2060
+ });
2061
+ if (updatedHtml && layer.setPopupContent) {
2062
+ layer.setPopupContent(updatedHtml);
2063
+ }
2064
+ }).catch(() => {
2065
+ trackedFeature.__zenit_popup_loaded = false;
2066
+ });
1725
2067
  }
1726
- });
1727
- };
2068
+ }
2069
+ onFeatureClick?.(feature, layerId);
2070
+ });
2071
+ layer.on("mouseover", () => {
2072
+ if (layer instanceof import_leaflet4.default.Path && originalStyle) {
2073
+ layer.setStyle({
2074
+ ...originalStyle,
2075
+ weight: (originalStyle.weight ?? 2) + 1,
2076
+ opacity: Math.min(1, (originalStyle.opacity ?? 1) + 0.2),
2077
+ fillOpacity: Math.min(1, (originalStyle.fillOpacity ?? 0.8) + 0.1)
2078
+ });
2079
+ }
2080
+ if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
2081
+ layer.setRadius(originalRadius + 1);
2082
+ }
2083
+ onFeatureHover?.(feature, layerId);
2084
+ });
2085
+ layer.on("mouseout", () => {
2086
+ if (layer instanceof import_leaflet4.default.Path && originalStyle) {
2087
+ layer.setStyle(originalStyle);
2088
+ }
2089
+ if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
2090
+ layer.setRadius(originalRadius);
2091
+ }
2092
+ });
1728
2093
  }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
1729
- const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
2094
+ const buildLayerStyle = (0, import_react5.useCallback)((layerId, baseOpacity, feature, layerType) => {
1730
2095
  const style = resolveLayerStyle(layerId);
1731
2096
  const featureStyleOverrides = getFeatureStyleOverrides(feature);
1732
2097
  const resolvedStyle = featureStyleOverrides ? { ...style ?? {}, ...featureStyleOverrides } : style;
@@ -1737,14 +2102,15 @@ var ZenitMap = (0, import_react5.forwardRef)(({
1737
2102
  zoom: currentZoom,
1738
2103
  layerStyle: resolvedStyle,
1739
2104
  geometryType,
1740
- layerType: resolvedLayerType
2105
+ layerType: resolvedLayerType,
2106
+ properties: feature?.properties ?? void 0
1741
2107
  });
1742
- };
1743
- const makeStyleFnForLayer = (layerId) => {
2108
+ }, [currentZoom, resolveLayerStyle]);
2109
+ const makeStyleFnForLayer = (0, import_react5.useCallback)((layerId) => {
1744
2110
  return (feature, layerType, baseOpacity) => {
1745
2111
  return buildLayerStyle(layerId, baseOpacity ?? 1, feature, layerType);
1746
2112
  };
1747
- };
2113
+ }, [buildLayerStyle]);
1748
2114
  (0, import_react5.useImperativeHandle)(ref, () => ({
1749
2115
  setLayerOpacity: (layerId, opacity) => {
1750
2116
  upsertUiOverride(layerId, { overrideOpacity: opacity });
@@ -1899,7 +2265,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
1899
2265
  const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
1900
2266
  const labelKey = labelKeyIndex.get(String(layerState.mapLayer.layerId));
1901
2267
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react5.default.Fragment, { children: [
1902
- layerState.data && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2268
+ layerState.data && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1903
2269
  LayerGeoJson,
1904
2270
  {
1905
2271
  layerId: layerState.mapLayer.layerId,
@@ -1942,7 +2308,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
1942
2308
  )) : null
1943
2309
  ] }, layerState.mapLayer.layerId.toString());
1944
2310
  }),
1945
- overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2311
+ overlayGeojson && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1946
2312
  LayerGeoJson,
1947
2313
  {
1948
2314
  layerId: "overlay-geojson",
@@ -1976,7 +2342,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
1976
2342
  overflowY: "auto"
1977
2343
  },
1978
2344
  children: [
1979
- overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2345
+ overlayGeojson && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1980
2346
  "div",
1981
2347
  {
1982
2348
  style: {
@@ -2664,6 +3030,14 @@ var ZenitLayerManager = ({
2664
3030
  return String(a.mapLayer.layerId).localeCompare(String(b.mapLayer.layerId));
2665
3031
  });
2666
3032
  }, [effectiveStates, layers, resolveFeatureCount]);
3033
+ const hasPrefilters = (0, import_react7.useMemo)(() => {
3034
+ const candidates = [...mapLayers ?? [], ...map?.mapLayers ?? []];
3035
+ return candidates.some((layer) => {
3036
+ const record = layer;
3037
+ const applied = record.appliedFilters ?? record.prefilters ?? record.initialFilters ?? record.filters ?? (record.layerConfig?.appliedFilters ?? record.layerConfig?.prefilters);
3038
+ return !!applied && typeof applied === "object" && Object.keys(applied).length > 0;
3039
+ });
3040
+ }, [map?.mapLayers, mapLayers]);
2667
3041
  const filterableLayers = (0, import_react7.useMemo)(() => {
2668
3042
  return decoratedLayers.filter((entry) => {
2669
3043
  const prefilters = entry.mapLayer.layerConfig?.prefilters;
@@ -2736,6 +3110,11 @@ var ZenitLayerManager = ({
2736
3110
  setSelectedFilterValue("");
2737
3111
  }
2738
3112
  }, [filterFields, selectedFilterField]);
3113
+ (0, import_react7.useEffect)(() => {
3114
+ if (hasPrefilters && activeTab === "filters") {
3115
+ setActiveTab("layers");
3116
+ }
3117
+ }, [activeTab, hasPrefilters]);
2739
3118
  (0, import_react7.useEffect)(() => {
2740
3119
  if (activeTab !== "filters") return;
2741
3120
  if (!selectedFilterLayer || !selectedFilterField || !activeCatalogKey) return;
@@ -3136,7 +3515,7 @@ var ZenitLayerManager = ({
3136
3515
  ]
3137
3516
  }
3138
3517
  ),
3139
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3518
+ !hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3140
3519
  "button",
3141
3520
  {
3142
3521
  type: "button",
@@ -3153,8 +3532,9 @@ var ZenitLayerManager = ({
3153
3532
  )
3154
3533
  ] }),
3155
3534
  panelVisible && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { padding: "12px 10px 18px", overflowY: "auto", flex: 1, minHeight: 0 }, children: [
3535
+ hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zlm-badge", style: { marginBottom: 10 }, children: "Este mapa ya incluye filtros preaplicados" }),
3156
3536
  activeTab === "layers" && renderLayerCards(),
3157
- activeTab === "filters" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zlm-filter-panel", style: { display: "flex", flexDirection: "column", gap: 12, background: "#fff", border: "1px solid #e2e8f0", borderRadius: 12, padding: 12 }, children: !filterableLayers.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "#64748b", fontSize: 13 }, children: "No hay filtros disponibles para las capas de este mapa." }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3537
+ !hasPrefilters && activeTab === "filters" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zlm-filter-panel", style: { display: "flex", flexDirection: "column", gap: 12, background: "#fff", border: "1px solid #e2e8f0", borderRadius: 12, padding: 12 }, children: !filterableLayers.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { color: "#64748b", fontSize: 13 }, children: "No hay filtros disponibles para las capas de este mapa." }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3158
3538
  filterableLayers.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
3159
3539
  "Capa",
3160
3540
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(