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.
package/dist/index.js CHANGED
@@ -1595,8 +1595,122 @@ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, op
1595
1595
  var import_react = require("react");
1596
1596
  var import_react_leaflet = require("react-leaflet");
1597
1597
  var import_leaflet = __toESM(require("leaflet"));
1598
+
1599
+ // src/react/map/geojson-sanitize.ts
1600
+ var warnedKeys = /* @__PURE__ */ new Set();
1601
+ function isFiniteNumber(value) {
1602
+ if (typeof value === "number") return Number.isFinite(value);
1603
+ if (typeof value === "string") return Number.isFinite(Number(value));
1604
+ return false;
1605
+ }
1606
+ function isValidLonLat(lon, lat) {
1607
+ const lonN = typeof lon === "string" ? Number(lon) : lon;
1608
+ const latN = typeof lat === "string" ? Number(lat) : lat;
1609
+ if (!isFiniteNumber(lonN) || !isFiniteNumber(latN)) return false;
1610
+ if (typeof lonN !== "number" || typeof latN !== "number") return false;
1611
+ return lonN >= -180 && lonN <= 180 && latN >= -90 && latN <= 90;
1612
+ }
1613
+ function asFeatureCollection(input) {
1614
+ const raw = input && typeof input === "object" && "data" in input ? input.data : input;
1615
+ if (!raw || typeof raw !== "object") {
1616
+ return { type: "FeatureCollection", features: [] };
1617
+ }
1618
+ if (raw.type === "FeatureCollection") {
1619
+ const collection = raw;
1620
+ const features = Array.isArray(collection.features) ? collection.features : [];
1621
+ return { ...collection, features };
1622
+ }
1623
+ if (raw.type === "Feature") {
1624
+ return {
1625
+ type: "FeatureCollection",
1626
+ features: [raw]
1627
+ };
1628
+ }
1629
+ return { type: "FeatureCollection", features: [] };
1630
+ }
1631
+ function sanitizeFeature(feature) {
1632
+ if (!feature || typeof feature !== "object") return null;
1633
+ const geometry = feature.geometry;
1634
+ if (!geometry || typeof geometry !== "object") return null;
1635
+ const geometryType = geometry.type;
1636
+ const coordinates = geometry.coordinates;
1637
+ if (geometryType === "Point") {
1638
+ if (!Array.isArray(coordinates) || coordinates.length < 2) return null;
1639
+ return isValidLonLat(coordinates[0], coordinates[1]) ? feature : null;
1640
+ }
1641
+ if (geometryType === "MultiPoint") {
1642
+ if (!Array.isArray(coordinates)) return null;
1643
+ const validCoordinates = coordinates.filter((coordinate) => {
1644
+ if (!Array.isArray(coordinate) || coordinate.length < 2) return false;
1645
+ return isValidLonLat(coordinate[0], coordinate[1]);
1646
+ });
1647
+ if (validCoordinates.length === 0) return null;
1648
+ if (validCoordinates.length === coordinates.length) {
1649
+ return feature;
1650
+ }
1651
+ return {
1652
+ ...feature,
1653
+ geometry: {
1654
+ ...geometry,
1655
+ coordinates: validCoordinates
1656
+ }
1657
+ };
1658
+ }
1659
+ if (geometryType === "LineString" || geometryType === "Polygon" || geometryType === "MultiPolygon" || geometryType === "MultiLineString") {
1660
+ return Array.isArray(coordinates) && coordinates.length > 0 ? feature : null;
1661
+ }
1662
+ return feature;
1663
+ }
1664
+ function logSanitizeStats(stats, debugKey) {
1665
+ if (process.env.NODE_ENV === "production") return;
1666
+ if (stats.droppedFeatures === 0 && stats.droppedPointsFromMultiPoint === 0) return;
1667
+ const resolvedKey = debugKey ?? "__default__";
1668
+ if (warnedKeys.has(resolvedKey)) return;
1669
+ warnedKeys.add(resolvedKey);
1670
+ console.warn("[zenit-sdk] GeoJSON sanitization dropped invalid geometries.", {
1671
+ layerId: debugKey,
1672
+ ...stats
1673
+ });
1674
+ }
1675
+ function sanitizeGeoJson(input, debugKey) {
1676
+ const featureCollection = asFeatureCollection(input);
1677
+ const safeFeatures = [];
1678
+ const stats = {
1679
+ droppedFeatures: 0,
1680
+ droppedPointsFromMultiPoint: 0
1681
+ };
1682
+ const rawFeatures = Array.isArray(featureCollection.features) ? featureCollection.features : [];
1683
+ rawFeatures.forEach((rawFeature) => {
1684
+ const feature = rawFeature;
1685
+ const geometryType = feature?.geometry?.type;
1686
+ const originalCoords = feature?.geometry?.coordinates;
1687
+ const sanitized = sanitizeFeature(feature);
1688
+ if (!sanitized) {
1689
+ stats.droppedFeatures += 1;
1690
+ if (geometryType === "MultiPoint" && Array.isArray(originalCoords)) {
1691
+ stats.droppedPointsFromMultiPoint += originalCoords.length;
1692
+ }
1693
+ return;
1694
+ }
1695
+ if (geometryType === "MultiPoint" && Array.isArray(originalCoords)) {
1696
+ const safeCoords = sanitized.geometry?.coordinates;
1697
+ const safeCount = Array.isArray(safeCoords) ? safeCoords.length : 0;
1698
+ stats.droppedPointsFromMultiPoint += Math.max(0, originalCoords.length - safeCount);
1699
+ }
1700
+ safeFeatures.push(sanitized);
1701
+ });
1702
+ logSanitizeStats(stats, debugKey);
1703
+ return {
1704
+ ...featureCollection,
1705
+ type: "FeatureCollection",
1706
+ features: safeFeatures
1707
+ };
1708
+ }
1709
+
1710
+ // src/react/map/layer-geojson.tsx
1598
1711
  var import_jsx_runtime = require("react/jsx-runtime");
1599
1712
  var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
1713
+ var DEV_MODE = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
1600
1714
  function normalizeBboxFromData(data) {
1601
1715
  const bboxCandidate = data.bbox;
1602
1716
  if (!Array.isArray(bboxCandidate) || bboxCandidate.length < 4) return null;
@@ -1629,6 +1743,45 @@ function isNonPointGeometry(feature) {
1629
1743
  const geometryType = getGeometryType(feature);
1630
1744
  return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
1631
1745
  }
1746
+ function isValidLatLng(latlng) {
1747
+ const candidate = latlng;
1748
+ return Boolean(
1749
+ candidate && Number.isFinite(candidate.lat) && Number.isFinite(candidate.lng)
1750
+ );
1751
+ }
1752
+ function createInvisibleFallbackMarker(latlng) {
1753
+ return import_leaflet.default.circleMarker(latlng ?? [0, 0], {
1754
+ radius: 0,
1755
+ opacity: 0,
1756
+ fillOpacity: 0,
1757
+ interactive: false
1758
+ });
1759
+ }
1760
+ function createInvisibleFallbackClusterMarker(latlng) {
1761
+ return import_leaflet.default.marker(latlng ?? [0, 0], {
1762
+ opacity: 0,
1763
+ interactive: false
1764
+ });
1765
+ }
1766
+ function ensurePaneName(mapInstance, paneName, fallbackPaneName) {
1767
+ if (!mapInstance) return fallbackPaneName;
1768
+ const existingPane = mapInstance.getPane(paneName) ?? mapInstance.createPane(paneName);
1769
+ if (!existingPane && DEV_MODE) {
1770
+ console.warn("[LayerGeoJson] pane unavailable, using fallback", { paneName, fallbackPaneName });
1771
+ }
1772
+ return mapInstance.getPane(paneName) ? paneName : fallbackPaneName;
1773
+ }
1774
+ function createPointDivIcon(style, isMobile) {
1775
+ const size = isMobile ? 18 : 14;
1776
+ const backgroundColor = style.fillColor ?? style.color ?? "#3388ff";
1777
+ const borderColor = style.color ?? style.fillColor ?? "#3388ff";
1778
+ return import_leaflet.default.divIcon({
1779
+ className: "zenit-point-marker",
1780
+ html: `<div style="width:${size}px;height:${size}px;border-radius:50%;background:${backgroundColor};border:2px solid ${borderColor};"></div>`,
1781
+ iconSize: [size, size],
1782
+ iconAnchor: [size / 2, size / 2]
1783
+ });
1784
+ }
1632
1785
  function buildFeatureCollection(features) {
1633
1786
  return {
1634
1787
  type: "FeatureCollection",
@@ -1649,14 +1802,15 @@ var LayerGeoJson = ({
1649
1802
  onEachFeature,
1650
1803
  onPolygonLabel
1651
1804
  }) => {
1652
- const features = data.features ?? [];
1653
- const fillFeatures = features.filter(isNonPointGeometry);
1654
- const pointFeatures = features.filter(isPointGeometry);
1805
+ const safeData = (0, import_react.useMemo)(() => sanitizeGeoJson(data, String(layerId)), [data, layerId]);
1806
+ const features = (0, import_react.useMemo)(() => safeData.features ?? [], [safeData]);
1807
+ const fillFeatures = (0, import_react.useMemo)(() => features.filter(isNonPointGeometry), [features]);
1808
+ const pointFeatures = (0, import_react.useMemo)(() => features.filter(isPointGeometry), [features]);
1655
1809
  const dataVersionRef = (0, import_react.useRef)(0);
1656
1810
  const prevSignatureRef = (0, import_react.useRef)("");
1657
1811
  const firstId = features.length > 0 ? String(features[0]?.id ?? "") : "";
1658
1812
  const lastId = features.length > 0 ? String(features[features.length - 1]?.id ?? "") : "";
1659
- const bbox = normalizeBboxFromData(data);
1813
+ const bbox = normalizeBboxFromData(safeData);
1660
1814
  const idsSample = buildIdsSample(features);
1661
1815
  const signature = bbox ? `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}|${bbox[0]}|${bbox[1]}|${bbox[2]}|${bbox[3]}` : `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}`;
1662
1816
  const signatureToken = signature.replace(/[^a-zA-Z0-9_-]/g, "_");
@@ -1664,25 +1818,40 @@ var LayerGeoJson = ({
1664
1818
  dataVersionRef.current += 1;
1665
1819
  prevSignatureRef.current = signature;
1666
1820
  }
1667
- const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
1668
- const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
1821
+ const fillData = (0, import_react.useMemo)(() => fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null, [fillFeatures]);
1822
+ const pointsData = (0, import_react.useMemo)(() => pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null, [pointFeatures]);
1669
1823
  const clusterLayerRef = (0, import_react.useRef)(null);
1670
1824
  const canCluster = typeof import_leaflet.default.markerClusterGroup === "function";
1825
+ const resolvedFillPane = (0, import_react.useMemo)(
1826
+ () => ensurePaneName(mapInstance, fillPaneName, "overlayPane"),
1827
+ [fillPaneName, mapInstance]
1828
+ );
1829
+ const resolvedPointsPane = (0, import_react.useMemo)(
1830
+ () => ensurePaneName(mapInstance, pointsPaneName, "markerPane"),
1831
+ [mapInstance, pointsPaneName]
1832
+ );
1671
1833
  (0, import_react.useEffect)(() => {
1672
1834
  if (!mapInstance || !panesReady || !pointsData || !canCluster) return;
1673
1835
  const markerClusterGroup = import_leaflet.default.markerClusterGroup;
1674
- const clusterLayer = clusterLayerRef.current ?? markerClusterGroup();
1836
+ const clusterPaneName = resolvedPointsPane;
1837
+ const clusterLayer = clusterLayerRef.current ?? markerClusterGroup({ pane: clusterPaneName, clusterPane: clusterPaneName });
1675
1838
  clusterLayerRef.current = clusterLayer;
1676
1839
  if (!mapInstance.hasLayer(clusterLayer)) {
1677
1840
  mapInstance.addLayer(clusterLayer);
1678
1841
  }
1679
1842
  clusterLayer.clearLayers();
1680
1843
  const geoJsonLayer = import_leaflet.default.geoJSON(pointsData, {
1681
- pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
1682
- radius: isMobile ? 8 : 6,
1683
- pane: mapInstance.getPane(pointsPaneName) ? pointsPaneName : void 0,
1684
- ...styleFn(feature, layerType, baseOpacity)
1685
- }),
1844
+ pointToLayer: (feature, latlng) => {
1845
+ if (!isValidLatLng(latlng)) {
1846
+ return createInvisibleFallbackClusterMarker();
1847
+ }
1848
+ const style = styleFn(feature, layerType, baseOpacity);
1849
+ return import_leaflet.default.marker(latlng, {
1850
+ icon: createPointDivIcon(style, isMobile),
1851
+ pane: clusterPaneName,
1852
+ interactive: true
1853
+ });
1854
+ },
1686
1855
  onEachFeature
1687
1856
  });
1688
1857
  clusterLayer.addLayer(geoJsonLayer);
@@ -1701,7 +1870,7 @@ var LayerGeoJson = ({
1701
1870
  onEachFeature,
1702
1871
  panesReady,
1703
1872
  pointsData,
1704
- pointsPaneName,
1873
+ resolvedPointsPane,
1705
1874
  styleFn
1706
1875
  ]);
1707
1876
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -1709,7 +1878,7 @@ var LayerGeoJson = ({
1709
1878
  import_react_leaflet.GeoJSON,
1710
1879
  {
1711
1880
  data: fillData,
1712
- pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
1881
+ pane: resolvedFillPane,
1713
1882
  style: (feature) => styleFn(feature, layerType, baseOpacity),
1714
1883
  onEachFeature: (feature, layer) => {
1715
1884
  onEachFeature(feature, layer);
@@ -1722,11 +1891,16 @@ var LayerGeoJson = ({
1722
1891
  import_react_leaflet.GeoJSON,
1723
1892
  {
1724
1893
  data: pointsData,
1725
- pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
1726
- pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
1727
- radius: isMobile ? 8 : 6,
1728
- ...styleFn(feature, layerType, baseOpacity)
1729
- }),
1894
+ pane: resolvedPointsPane,
1895
+ pointToLayer: (feature, latlng) => {
1896
+ if (!isValidLatLng(latlng)) {
1897
+ return createInvisibleFallbackMarker();
1898
+ }
1899
+ return import_leaflet.default.circleMarker(latlng, {
1900
+ radius: isMobile ? 8 : 6,
1901
+ ...styleFn(feature, layerType, baseOpacity)
1902
+ });
1903
+ },
1730
1904
  onEachFeature
1731
1905
  },
1732
1906
  `points-${layerId}-${signatureToken}-v${dataVersionRef.current}`
@@ -1808,7 +1982,22 @@ function useGeolocation(options) {
1808
1982
  var import_leaflet2 = __toESM(require("leaflet"));
1809
1983
  var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
1810
1984
  var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry", "_private"]);
1811
- var POPUP_HEADER_KEYS = ["nombre", "name", "title", "titulo"];
1985
+ var POPUP_TITLE_KEYS = ["id", "nombre", "name", "title", "titulo", "cluster"];
1986
+ var POPUP_BADGE_KEYS = ["tipo", "type", "category", "categoria", "estado", "status"];
1987
+ var POPUP_DESCRIPTION_KEYS = ["descripcion", "description", "desc"];
1988
+ var CURRENCY_KEYWORDS = [
1989
+ "capital",
1990
+ "monto",
1991
+ "precio",
1992
+ "costo",
1993
+ "valor",
1994
+ "ingreso",
1995
+ "egreso",
1996
+ "saldo",
1997
+ "subtotal",
1998
+ "venta",
1999
+ "compra"
2000
+ ];
1812
2001
  var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 360, minWidth: 280, maxHeight: 520 };
1813
2002
  var MOBILE_POPUP_DIMENSIONS = { maxWidth: 300, minWidth: 240, maxHeight: 420 };
1814
2003
  var ZENIT_LEAFLET_POPUP_STYLES = `
@@ -1926,6 +2115,68 @@ function escapeHtml(value) {
1926
2115
  function formatLabel(key) {
1927
2116
  return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
1928
2117
  }
2118
+ function isHexColor(value) {
2119
+ return /^#([0-9A-F]{3}){1,2}$/i.test(value.trim());
2120
+ }
2121
+ function isCurrencyField(key) {
2122
+ const normalized = key.trim().toLowerCase();
2123
+ return CURRENCY_KEYWORDS.some((keyword) => normalized.includes(keyword));
2124
+ }
2125
+ function formatCurrency(value) {
2126
+ return new Intl.NumberFormat("es-GT", { style: "currency", currency: "GTQ" }).format(value);
2127
+ }
2128
+ function getBadgeForProperty(key, value) {
2129
+ if (value === null || value === void 0) return null;
2130
+ const normalizedKey = key.trim().toLowerCase();
2131
+ const normalizedValue = String(value).trim().toLowerCase();
2132
+ const byType = {
2133
+ mora: { label: "Mora", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" },
2134
+ castigo: { label: "Castigo", bg: "#ffedd5", color: "#c2410c", border: "#fed7aa" },
2135
+ colocacion: { label: "Colocaci\xF3n", bg: "#dbeafe", color: "#1d4ed8", border: "#bfdbfe" },
2136
+ sano: { label: "Sano", bg: "#dcfce7", color: "#15803d", border: "#bbf7d0" }
2137
+ };
2138
+ const byStatus = {
2139
+ activo: { label: "Activo", bg: "#dcfce7", color: "#15803d", border: "#bbf7d0" },
2140
+ inactivo: { label: "Inactivo", bg: "#fef9c3", color: "#a16207", border: "#fde68a" },
2141
+ pendiente: { label: "Pendiente", bg: "#fef9c3", color: "#a16207", border: "#fde68a" },
2142
+ cancelado: { label: "Cancelado", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" },
2143
+ rechazado: { label: "Rechazado", bg: "#fee2e2", color: "#b91c1c", border: "#fecaca" }
2144
+ };
2145
+ if (["tipo", "type", "category", "categoria"].includes(normalizedKey)) {
2146
+ return byType[normalizedValue] ?? {
2147
+ label: String(value),
2148
+ bg: "#e0e7ff",
2149
+ color: "#4338ca",
2150
+ border: "#c7d2fe"
2151
+ };
2152
+ }
2153
+ if (["estado", "status"].includes(normalizedKey)) {
2154
+ return byStatus[normalizedValue] ?? {
2155
+ label: String(value),
2156
+ bg: "#f1f5f9",
2157
+ color: "#475569",
2158
+ border: "#cbd5e1"
2159
+ };
2160
+ }
2161
+ return null;
2162
+ }
2163
+ function findHeaderProperties(properties) {
2164
+ const entries = Object.entries(properties);
2165
+ const titleEntry = entries.find(
2166
+ ([key, value]) => POPUP_TITLE_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
2167
+ );
2168
+ const badgeEntry = entries.find(
2169
+ ([key, value]) => POPUP_BADGE_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
2170
+ );
2171
+ const descriptionEntry = entries.find(
2172
+ ([key, value]) => POPUP_DESCRIPTION_KEYS.includes(key.trim().toLowerCase()) && String(value ?? "").trim().length > 0
2173
+ );
2174
+ return {
2175
+ title: titleEntry ? { key: titleEntry[0], value: String(titleEntry[1]).trim() } : void 0,
2176
+ badge: badgeEntry ? { key: badgeEntry[0], value: badgeEntry[1] } : void 0,
2177
+ description: descriptionEntry ? { key: descriptionEntry[0], value: String(descriptionEntry[1]).trim() } : void 0
2178
+ };
2179
+ }
1929
2180
  function renderPopupValue(value) {
1930
2181
  if (value === null || value === void 0) return '<span style="color:#94a3b8;">Sin datos</span>';
1931
2182
  if (value instanceof Date) {
@@ -1951,12 +2202,8 @@ function renderPopupValue(value) {
1951
2202
  return escapeHtml(String(value));
1952
2203
  }
1953
2204
  function extractPopupHeader(properties) {
1954
- const entry = Object.entries(properties).find(([key, value]) => {
1955
- if (typeof value !== "string") return false;
1956
- const normalized = key.trim().toLowerCase();
1957
- return POPUP_HEADER_KEYS.includes(normalized) && value.trim().length > 0;
1958
- });
1959
- return entry ? entry[1].trim() : null;
2205
+ const header = findHeaderProperties(properties);
2206
+ return header.title?.value ?? null;
1960
2207
  }
1961
2208
  function shouldIncludePopupEntry(key, value) {
1962
2209
  if (!key) return false;
@@ -1969,21 +2216,36 @@ function shouldIncludePopupEntry(key, value) {
1969
2216
  return true;
1970
2217
  }
1971
2218
  function createPopupContent(properties) {
1972
- const headerText = extractPopupHeader(properties);
2219
+ const header = findHeaderProperties(properties);
2220
+ const headerText = header.title?.value ?? extractPopupHeader(properties);
2221
+ const usedKeys = new Set([header.title?.key, header.badge?.key, header.description?.key].filter(Boolean));
2222
+ const badge = header.badge ? getBadgeForProperty(header.badge.key, header.badge.value) : null;
2223
+ const colorValue = properties.color;
2224
+ 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>` : "";
1973
2225
  const entries = Object.entries(properties).filter(([key, value]) => {
1974
2226
  if (!shouldIncludePopupEntry(key, value)) return false;
1975
- if (headerText && POPUP_HEADER_KEYS.includes(key.trim().toLowerCase())) return false;
2227
+ if (usedKeys.has(key)) return false;
1976
2228
  return true;
1977
2229
  });
1978
2230
  if (entries.length === 0) {
1979
2231
  return '<div style="padding:8px 0; color:#64748b; text-align:center;">Sin datos disponibles</div>';
1980
2232
  }
1981
- const headerHtml = headerText ? `<div style="font-weight:700; font-size:14px; margin-bottom:8px; color:#0f172a;">${escapeHtml(
1982
- headerText
2233
+ const headerHtml = headerText || badge ? `<div style="display:flex; justify-content:space-between; gap:8px; align-items:flex-start; margin-bottom:8px;">
2234
+ <div style="font-weight:700; font-size:14px; color:#0f172a;">${escapeHtml(headerText ?? "Detalle")}</div>
2235
+ ${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(
2236
+ badge.label
2237
+ )}</span>` : ""}
2238
+ </div>` : "";
2239
+ 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(
2240
+ header.description.value
1983
2241
  )}</div>` : "";
1984
2242
  const rowsHtml = entries.map(([key, value]) => {
1985
2243
  const label = escapeHtml(formatLabel(key));
1986
- const valueHtml = renderPopupValue(value);
2244
+ const normalizedKey = key.trim().toLowerCase();
2245
+ let valueHtml = renderPopupValue(value);
2246
+ if (typeof value === "number") {
2247
+ 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>`;
2248
+ }
1987
2249
  return `
1988
2250
  <div style="display:grid; grid-template-columns:minmax(90px, 35%) 1fr; gap:8px; padding:6px 0; border-bottom:1px solid #e2e8f0;">
1989
2251
  <div style="font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:0.04em; color:#64748b;">${label}</div>
@@ -1991,7 +2253,7 @@ function createPopupContent(properties) {
1991
2253
  </div>
1992
2254
  `;
1993
2255
  }).join("");
1994
- return `<div>${headerHtml}${rowsHtml}</div>`;
2256
+ return `<div>${colorBar}${headerHtml}${descriptionHtml}${rowsHtml}</div>`;
1995
2257
  }
1996
2258
  function isPolygonType(layerType, geometryType) {
1997
2259
  const candidate = (layerType ?? geometryType ?? "").toLowerCase();
@@ -2001,21 +2263,27 @@ function calculateZoomBasedOpacity(zoom, baseOpacity, layerType, geometryType) {
2001
2263
  if (!isPolygonType(layerType, geometryType)) return clampOpacity4(baseOpacity);
2002
2264
  const minZoom = 11;
2003
2265
  const maxZoom = 15;
2004
- const minFactor = 0.3;
2266
+ const minFactor = 0.5;
2005
2267
  if (maxZoom <= minZoom) return clampOpacity4(baseOpacity * minFactor);
2006
2268
  const t = clampNumber2((zoom - minZoom) / (maxZoom - minZoom), 0, 1);
2007
2269
  const factor = 1 - (1 - minFactor) * t;
2008
2270
  return clampOpacity4(baseOpacity * factor);
2009
2271
  }
2010
2272
  function layerStyleToLeaflet(options) {
2011
- const { baseOpacity, zoom, layerStyle, geometryType, layerType } = options;
2273
+ const { baseOpacity, zoom, layerStyle, geometryType, layerType, properties } = options;
2012
2274
  const sanitizedOpacity = clampOpacity4(baseOpacity);
2013
2275
  const zoomOpacity = calculateZoomBasedOpacity(zoom, sanitizedOpacity, layerType, geometryType);
2014
- const styleFillOpacity = typeof layerStyle?.fillOpacity === "number" ? clampOpacity4(layerStyle.fillOpacity) : 0.8;
2276
+ const badgeForType = getBadgeForProperty("type", properties?.type ?? properties?.tipo ?? properties?.category ?? properties?.categoria);
2277
+ const badgeForStatus = getBadgeForProperty("status", properties?.status ?? properties?.estado);
2278
+ const featureColor = (typeof properties?.color === "string" && properties.color.trim().length > 0 ? properties.color : void 0) ?? badgeForType?.color ?? badgeForStatus?.color;
2279
+ const styleFillOpacity = typeof layerStyle?.fillOpacity === "number" ? clampOpacity4(layerStyle.fillOpacity) : 0.65;
2280
+ const fallbackColor = "#2563eb";
2281
+ const resolvedColor = featureColor ?? layerStyle?.color ?? layerStyle?.fillColor ?? fallbackColor;
2282
+ const resolvedFillColor = featureColor ?? layerStyle?.fillColor ?? layerStyle?.color ?? fallbackColor;
2015
2283
  return {
2016
- color: layerStyle?.color ?? layerStyle?.fillColor ?? "#2563eb",
2284
+ color: resolvedColor,
2017
2285
  weight: layerStyle?.weight ?? 2,
2018
- fillColor: layerStyle?.fillColor ?? layerStyle?.color ?? "#2563eb",
2286
+ fillColor: resolvedFillColor,
2019
2287
  opacity: clampOpacity4(Math.max(0.35, zoomOpacity * 0.9)),
2020
2288
  fillOpacity: clampOpacity4(zoomOpacity * styleFillOpacity)
2021
2289
  };
@@ -2413,6 +2681,7 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
2413
2681
  var DEFAULT_CENTER = [0, 0];
2414
2682
  var DEFAULT_ZOOM = 3;
2415
2683
  var LABELS_PANE_NAME = "zenit-labels-pane";
2684
+ var DEV_MODE2 = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
2416
2685
  function isRecord(value) {
2417
2686
  return typeof value === "object" && value !== null;
2418
2687
  }
@@ -2436,6 +2705,8 @@ function getFeatureLayerId(feature) {
2436
2705
  return layerId;
2437
2706
  }
2438
2707
  var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
2708
+ var POINT_GEOMETRY_TYPES2 = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
2709
+ var POLYGON_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Polygon", "MultiPolygon"]);
2439
2710
  function normalizeDescriptionValue(value) {
2440
2711
  if (value === void 0 || value === null) return null;
2441
2712
  if (typeof value === "string") {
@@ -2455,6 +2726,31 @@ function extractDescriptionValue(properties) {
2455
2726
  if (!matches) return null;
2456
2727
  return normalizeDescriptionValue(matches[1]);
2457
2728
  }
2729
+ function normalizeLayerId(value) {
2730
+ if (value === null || value === void 0) return null;
2731
+ const normalized = String(value).trim();
2732
+ return normalized ? normalized : null;
2733
+ }
2734
+ function isLayerIdMatch(a, b) {
2735
+ const normalizedA = normalizeLayerId(a);
2736
+ const normalizedB = normalizeLayerId(b);
2737
+ return normalizedA !== null && normalizedB !== null && normalizedA === normalizedB;
2738
+ }
2739
+ function getClickIntent(params) {
2740
+ const geometryType = params.feature?.geometry?.type;
2741
+ if (geometryType && POINT_GEOMETRY_TYPES2.has(geometryType)) return "point";
2742
+ if (params.leafletLayer instanceof import_leaflet4.default.Marker) return "point";
2743
+ if (geometryType && POLYGON_GEOMETRY_TYPES.has(geometryType)) return "polygon";
2744
+ if (params.leafletLayer instanceof import_leaflet4.default.Path) return "polygon";
2745
+ return "unknown";
2746
+ }
2747
+ function candidateLayerId(candidate) {
2748
+ return getFeatureLayerId(candidate);
2749
+ }
2750
+ function isCandidateGeometryType(candidate, allowedTypes) {
2751
+ const geometryType = candidate?.geometry?.type;
2752
+ return Boolean(geometryType && allowedTypes.has(geometryType));
2753
+ }
2458
2754
  function getFeatureStyleOverrides(feature) {
2459
2755
  const candidate = feature?.properties?._style;
2460
2756
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return null;
@@ -2472,17 +2768,50 @@ function buildFeaturePopupHtml(feature) {
2472
2768
  const rendered = createPopupContent(properties);
2473
2769
  return rendered ? rendered : null;
2474
2770
  }
2475
- function pickIntersectFeature(baseFeature, candidates) {
2771
+ function pickIntersectFeature(params) {
2772
+ const { baseFeature, candidates, expectedLayerId, clickIntent } = params;
2476
2773
  if (!Array.isArray(candidates) || candidates.length === 0) return null;
2774
+ const pickByGeometry = (pool, intent) => {
2775
+ const preferredTypes = intent === "point" ? POINT_GEOMETRY_TYPES2 : intent === "polygon" ? POLYGON_GEOMETRY_TYPES : null;
2776
+ if (!preferredTypes) return null;
2777
+ const found = pool.find((candidate) => isCandidateGeometryType(candidate, preferredTypes));
2778
+ if (!found) return null;
2779
+ return { feature: found, reason: `geometry:${intent}` };
2780
+ };
2781
+ const getResult = (feature, reason) => ({
2782
+ feature,
2783
+ selectedIdx: candidates.findIndex((candidate) => candidate === feature),
2784
+ reason
2785
+ });
2786
+ const sameLayer = candidates.filter(
2787
+ (candidate) => isLayerIdMatch(candidateLayerId(candidate), expectedLayerId)
2788
+ );
2789
+ const geometryFromSameLayer = pickByGeometry(sameLayer, clickIntent);
2790
+ if (geometryFromSameLayer?.feature) {
2791
+ return getResult(geometryFromSameLayer.feature, `sameLayer>${geometryFromSameLayer.reason}`);
2792
+ }
2793
+ if (sameLayer.length > 0) {
2794
+ const sameLayerWithDescription = sameLayer.find(
2795
+ (candidate) => extractDescriptionValue(candidate?.properties)
2796
+ );
2797
+ if (sameLayerWithDescription) {
2798
+ return getResult(sameLayerWithDescription, "sameLayer>description");
2799
+ }
2800
+ }
2801
+ const geometryFallback = pickByGeometry(candidates, clickIntent);
2802
+ if (geometryFallback?.feature) {
2803
+ return getResult(geometryFallback.feature, `anyLayer>${geometryFallback.reason}`);
2804
+ }
2477
2805
  const baseId = baseFeature?.id;
2478
2806
  if (baseId !== void 0 && baseId !== null) {
2479
2807
  const matchById = candidates.find((candidate) => candidate?.id === baseId);
2480
- if (matchById) return matchById;
2808
+ if (matchById) return getResult(matchById, "fallback>baseId");
2481
2809
  }
2482
2810
  const matchWithDescription = candidates.find(
2483
2811
  (candidate) => extractDescriptionValue(candidate?.properties)
2484
2812
  );
2485
- return matchWithDescription ?? candidates[0];
2813
+ if (matchWithDescription) return getResult(matchWithDescription, "fallback>description");
2814
+ return getResult(candidates[0], "fallback>first");
2486
2815
  }
2487
2816
  function normalizeCenterTuple(center) {
2488
2817
  if (!center) return null;
@@ -2889,7 +3218,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2889
3218
  }, [currentZoom, decoratedLayers, labelKeyIndex, layerMetaIndex, resolveLayerStyle]);
2890
3219
  const ensureLayerPanes = (0, import_react5.useCallback)(
2891
3220
  (targetMap, targetLayers) => {
2892
- const baseZIndex = 400;
3221
+ const fillBaseZIndex = 400;
3222
+ const pointsBaseZIndex = 700;
2893
3223
  targetLayers.forEach((layer) => {
2894
3224
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
2895
3225
  const orderOffset = Math.max(0, Math.min(order, 150));
@@ -2897,8 +3227,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2897
3227
  const pointPaneName = `zenit-layer-${layer.layerId}-points`;
2898
3228
  const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
2899
3229
  const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
2900
- fillPane.style.zIndex = String(baseZIndex + orderOffset);
2901
- pointPane.style.zIndex = String(baseZIndex + orderOffset + 100);
3230
+ fillPane.style.zIndex = String(fillBaseZIndex + orderOffset);
3231
+ pointPane.style.zIndex = String(pointsBaseZIndex + orderOffset);
2902
3232
  });
2903
3233
  },
2904
3234
  []
@@ -2916,114 +3246,149 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2916
3246
  setPanesReady(false);
2917
3247
  return;
2918
3248
  }
2919
- if (orderedLayers.length === 0) {
3249
+ if (orderedLayers.length === 0 && !overlayGeojson) {
2920
3250
  return;
2921
3251
  }
2922
3252
  const layerTargets = orderedLayers.map((layer) => ({
2923
3253
  layerId: layer.mapLayer.layerId,
2924
3254
  displayOrder: layer.displayOrder
2925
3255
  }));
3256
+ if (overlayGeojson) {
3257
+ layerTargets.push({ layerId: "overlay-geojson", displayOrder: 999 });
3258
+ }
2926
3259
  ensureLayerPanes(mapInstance, layerTargets);
2927
3260
  const first = layerTargets[0];
2928
- const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-fill`);
3261
+ const testPane = first ? mapInstance.getPane(`zenit-layer-${first.layerId}-fill`) : null;
2929
3262
  const labelsPane = mapInstance.getPane(LABELS_PANE_NAME);
2930
3263
  if (testPane && labelsPane) {
2931
3264
  setPanesReady(true);
2932
3265
  }
2933
- }, [mapInstance, orderedLayers, ensureLayerPanes]);
2934
- const overlayOnEachFeature = (0, import_react5.useMemo)(() => {
2935
- return (feature, layer) => {
2936
- const layerId = getFeatureLayerId(feature) ?? void 0;
2937
- const geometryType = feature?.geometry?.type;
2938
- const isPointFeature = geometryType === "Point" || geometryType === "MultiPoint" || layer instanceof import_leaflet4.default.CircleMarker;
2939
- const originalStyle = layer instanceof import_leaflet4.default.Path ? {
2940
- color: layer.options.color,
2941
- weight: layer.options.weight,
2942
- fillColor: layer.options.fillColor,
2943
- opacity: layer.options.opacity,
2944
- fillOpacity: layer.options.fillOpacity
2945
- } : null;
2946
- const originalRadius = layer instanceof import_leaflet4.default.CircleMarker ? layer.getRadius() : null;
2947
- if (featureInfoMode === "popup") {
2948
- const content = buildFeaturePopupHtml(feature);
2949
- if (content) {
2950
- const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
2951
- layer.bindPopup(content, {
2952
- maxWidth,
2953
- minWidth,
2954
- maxHeight,
2955
- className: "custom-leaflet-popup",
2956
- autoPan: true,
2957
- closeButton: true,
2958
- keepInView: true
2959
- });
2960
- }
2961
- }
2962
- if (isPointFeature && layer.bindTooltip) {
2963
- layer.bindTooltip("Click para ver detalle", {
2964
- sticky: true,
2965
- direction: "top",
2966
- opacity: 0.9,
2967
- className: "zenit-map-tooltip"
3266
+ }, [mapInstance, orderedLayers, overlayGeojson, ensureLayerPanes]);
3267
+ const overlayOnEachFeature = (0, import_react5.useCallback)((feature, layer) => {
3268
+ const layerId = getFeatureLayerId(feature) ?? void 0;
3269
+ const geometryType = feature?.geometry?.type;
3270
+ const clickIntent = getClickIntent({ feature, leafletLayer: layer });
3271
+ const isPointFeature = clickIntent === "point";
3272
+ const originalStyle = layer instanceof import_leaflet4.default.Path ? {
3273
+ color: layer.options.color,
3274
+ weight: layer.options.weight,
3275
+ fillColor: layer.options.fillColor,
3276
+ opacity: layer.options.opacity,
3277
+ fillOpacity: layer.options.fillOpacity
3278
+ } : null;
3279
+ const originalRadius = layer instanceof import_leaflet4.default.CircleMarker ? layer.getRadius() : null;
3280
+ if (featureInfoMode === "popup") {
3281
+ const content = buildFeaturePopupHtml(feature);
3282
+ if (content) {
3283
+ const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
3284
+ layer.bindPopup(content, {
3285
+ maxWidth,
3286
+ minWidth,
3287
+ maxHeight,
3288
+ className: "custom-leaflet-popup",
3289
+ autoClose: true,
3290
+ closeOnClick: true,
3291
+ autoPan: true,
3292
+ closeButton: true,
3293
+ keepInView: true,
3294
+ autoPanPadding: [50, 50]
2968
3295
  });
2969
3296
  }
2970
- layer.on("click", () => {
2971
- if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
2972
- const trackedFeature = feature;
2973
- if (!trackedFeature.__zenit_popup_loaded) {
2974
- trackedFeature.__zenit_popup_loaded = true;
2975
- client.layers.getLayerGeoJsonIntersect({
2976
- id: layerId,
2977
- geometry: feature.geometry
2978
- }).then((response) => {
2979
- const geo = extractGeoJsonFeatureCollection(response);
2980
- const candidates = geo?.features ?? [];
2981
- const resolved = pickIntersectFeature(feature, candidates);
2982
- if (!resolved?.properties) return;
2983
- const mergedProperties = {
2984
- ...trackedFeature.properties ?? {},
2985
- ...resolved.properties
2986
- };
2987
- trackedFeature.properties = mergedProperties;
2988
- const updatedHtml = buildFeaturePopupHtml({
2989
- ...feature,
2990
- properties: mergedProperties
2991
- });
2992
- if (updatedHtml && layer.setPopupContent) {
2993
- layer.setPopupContent(updatedHtml);
2994
- }
2995
- }).catch(() => {
2996
- trackedFeature.__zenit_popup_loaded = false;
2997
- });
2998
- }
2999
- }
3000
- onFeatureClick?.(feature, layerId);
3297
+ }
3298
+ if (isPointFeature && layer.bindTooltip) {
3299
+ layer.bindTooltip("Click para ver detalle", {
3300
+ sticky: true,
3301
+ direction: "top",
3302
+ opacity: 0.9,
3303
+ className: "zenit-map-tooltip"
3001
3304
  });
3002
- layer.on("mouseover", () => {
3003
- if (layer instanceof import_leaflet4.default.Path && originalStyle) {
3004
- layer.setStyle({
3005
- ...originalStyle,
3006
- weight: (originalStyle.weight ?? 2) + 1,
3007
- opacity: Math.min(1, (originalStyle.opacity ?? 1) + 0.2),
3008
- fillOpacity: Math.min(1, (originalStyle.fillOpacity ?? 0.8) + 0.1)
3305
+ }
3306
+ layer.on("click", () => {
3307
+ if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
3308
+ if (DEV_MODE2) {
3309
+ console.debug("[ZenitMap] click/intersect:start", {
3310
+ expectedLayerId: layerId,
3311
+ geometryType,
3312
+ clickIntent,
3313
+ leafletLayerType: layer?.constructor?.name,
3314
+ pane: layer?.options?.pane
3009
3315
  });
3010
3316
  }
3011
- if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
3012
- layer.setRadius(originalRadius + 1);
3013
- }
3014
- onFeatureHover?.(feature, layerId);
3015
- });
3016
- layer.on("mouseout", () => {
3017
- if (layer instanceof import_leaflet4.default.Path && originalStyle) {
3018
- layer.setStyle(originalStyle);
3019
- }
3020
- if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
3021
- layer.setRadius(originalRadius);
3317
+ const trackedFeature = feature;
3318
+ if (!trackedFeature.__zenit_popup_loaded) {
3319
+ trackedFeature.__zenit_popup_loaded = true;
3320
+ client.layers.getLayerGeoJsonIntersect({
3321
+ id: layerId,
3322
+ geometry: feature.geometry
3323
+ }).then((response) => {
3324
+ const geo = extractGeoJsonFeatureCollection(response);
3325
+ const candidates = geo?.features ?? [];
3326
+ const selection = pickIntersectFeature({
3327
+ baseFeature: feature,
3328
+ candidates,
3329
+ expectedLayerId: layerId,
3330
+ clickIntent
3331
+ });
3332
+ const resolved = selection?.feature;
3333
+ if (DEV_MODE2) {
3334
+ console.debug("[ZenitMap] click/intersect:result", {
3335
+ expectedLayerId: layerId,
3336
+ clickIntent,
3337
+ candidatesCount: candidates.length,
3338
+ candidates: candidates.map((candidate, idx) => ({
3339
+ idx,
3340
+ geomType: candidate?.geometry?.type,
3341
+ candidateLayerId: candidateLayerId(candidate),
3342
+ hasDescription: Boolean(extractDescriptionValue(candidate?.properties))
3343
+ })),
3344
+ selectedIdx: selection?.selectedIdx ?? -1,
3345
+ selectionRule: selection?.reason ?? "none"
3346
+ });
3347
+ }
3348
+ if (!resolved?.properties) return;
3349
+ const mergedProperties = {
3350
+ ...trackedFeature.properties ?? {},
3351
+ ...resolved.properties
3352
+ };
3353
+ trackedFeature.properties = mergedProperties;
3354
+ const updatedHtml = buildFeaturePopupHtml({
3355
+ ...feature,
3356
+ properties: mergedProperties
3357
+ });
3358
+ if (updatedHtml && layer.setPopupContent) {
3359
+ layer.setPopupContent(updatedHtml);
3360
+ }
3361
+ }).catch(() => {
3362
+ trackedFeature.__zenit_popup_loaded = false;
3363
+ });
3022
3364
  }
3023
- });
3024
- };
3365
+ }
3366
+ onFeatureClick?.(feature, layerId);
3367
+ });
3368
+ layer.on("mouseover", () => {
3369
+ if (layer instanceof import_leaflet4.default.Path && originalStyle) {
3370
+ layer.setStyle({
3371
+ ...originalStyle,
3372
+ weight: (originalStyle.weight ?? 2) + 1,
3373
+ opacity: Math.min(1, (originalStyle.opacity ?? 1) + 0.2),
3374
+ fillOpacity: Math.min(1, (originalStyle.fillOpacity ?? 0.8) + 0.1)
3375
+ });
3376
+ }
3377
+ if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
3378
+ layer.setRadius(originalRadius + 1);
3379
+ }
3380
+ onFeatureHover?.(feature, layerId);
3381
+ });
3382
+ layer.on("mouseout", () => {
3383
+ if (layer instanceof import_leaflet4.default.Path && originalStyle) {
3384
+ layer.setStyle(originalStyle);
3385
+ }
3386
+ if (layer instanceof import_leaflet4.default.CircleMarker && typeof originalRadius === "number") {
3387
+ layer.setRadius(originalRadius);
3388
+ }
3389
+ });
3025
3390
  }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
3026
- const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
3391
+ const buildLayerStyle = (0, import_react5.useCallback)((layerId, baseOpacity, feature, layerType) => {
3027
3392
  const style = resolveLayerStyle(layerId);
3028
3393
  const featureStyleOverrides = getFeatureStyleOverrides(feature);
3029
3394
  const resolvedStyle = featureStyleOverrides ? { ...style ?? {}, ...featureStyleOverrides } : style;
@@ -3034,14 +3399,15 @@ var ZenitMap = (0, import_react5.forwardRef)(({
3034
3399
  zoom: currentZoom,
3035
3400
  layerStyle: resolvedStyle,
3036
3401
  geometryType,
3037
- layerType: resolvedLayerType
3402
+ layerType: resolvedLayerType,
3403
+ properties: feature?.properties ?? void 0
3038
3404
  });
3039
- };
3040
- const makeStyleFnForLayer = (layerId) => {
3405
+ }, [currentZoom, resolveLayerStyle]);
3406
+ const makeStyleFnForLayer = (0, import_react5.useCallback)((layerId) => {
3041
3407
  return (feature, layerType, baseOpacity) => {
3042
3408
  return buildLayerStyle(layerId, baseOpacity ?? 1, feature, layerType);
3043
3409
  };
3044
- };
3410
+ }, [buildLayerStyle]);
3045
3411
  (0, import_react5.useImperativeHandle)(ref, () => ({
3046
3412
  setLayerOpacity: (layerId, opacity) => {
3047
3413
  upsertUiOverride(layerId, { overrideOpacity: opacity });
@@ -3196,7 +3562,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
3196
3562
  const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
3197
3563
  const labelKey = labelKeyIndex.get(String(layerState.mapLayer.layerId));
3198
3564
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react5.default.Fragment, { children: [
3199
- layerState.data && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3565
+ layerState.data && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3200
3566
  LayerGeoJson,
3201
3567
  {
3202
3568
  layerId: layerState.mapLayer.layerId,
@@ -3239,7 +3605,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
3239
3605
  )) : null
3240
3606
  ] }, layerState.mapLayer.layerId.toString());
3241
3607
  }),
3242
- overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3608
+ overlayGeojson && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3243
3609
  LayerGeoJson,
3244
3610
  {
3245
3611
  layerId: "overlay-geojson",
@@ -3273,7 +3639,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
3273
3639
  overflowY: "auto"
3274
3640
  },
3275
3641
  children: [
3276
- overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3642
+ overlayGeojson && panesReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3277
3643
  "div",
3278
3644
  {
3279
3645
  style: {
@@ -3961,6 +4327,14 @@ var ZenitLayerManager = ({
3961
4327
  return String(a.mapLayer.layerId).localeCompare(String(b.mapLayer.layerId));
3962
4328
  });
3963
4329
  }, [effectiveStates, layers, resolveFeatureCount]);
4330
+ const hasPrefilters = (0, import_react7.useMemo)(() => {
4331
+ const candidates = [...mapLayers ?? [], ...map?.mapLayers ?? []];
4332
+ return candidates.some((layer) => {
4333
+ const record = layer;
4334
+ const applied = record.appliedFilters ?? record.prefilters ?? record.initialFilters ?? record.filters ?? (record.layerConfig?.appliedFilters ?? record.layerConfig?.prefilters);
4335
+ return !!applied && typeof applied === "object" && Object.keys(applied).length > 0;
4336
+ });
4337
+ }, [map?.mapLayers, mapLayers]);
3964
4338
  const filterableLayers = (0, import_react7.useMemo)(() => {
3965
4339
  return decoratedLayers.filter((entry) => {
3966
4340
  const prefilters = entry.mapLayer.layerConfig?.prefilters;
@@ -4033,6 +4407,11 @@ var ZenitLayerManager = ({
4033
4407
  setSelectedFilterValue("");
4034
4408
  }
4035
4409
  }, [filterFields, selectedFilterField]);
4410
+ (0, import_react7.useEffect)(() => {
4411
+ if (hasPrefilters && activeTab === "filters") {
4412
+ setActiveTab("layers");
4413
+ }
4414
+ }, [activeTab, hasPrefilters]);
4036
4415
  (0, import_react7.useEffect)(() => {
4037
4416
  if (activeTab !== "filters") return;
4038
4417
  if (!selectedFilterLayer || !selectedFilterField || !activeCatalogKey) return;
@@ -4433,7 +4812,7 @@ var ZenitLayerManager = ({
4433
4812
  ]
4434
4813
  }
4435
4814
  ),
4436
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4815
+ !hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4437
4816
  "button",
4438
4817
  {
4439
4818
  type: "button",
@@ -4450,8 +4829,9 @@ var ZenitLayerManager = ({
4450
4829
  )
4451
4830
  ] }),
4452
4831
  panelVisible && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { padding: "12px 10px 18px", overflowY: "auto", flex: 1, minHeight: 0 }, children: [
4832
+ hasPrefilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zlm-badge", style: { marginBottom: 10 }, children: "Este mapa ya incluye filtros preaplicados" }),
4453
4833
  activeTab === "layers" && renderLayerCards(),
4454
- 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: [
4834
+ !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: [
4455
4835
  filterableLayers.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
4456
4836
  "Capa",
4457
4837
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(