zenit-sdk 0.1.0 → 0.1.2

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.
@@ -1,5 +1,5 @@
1
1
  // src/react/ZenitMap.tsx
2
- import React3, { useCallback as useCallback2, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo2, useState as useState2, forwardRef } from "react";
2
+ import React4, { useCallback as useCallback2, useEffect as useEffect5, useImperativeHandle, useMemo as useMemo2, useRef as useRef5, useState as useState2, forwardRef } from "react";
3
3
  import { MapContainer, Marker as Marker2, TileLayer, ZoomControl } from "react-leaflet";
4
4
  import L4 from "leaflet";
5
5
 
@@ -246,10 +246,31 @@ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, op
246
246
  }
247
247
 
248
248
  // src/react/map/layer-geojson.tsx
249
+ import { useEffect, useRef } from "react";
249
250
  import { GeoJSON } from "react-leaflet";
250
251
  import L from "leaflet";
251
252
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
252
253
  var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
254
+ function normalizeBboxFromData(data) {
255
+ const bboxCandidate = data.bbox;
256
+ if (!Array.isArray(bboxCandidate) || bboxCandidate.length < 4) return null;
257
+ const [minLon, minLat, maxLon, maxLat] = bboxCandidate;
258
+ const values = [minLon, minLat, maxLon, maxLat].map((value) => Number(value));
259
+ if (values.every((value) => Number.isFinite(value))) {
260
+ return values;
261
+ }
262
+ return null;
263
+ }
264
+ function buildIdsSample(features) {
265
+ return features.slice(0, 10).map((feature) => {
266
+ const typedFeature = feature;
267
+ if (typedFeature.id !== void 0 && typedFeature.id !== null) {
268
+ return String(typedFeature.id);
269
+ }
270
+ const featureId = typedFeature.properties?.featureId;
271
+ return featureId !== void 0 && featureId !== null ? String(featureId) : "";
272
+ }).join(",");
273
+ }
253
274
  function getGeometryType(feature) {
254
275
  const t = feature?.geometry?.type;
255
276
  return typeof t === "string" ? t : null;
@@ -285,8 +306,58 @@ var LayerGeoJson = ({
285
306
  const features = data.features ?? [];
286
307
  const fillFeatures = features.filter(isNonPointGeometry);
287
308
  const pointFeatures = features.filter(isPointGeometry);
309
+ const dataVersionRef = useRef(0);
310
+ const prevSignatureRef = useRef("");
311
+ const firstId = features.length > 0 ? String(features[0]?.id ?? "") : "";
312
+ const lastId = features.length > 0 ? String(features[features.length - 1]?.id ?? "") : "";
313
+ const bbox = normalizeBboxFromData(data);
314
+ const idsSample = buildIdsSample(features);
315
+ const signature = bbox ? `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}|${bbox[0]}|${bbox[1]}|${bbox[2]}|${bbox[3]}` : `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}`;
316
+ const signatureToken = signature.replace(/[^a-zA-Z0-9_-]/g, "_");
317
+ if (prevSignatureRef.current !== signature) {
318
+ dataVersionRef.current += 1;
319
+ prevSignatureRef.current = signature;
320
+ }
288
321
  const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
289
322
  const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
323
+ const clusterLayerRef = useRef(null);
324
+ const canCluster = typeof L.markerClusterGroup === "function";
325
+ useEffect(() => {
326
+ if (!mapInstance || !panesReady || !pointsData || !canCluster) return;
327
+ const markerClusterGroup = L.markerClusterGroup;
328
+ const clusterLayer = clusterLayerRef.current ?? markerClusterGroup();
329
+ clusterLayerRef.current = clusterLayer;
330
+ if (!mapInstance.hasLayer(clusterLayer)) {
331
+ mapInstance.addLayer(clusterLayer);
332
+ }
333
+ clusterLayer.clearLayers();
334
+ const geoJsonLayer = L.geoJSON(pointsData, {
335
+ pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
336
+ radius: isMobile ? 8 : 6,
337
+ pane: mapInstance.getPane(pointsPaneName) ? pointsPaneName : void 0,
338
+ ...styleFn(feature, layerType, baseOpacity)
339
+ }),
340
+ onEachFeature
341
+ });
342
+ clusterLayer.addLayer(geoJsonLayer);
343
+ return () => {
344
+ clusterLayer.clearLayers();
345
+ if (mapInstance.hasLayer(clusterLayer)) {
346
+ mapInstance.removeLayer(clusterLayer);
347
+ }
348
+ };
349
+ }, [
350
+ baseOpacity,
351
+ canCluster,
352
+ isMobile,
353
+ layerType,
354
+ mapInstance,
355
+ onEachFeature,
356
+ panesReady,
357
+ pointsData,
358
+ pointsPaneName,
359
+ styleFn
360
+ ]);
290
361
  return /* @__PURE__ */ jsxs(Fragment, { children: [
291
362
  fillData && /* @__PURE__ */ jsx(
292
363
  GeoJSON,
@@ -299,9 +370,9 @@ var LayerGeoJson = ({
299
370
  onPolygonLabel?.(feature, layer);
300
371
  }
301
372
  },
302
- `fill-${layerId}`
373
+ `fill-${layerId}-${signatureToken}-v${dataVersionRef.current}`
303
374
  ),
304
- pointsData && /* @__PURE__ */ jsx(
375
+ pointsData && !canCluster && /* @__PURE__ */ jsx(
305
376
  GeoJSON,
306
377
  {
307
378
  data: pointsData,
@@ -312,24 +383,24 @@ var LayerGeoJson = ({
312
383
  }),
313
384
  onEachFeature
314
385
  },
315
- `points-${layerId}`
386
+ `points-${layerId}-${signatureToken}-v${dataVersionRef.current}`
316
387
  )
317
388
  ] });
318
389
  };
319
390
 
320
391
  // src/react/map/location-control.tsx
321
- import { useEffect as useEffect2, useMemo, useRef as useRef2 } from "react";
392
+ import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
322
393
  import { createPortal } from "react-dom";
323
394
  import { Circle, Marker, useMap } from "react-leaflet";
324
395
  import L3 from "leaflet";
325
396
 
326
397
  // src/react/hooks/use-geolocation.ts
327
- import { useCallback, useEffect, useRef, useState } from "react";
398
+ import { useCallback, useEffect as useEffect2, useRef as useRef2, useState } from "react";
328
399
  function useGeolocation(options) {
329
400
  const [isTracking, setIsTracking] = useState(false);
330
401
  const [location, setLocation] = useState(null);
331
402
  const [error, setError] = useState(null);
332
- const watchIdRef = useRef(null);
403
+ const watchIdRef = useRef2(null);
333
404
  const stopTracking = useCallback(() => {
334
405
  if (watchIdRef.current !== null && typeof navigator !== "undefined" && navigator.geolocation) {
335
406
  navigator.geolocation.clearWatch(watchIdRef.current);
@@ -371,7 +442,7 @@ function useGeolocation(options) {
371
442
  }
372
443
  }, [isTracking, startTracking, stopTracking]);
373
444
  const clearError = useCallback(() => setError(null), []);
374
- useEffect(() => {
445
+ useEffect2(() => {
375
446
  return () => {
376
447
  stopTracking();
377
448
  };
@@ -798,10 +869,10 @@ var LocateIcon = () => /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18",
798
869
  ] });
799
870
  var LocationControl = ({ position = "bottomleft", zoom = 16 }) => {
800
871
  const map = useMap();
801
- const controlRef = useRef2(null);
802
- const hasCenteredRef = useRef2(false);
872
+ const controlRef = useRef3(null);
873
+ const hasCenteredRef = useRef3(false);
803
874
  const { isTracking, location, error, toggleTracking, clearError } = useGeolocation();
804
- useEffect2(() => {
875
+ useEffect3(() => {
805
876
  if (typeof document === "undefined") return;
806
877
  if (document.getElementById(LOCATION_STYLE_ID)) return;
807
878
  const styleTag = document.createElement("style");
@@ -809,7 +880,7 @@ var LocationControl = ({ position = "bottomleft", zoom = 16 }) => {
809
880
  styleTag.textContent = LOCATION_STYLES;
810
881
  document.head.appendChild(styleTag);
811
882
  }, []);
812
- useEffect2(() => {
883
+ useEffect3(() => {
813
884
  const control = L3.control({ position });
814
885
  control.onAdd = () => {
815
886
  const container = L3.DomUtil.create("div", "zenit-location-control");
@@ -823,7 +894,7 @@ var LocationControl = ({ position = "bottomleft", zoom = 16 }) => {
823
894
  controlRef.current = null;
824
895
  };
825
896
  }, [map, position]);
826
- useEffect2(() => {
897
+ useEffect3(() => {
827
898
  if (!location || !isTracking) return;
828
899
  if (hasCenteredRef.current) return;
829
900
  hasCenteredRef.current = true;
@@ -870,8 +941,20 @@ var LocationControl = ({ position = "bottomleft", zoom = 16 }) => {
870
941
  };
871
942
 
872
943
  // src/react/map/map-handlers.tsx
873
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
944
+ import { useEffect as useEffect4, useRef as useRef4 } from "react";
874
945
  import { useMap as useMap2 } from "react-leaflet";
946
+ var MapInvalidator = ({ trigger }) => {
947
+ const map = useMap2();
948
+ const lastTrigger = useRef4(void 0);
949
+ useEffect4(() => {
950
+ if (lastTrigger.current === trigger) return;
951
+ lastTrigger.current = trigger;
952
+ requestAnimationFrame(() => {
953
+ map.invalidateSize();
954
+ });
955
+ }, [map, trigger]);
956
+ return null;
957
+ };
875
958
  function computeBBoxFromGeojson(geojson) {
876
959
  if (!geojson || !Array.isArray(geojson.features)) return null;
877
960
  const coords = [];
@@ -918,9 +1001,9 @@ var BBoxZoomHandler = ({
918
1001
  enabled = true
919
1002
  }) => {
920
1003
  const map = useMap2();
921
- const lastAppliedBBox = useRef3(null);
922
- const lastUserInteracted = useRef3(false);
923
- useEffect3(() => {
1004
+ const lastAppliedBBox = useRef4(null);
1005
+ const lastUserInteracted = useRef4(false);
1006
+ useEffect4(() => {
924
1007
  const handleInteraction = () => {
925
1008
  lastUserInteracted.current = true;
926
1009
  };
@@ -931,7 +1014,7 @@ var BBoxZoomHandler = ({
931
1014
  map.off("zoomstart", handleInteraction);
932
1015
  };
933
1016
  }, [map]);
934
- useEffect3(() => {
1017
+ useEffect4(() => {
935
1018
  if (!enabled) return;
936
1019
  let resolvedBBox = bbox ?? null;
937
1020
  if (!resolvedBBox && geojson) {
@@ -959,7 +1042,7 @@ var BBoxZoomHandler = ({
959
1042
  };
960
1043
  var ZoomBasedOpacityHandler = ({ onZoomChange }) => {
961
1044
  const map = useMap2();
962
- useEffect3(() => {
1045
+ useEffect4(() => {
963
1046
  const handleZoom = () => {
964
1047
  onZoomChange(map.getZoom());
965
1048
  };
@@ -973,7 +1056,7 @@ var ZoomBasedOpacityHandler = ({ onZoomChange }) => {
973
1056
  };
974
1057
  var MapInstanceBridge = ({ onReady }) => {
975
1058
  const map = useMap2();
976
- useEffect3(() => {
1059
+ useEffect4(() => {
977
1060
  onReady(map);
978
1061
  }, [map, onReady]);
979
1062
  return null;
@@ -1100,6 +1183,8 @@ var ZenitMap = forwardRef(({
1100
1183
  const [loadingMap, setLoadingMap] = useState2(false);
1101
1184
  const [mapError, setMapError] = useState2(null);
1102
1185
  const [mapInstance, setMapInstance] = useState2(null);
1186
+ const [layerGeojsonOverrides, setLayerGeojsonOverrides] = useState2({});
1187
+ const originalGeojsonByLayerIdRef = useRef5({});
1103
1188
  const [panesReady, setPanesReady] = useState2(false);
1104
1189
  const [currentZoom, setCurrentZoom] = useState2(initialZoom ?? DEFAULT_ZOOM);
1105
1190
  const [isPopupOpen, setIsPopupOpen] = useState2(false);
@@ -1108,7 +1193,7 @@ var ZenitMap = forwardRef(({
1108
1193
  return window.matchMedia("(max-width: 768px)").matches;
1109
1194
  });
1110
1195
  const normalizedLayers = useMemo2(() => normalizeMapLayers(map), [map]);
1111
- useEffect4(() => {
1196
+ useEffect5(() => {
1112
1197
  if (typeof window === "undefined") return;
1113
1198
  const mql = window.matchMedia("(max-width: 768px)");
1114
1199
  const onChange = (e) => {
@@ -1126,17 +1211,17 @@ var ZenitMap = forwardRef(({
1126
1211
  }
1127
1212
  return;
1128
1213
  }, []);
1129
- useEffect4(() => {
1214
+ useEffect5(() => {
1130
1215
  if (featureInfoMode === "popup") {
1131
1216
  ensurePopupStyles();
1132
1217
  }
1133
1218
  }, [featureInfoMode]);
1134
- useEffect4(() => {
1219
+ useEffect5(() => {
1135
1220
  if (featureInfoMode !== "popup") {
1136
1221
  setIsPopupOpen(false);
1137
1222
  }
1138
1223
  }, [featureInfoMode]);
1139
- useEffect4(() => {
1224
+ useEffect5(() => {
1140
1225
  if (!mapInstance) return;
1141
1226
  const popupPane = mapInstance.getPane("popupPane");
1142
1227
  if (popupPane) {
@@ -1145,7 +1230,7 @@ var ZenitMap = forwardRef(({
1145
1230
  const labelsPane = mapInstance.getPane(LABELS_PANE_NAME) ?? mapInstance.createPane(LABELS_PANE_NAME);
1146
1231
  labelsPane.style.zIndex = "600";
1147
1232
  }, [mapInstance]);
1148
- useEffect4(() => {
1233
+ useEffect5(() => {
1149
1234
  if (!mapInstance) return;
1150
1235
  const handlePopupOpen = () => setIsPopupOpen(true);
1151
1236
  const handlePopupClose = () => setIsPopupOpen(false);
@@ -1214,7 +1299,7 @@ var ZenitMap = forwardRef(({
1214
1299
  const [mapOverrides, setMapOverrides] = useState2([]);
1215
1300
  const [controlOverrides, setControlOverrides] = useState2([]);
1216
1301
  const [uiOverrides, setUiOverrides] = useState2([]);
1217
- useEffect4(() => {
1302
+ useEffect5(() => {
1218
1303
  let isMounted = true;
1219
1304
  setLoadingMap(true);
1220
1305
  setMapError(null);
@@ -1237,7 +1322,7 @@ var ZenitMap = forwardRef(({
1237
1322
  isMounted = false;
1238
1323
  };
1239
1324
  }, [client.maps, mapId, onError, onLoadingChange]);
1240
- useEffect4(() => {
1325
+ useEffect5(() => {
1241
1326
  if (normalizedLayers.length === 0) {
1242
1327
  setLayers([]);
1243
1328
  setBaseStates([]);
@@ -1268,7 +1353,7 @@ var ZenitMap = forwardRef(({
1268
1353
  setMapOverrides(initialOverrides);
1269
1354
  setUiOverrides([]);
1270
1355
  }, [normalizedLayers]);
1271
- useEffect4(() => {
1356
+ useEffect5(() => {
1272
1357
  if (!layerControls) {
1273
1358
  setControlOverrides([]);
1274
1359
  return;
@@ -1280,7 +1365,7 @@ var ZenitMap = forwardRef(({
1280
1365
  }));
1281
1366
  setControlOverrides(overrides);
1282
1367
  }, [layerControls]);
1283
- useEffect4(() => {
1368
+ useEffect5(() => {
1284
1369
  if (layerStates) {
1285
1370
  return;
1286
1371
  }
@@ -1290,12 +1375,12 @@ var ZenitMap = forwardRef(({
1290
1375
  onLayerStateChange?.(reset);
1291
1376
  }
1292
1377
  }, [baseStates, effectiveStates.length, layerControls, layerStates, onLayerStateChange]);
1293
- useEffect4(() => {
1378
+ useEffect5(() => {
1294
1379
  if (layerStates) {
1295
1380
  setEffectiveStates(layerStates);
1296
1381
  }
1297
1382
  }, [layerStates]);
1298
- useEffect4(() => {
1383
+ useEffect5(() => {
1299
1384
  if (layerStates) {
1300
1385
  return;
1301
1386
  }
@@ -1308,11 +1393,11 @@ var ZenitMap = forwardRef(({
1308
1393
  setEffectiveStates(next);
1309
1394
  onLayerStateChange?.(next);
1310
1395
  }, [baseStates, controlOverrides, layerStates, mapOverrides, onLayerStateChange, uiOverrides]);
1311
- useEffect4(() => {
1396
+ useEffect5(() => {
1312
1397
  if (!Array.isArray(layerControls) || layerControls.length > 0) return;
1313
1398
  setUiOverrides([]);
1314
1399
  }, [layerControls]);
1315
- useEffect4(() => {
1400
+ useEffect5(() => {
1316
1401
  if (layerStates) {
1317
1402
  return;
1318
1403
  }
@@ -1382,16 +1467,30 @@ var ZenitMap = forwardRef(({
1382
1467
  return DEFAULT_CENTER;
1383
1468
  }, [initialCenter, map?.settings?.center]);
1384
1469
  const zoom = initialZoom ?? map?.settings?.zoom ?? DEFAULT_ZOOM;
1385
- useEffect4(() => {
1470
+ useEffect5(() => {
1386
1471
  setCurrentZoom(zoom);
1387
1472
  }, [zoom]);
1473
+ useEffect5(() => {
1474
+ if (!layerGeojson) return;
1475
+ layers.forEach((layer) => {
1476
+ const layerKey = String(layer.mapLayer.layerId);
1477
+ const incoming = layerGeojson[layer.mapLayer.layerId] ?? layerGeojson[layerKey] ?? null;
1478
+ if (incoming && !originalGeojsonByLayerIdRef.current[layerKey]) {
1479
+ originalGeojsonByLayerIdRef.current[layerKey] = incoming;
1480
+ }
1481
+ });
1482
+ }, [layerGeojson, layers]);
1388
1483
  const decoratedLayers = useMemo2(() => {
1389
- return layers.map((layer) => ({
1390
- ...layer,
1391
- effective: effectiveStates.find((state) => state.layerId === layer.mapLayer.layerId),
1392
- data: layerGeojson?.[layer.mapLayer.layerId] ?? layerGeojson?.[String(layer.mapLayer.layerId)] ?? null
1393
- }));
1394
- }, [effectiveStates, layerGeojson, layers]);
1484
+ return layers.map((layer) => {
1485
+ const layerKey = String(layer.mapLayer.layerId);
1486
+ const override = layerGeojsonOverrides[layerKey];
1487
+ return {
1488
+ ...layer,
1489
+ effective: effectiveStates.find((state) => state.layerId === layer.mapLayer.layerId),
1490
+ data: override ?? layerGeojson?.[layer.mapLayer.layerId] ?? layerGeojson?.[layerKey] ?? null
1491
+ };
1492
+ });
1493
+ }, [effectiveStates, layerGeojson, layerGeojsonOverrides, layers]);
1395
1494
  const orderedLayers = useMemo2(() => {
1396
1495
  return [...decoratedLayers].filter((layer) => layer.effective?.visible && layer.data).sort((a, b) => a.displayOrder - b.displayOrder);
1397
1496
  }, [decoratedLayers]);
@@ -1466,7 +1565,7 @@ var ZenitMap = forwardRef(({
1466
1565
  },
1467
1566
  [onMapReady]
1468
1567
  );
1469
- useEffect4(() => {
1568
+ useEffect5(() => {
1470
1569
  if (!mapInstance) {
1471
1570
  setPanesReady(false);
1472
1571
  return;
@@ -1620,6 +1719,24 @@ var ZenitMap = forwardRef(({
1620
1719
  };
1621
1720
  mapInstance.fitBounds(bounds, fitOptions);
1622
1721
  },
1722
+ fitToBbox: (bbox, padding) => {
1723
+ if (!mapInstance) return;
1724
+ if (typeof bbox.minLat !== "number" || typeof bbox.minLon !== "number" || typeof bbox.maxLat !== "number" || typeof bbox.maxLon !== "number" || !Number.isFinite(bbox.minLat) || !Number.isFinite(bbox.minLon) || !Number.isFinite(bbox.maxLat) || !Number.isFinite(bbox.maxLon)) {
1725
+ console.warn("[ZenitMap.fitToBbox] Invalid bbox", bbox);
1726
+ return;
1727
+ }
1728
+ const resolvedPadding = padding ?? (isMobile ? 40 : 24);
1729
+ const bounds = L4.latLngBounds([bbox.minLat, bbox.minLon], [bbox.maxLat, bbox.maxLon]);
1730
+ mapInstance.fitBounds(bounds, { padding: [resolvedPadding, resolvedPadding], animate: true });
1731
+ },
1732
+ fitToGeoJson: (fc, padding) => {
1733
+ if (!mapInstance) return;
1734
+ if (!fc?.features?.length) return;
1735
+ const bounds = L4.geoJSON(fc).getBounds();
1736
+ if (!bounds.isValid()) return;
1737
+ const resolvedPadding = padding ?? (isMobile ? 40 : 24);
1738
+ mapInstance.fitBounds(bounds, { padding: [resolvedPadding, resolvedPadding], animate: true });
1739
+ },
1623
1740
  setView: (coordinates, zoom2) => {
1624
1741
  if (!mapInstance) return;
1625
1742
  mapInstance.setView([coordinates.lat, coordinates.lon], zoom2 ?? mapInstance.getZoom(), {
@@ -1644,8 +1761,25 @@ var ZenitMap = forwardRef(({
1644
1761
  highlightFeature: (layerId, featureId) => {
1645
1762
  upsertUiOverride(layerId, { overrideVisible: true, overrideOpacity: 1 });
1646
1763
  },
1764
+ updateLayerGeoJson: (layerId, featureCollection) => {
1765
+ const layerKey = String(layerId);
1766
+ setLayerGeojsonOverrides((prev) => ({ ...prev, [layerKey]: featureCollection }));
1767
+ },
1768
+ restoreLayerGeoJson: (layerId) => {
1769
+ const layerKey = String(layerId);
1770
+ const original = originalGeojsonByLayerIdRef.current[layerKey];
1771
+ setLayerGeojsonOverrides((prev) => {
1772
+ const next = { ...prev };
1773
+ if (original) {
1774
+ next[layerKey] = original;
1775
+ } else {
1776
+ delete next[layerKey];
1777
+ }
1778
+ return next;
1779
+ });
1780
+ },
1647
1781
  getMapInstance: () => mapInstance
1648
- }), [effectiveStates, mapInstance]);
1782
+ }), [effectiveStates, isMobile, mapInstance]);
1649
1783
  if (loadingMap) {
1650
1784
  return /* @__PURE__ */ jsx3("div", { style: { padding: 16, height, width }, children: "Cargando mapa..." });
1651
1785
  }
@@ -1698,6 +1832,7 @@ var ZenitMap = forwardRef(({
1698
1832
  ),
1699
1833
  /* @__PURE__ */ jsx3(ZoomControl, { position: "topright" }),
1700
1834
  /* @__PURE__ */ jsx3(MapInstanceBridge, { onReady: handleMapReady }),
1835
+ /* @__PURE__ */ jsx3(MapInvalidator, { trigger: mapId }),
1701
1836
  /* @__PURE__ */ jsx3(
1702
1837
  BBoxZoomHandler,
1703
1838
  {
@@ -1714,7 +1849,7 @@ var ZenitMap = forwardRef(({
1714
1849
  const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
1715
1850
  const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
1716
1851
  const labelKey = labelKeyIndex.get(String(layerState.mapLayer.layerId));
1717
- return /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
1852
+ return /* @__PURE__ */ jsxs3(React4.Fragment, { children: [
1718
1853
  layerState.data && /* @__PURE__ */ jsx3(
1719
1854
  LayerGeoJson,
1720
1855
  {
@@ -1871,13 +2006,13 @@ var ZenitMap = forwardRef(({
1871
2006
  ZenitMap.displayName = "ZenitMap";
1872
2007
 
1873
2008
  // src/react/ZenitLayerManager.tsx
1874
- import React4, { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef5, useState as useState3 } from "react";
2009
+ import React5, { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState3 } from "react";
1875
2010
 
1876
2011
  // src/react/icons.tsx
1877
2012
  import { Eye, EyeOff, ChevronLeft, ChevronRight, Layers, Upload, X, ZoomIn } from "lucide-react";
1878
2013
 
1879
2014
  // src/react/ZenitLayerManager.tsx
1880
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2015
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1881
2016
  var FLOAT_TOLERANCE = 1e-3;
1882
2017
  function areEffectiveStatesEqual(a, b) {
1883
2018
  if (a.length !== b.length) return false;
@@ -1915,7 +2050,9 @@ var ZenitLayerManager = ({
1915
2050
  showUploadTab = true,
1916
2051
  showLayerVisibilityIcon = true,
1917
2052
  layerFeatureCounts,
1918
- mapLayers
2053
+ mapLayers,
2054
+ onApplyLayerFilter,
2055
+ onClearLayerFilter
1919
2056
  }) => {
1920
2057
  const [map, setMap] = useState3(null);
1921
2058
  const [loadingMap, setLoadingMap] = useState3(false);
@@ -1923,7 +2060,16 @@ var ZenitLayerManager = ({
1923
2060
  const [layers, setLayers] = useState3([]);
1924
2061
  const [activeTab, setActiveTab] = useState3("layers");
1925
2062
  const [panelVisible, setPanelVisible] = useState3(true);
1926
- const lastEmittedStatesRef = useRef5(null);
2063
+ const [selectedFilterLayerId, setSelectedFilterLayerId] = useState3("");
2064
+ const [selectedFilterField, setSelectedFilterField] = useState3("");
2065
+ const [selectedFilterValue, setSelectedFilterValue] = useState3("");
2066
+ const [catalogByLayerField, setCatalogByLayerField] = useState3({});
2067
+ const [loadingCatalog, setLoadingCatalog] = useState3(false);
2068
+ const [applyingFilter, setApplyingFilter] = useState3(false);
2069
+ const [filterError, setFilterError] = useState3(null);
2070
+ const [appliedFilter, setAppliedFilter] = useState3(null);
2071
+ const catalogAbortRef = useRef6(null);
2072
+ const lastEmittedStatesRef = useRef6(null);
1927
2073
  const isControlled = Array.isArray(layerStates) && typeof onLayerStatesChange === "function";
1928
2074
  const baseStates = useMemo3(
1929
2075
  () => initLayerStates(
@@ -1964,7 +2110,7 @@ var ZenitLayerManager = ({
1964
2110
  });
1965
2111
  return index;
1966
2112
  }, [map, mapLayers]);
1967
- const resolveUserOpacity = React4.useCallback((state) => {
2113
+ const resolveUserOpacity = React5.useCallback((state) => {
1968
2114
  if (typeof state.overrideOpacity === "number") return state.overrideOpacity;
1969
2115
  if (typeof state.overrideOpacity === "string") {
1970
2116
  const parsed = Number.parseFloat(state.overrideOpacity);
@@ -1972,7 +2118,7 @@ var ZenitLayerManager = ({
1972
2118
  }
1973
2119
  return state.opacity ?? 1;
1974
2120
  }, []);
1975
- const resolveEffectiveOpacity = React4.useCallback(
2121
+ const resolveEffectiveOpacity = React5.useCallback(
1976
2122
  (layerId, userOpacity) => {
1977
2123
  if (!autoOpacityOnZoom || typeof mapZoom !== "number") {
1978
2124
  return userOpacity;
@@ -2002,7 +2148,7 @@ var ZenitLayerManager = ({
2002
2148
  };
2003
2149
  });
2004
2150
  }, [autoOpacityOnZoom, effectiveStates, mapZoom, resolveEffectiveOpacity, resolveUserOpacity]);
2005
- useEffect5(() => {
2151
+ useEffect6(() => {
2006
2152
  let cancelled = false;
2007
2153
  setLoadingMap(true);
2008
2154
  setMapError(null);
@@ -2034,12 +2180,12 @@ var ZenitLayerManager = ({
2034
2180
  cancelled = true;
2035
2181
  };
2036
2182
  }, [client.maps, mapId]);
2037
- useEffect5(() => {
2183
+ useEffect6(() => {
2038
2184
  if (!showUploadTab && activeTab === "upload") {
2039
2185
  setActiveTab("layers");
2040
2186
  }
2041
2187
  }, [activeTab, showUploadTab]);
2042
- useEffect5(() => {
2188
+ useEffect6(() => {
2043
2189
  if (isControlled) return;
2044
2190
  if (!onLayerStatesChange) return;
2045
2191
  const emitStates = autoOpacityOnZoom && typeof mapZoom === "number" ? effectiveStatesWithZoom : effectiveStates;
@@ -2057,7 +2203,7 @@ var ZenitLayerManager = ({
2057
2203
  mapZoom,
2058
2204
  onLayerStatesChange
2059
2205
  ]);
2060
- const updateLayerVisible = React4.useCallback(
2206
+ const updateLayerVisible = React5.useCallback(
2061
2207
  (layerId, visible) => {
2062
2208
  if (!onLayerStatesChange) return;
2063
2209
  const next = effectiveStates.map(
@@ -2067,7 +2213,7 @@ var ZenitLayerManager = ({
2067
2213
  },
2068
2214
  [effectiveStates, onLayerStatesChange]
2069
2215
  );
2070
- const updateLayerOpacity = React4.useCallback(
2216
+ const updateLayerOpacity = React5.useCallback(
2071
2217
  (layerId, opacity) => {
2072
2218
  if (!onLayerStatesChange) return;
2073
2219
  const adjustedOpacity = resolveEffectiveOpacity(layerId, opacity);
@@ -2078,7 +2224,7 @@ var ZenitLayerManager = ({
2078
2224
  },
2079
2225
  [effectiveStates, onLayerStatesChange, resolveEffectiveOpacity]
2080
2226
  );
2081
- const resolveFeatureCount = React4.useCallback(
2227
+ const resolveFeatureCount = React5.useCallback(
2082
2228
  (layerId, layer) => {
2083
2229
  const resolvedFeatureCount = layerFeatureCounts?.[layerId] ?? layerFeatureCounts?.[String(layerId)];
2084
2230
  if (typeof resolvedFeatureCount === "number") return resolvedFeatureCount;
@@ -2116,7 +2262,140 @@ var ZenitLayerManager = ({
2116
2262
  return String(a.mapLayer.layerId).localeCompare(String(b.mapLayer.layerId));
2117
2263
  });
2118
2264
  }, [effectiveStates, layers, resolveFeatureCount]);
2119
- const resolveLayerStyle = React4.useCallback(
2265
+ const filterableLayers = useMemo3(() => {
2266
+ return decoratedLayers.filter((entry) => {
2267
+ const prefilters = entry.mapLayer.layerConfig?.prefilters;
2268
+ return !!prefilters && Object.keys(prefilters).length > 0;
2269
+ });
2270
+ }, [decoratedLayers]);
2271
+ const selectedFilterLayer = useMemo3(
2272
+ () => filterableLayers.find((layer) => String(layer.mapLayer.layerId) === selectedFilterLayerId) ?? null,
2273
+ [filterableLayers, selectedFilterLayerId]
2274
+ );
2275
+ const filterFields = useMemo3(() => {
2276
+ const prefilters = selectedFilterLayer?.mapLayer.layerConfig?.prefilters;
2277
+ return prefilters ? Object.keys(prefilters) : [];
2278
+ }, [selectedFilterLayer]);
2279
+ const activeCatalogKey = selectedFilterLayer ? `${selectedFilterLayer.mapLayer.layerId}:${selectedFilterField}` : null;
2280
+ const activeCatalogValues = activeCatalogKey ? catalogByLayerField[activeCatalogKey] ?? [] : [];
2281
+ const extractCatalogValues = React5.useCallback((catalogData, field) => {
2282
+ const values = /* @__PURE__ */ new Set();
2283
+ const pushValue = (value) => {
2284
+ if (value === null || value === void 0) return;
2285
+ const normalized = String(value).trim();
2286
+ if (normalized) values.add(normalized);
2287
+ };
2288
+ if (catalogData && typeof catalogData === "object") {
2289
+ const maybeRecord = catalogData;
2290
+ const directField = maybeRecord[field];
2291
+ if (Array.isArray(directField)) {
2292
+ directField.forEach(pushValue);
2293
+ }
2294
+ const items = maybeRecord.items;
2295
+ if (Array.isArray(items)) {
2296
+ items.forEach((item) => {
2297
+ if (!item || typeof item !== "object") return;
2298
+ const row = item;
2299
+ const rowField = row.field;
2300
+ if (String(rowField ?? "").toUpperCase() === field.toUpperCase() && Array.isArray(row.values)) {
2301
+ row.values.forEach(pushValue);
2302
+ }
2303
+ });
2304
+ }
2305
+ const features = maybeRecord.features;
2306
+ if (Array.isArray(features)) {
2307
+ features.forEach((feature) => {
2308
+ if (!feature || typeof feature !== "object") return;
2309
+ const properties = feature.properties;
2310
+ if (properties && field in properties) {
2311
+ pushValue(properties[field]);
2312
+ }
2313
+ });
2314
+ }
2315
+ }
2316
+ return [...values].sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
2317
+ }, []);
2318
+ useEffect6(() => {
2319
+ if (!filterableLayers.length) {
2320
+ setSelectedFilterLayerId("");
2321
+ return;
2322
+ }
2323
+ if (!selectedFilterLayerId || !filterableLayers.some((layer) => String(layer.mapLayer.layerId) === selectedFilterLayerId)) {
2324
+ setSelectedFilterLayerId(String(filterableLayers[0].mapLayer.layerId));
2325
+ }
2326
+ }, [filterableLayers, selectedFilterLayerId]);
2327
+ useEffect6(() => {
2328
+ if (!filterFields.length) {
2329
+ setSelectedFilterField("");
2330
+ return;
2331
+ }
2332
+ if (!selectedFilterField || !filterFields.includes(selectedFilterField)) {
2333
+ setSelectedFilterField(filterFields[0]);
2334
+ setSelectedFilterValue("");
2335
+ }
2336
+ }, [filterFields, selectedFilterField]);
2337
+ useEffect6(() => {
2338
+ if (activeTab !== "filters") return;
2339
+ if (!selectedFilterLayer || !selectedFilterField || !activeCatalogKey) return;
2340
+ if (catalogByLayerField[activeCatalogKey]) return;
2341
+ catalogAbortRef.current?.abort();
2342
+ const controller = new AbortController();
2343
+ catalogAbortRef.current = controller;
2344
+ setLoadingCatalog(true);
2345
+ setFilterError(null);
2346
+ client.layers.getLayerFeaturesCatalog(selectedFilterLayer.mapLayer.layerId).then((response) => {
2347
+ if (controller.signal.aborted) return;
2348
+ const values = extractCatalogValues(response.data, selectedFilterField);
2349
+ setCatalogByLayerField((prev) => ({ ...prev, [activeCatalogKey]: values }));
2350
+ }).catch((error) => {
2351
+ if (controller.signal.aborted) return;
2352
+ const message = error instanceof Error ? error.message : "No se pudo cargar el cat\xE1logo";
2353
+ setFilterError(message);
2354
+ }).finally(() => {
2355
+ if (!controller.signal.aborted) setLoadingCatalog(false);
2356
+ });
2357
+ return () => {
2358
+ controller.abort();
2359
+ };
2360
+ }, [activeCatalogKey, activeTab, catalogByLayerField, client.layers, extractCatalogValues, selectedFilterField, selectedFilterLayer]);
2361
+ const handleApplyFilter = React5.useCallback(async () => {
2362
+ if (!selectedFilterLayer || !selectedFilterField || !selectedFilterValue || !onApplyLayerFilter) return;
2363
+ setApplyingFilter(true);
2364
+ setFilterError(null);
2365
+ try {
2366
+ await onApplyLayerFilter({
2367
+ layerId: selectedFilterLayer.mapLayer.layerId,
2368
+ field: selectedFilterField,
2369
+ value: selectedFilterValue
2370
+ });
2371
+ setAppliedFilter({
2372
+ layerId: selectedFilterLayer.mapLayer.layerId,
2373
+ field: selectedFilterField,
2374
+ value: selectedFilterValue
2375
+ });
2376
+ } catch (error) {
2377
+ const message = error instanceof Error ? error.message : "No se pudo aplicar el filtro";
2378
+ setFilterError(message);
2379
+ } finally {
2380
+ setApplyingFilter(false);
2381
+ }
2382
+ }, [onApplyLayerFilter, selectedFilterField, selectedFilterLayer, selectedFilterValue]);
2383
+ const handleClearFilter = React5.useCallback(async () => {
2384
+ if (!selectedFilterLayer) return;
2385
+ setApplyingFilter(true);
2386
+ setFilterError(null);
2387
+ try {
2388
+ await onClearLayerFilter?.({ layerId: selectedFilterLayer.mapLayer.layerId, field: selectedFilterField || void 0 });
2389
+ setSelectedFilterValue("");
2390
+ setAppliedFilter(null);
2391
+ } catch (error) {
2392
+ const message = error instanceof Error ? error.message : "No se pudo limpiar el filtro";
2393
+ setFilterError(message);
2394
+ } finally {
2395
+ setApplyingFilter(false);
2396
+ }
2397
+ }, [onClearLayerFilter, selectedFilterField, selectedFilterLayer]);
2398
+ const resolveLayerStyle = React5.useCallback(
2120
2399
  (layerId) => {
2121
2400
  const layerKey = String(layerId);
2122
2401
  const fromProp = mapLayers?.find((entry) => String(entry.layerId) === layerKey)?.style;
@@ -2454,6 +2733,18 @@ var ZenitLayerManager = ({
2454
2733
  "Subir"
2455
2734
  ]
2456
2735
  }
2736
+ ),
2737
+ /* @__PURE__ */ jsxs4(
2738
+ "button",
2739
+ {
2740
+ type: "button",
2741
+ className: `zlm-tab${activeTab === "filters" ? " is-active" : ""}`,
2742
+ onClick: () => setActiveTab("filters"),
2743
+ children: [
2744
+ /* @__PURE__ */ jsx4(Layers, { size: 16 }),
2745
+ "Filtros"
2746
+ ]
2747
+ }
2457
2748
  )
2458
2749
  ]
2459
2750
  }
@@ -2461,6 +2752,68 @@ var ZenitLayerManager = ({
2461
2752
  ] }),
2462
2753
  panelVisible && /* @__PURE__ */ jsxs4("div", { style: { padding: "12px 10px 18px", overflowY: "auto", flex: 1, minHeight: 0 }, children: [
2463
2754
  activeTab === "layers" && renderLayerCards(),
2755
+ activeTab === "filters" && /* @__PURE__ */ jsx4("div", { className: "zlm-filter-panel", style: { display: "flex", flexDirection: "column", gap: 10, background: "#fff", border: "1px solid #e2e8f0", borderRadius: 12, padding: 12 }, children: !filterableLayers.length ? /* @__PURE__ */ jsx4("div", { style: { color: "#64748b", fontSize: 13 }, children: "No hay filtros disponibles para las capas de este mapa." }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
2756
+ filterableLayers.length > 1 && /* @__PURE__ */ jsxs4("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
2757
+ "Capa",
2758
+ /* @__PURE__ */ jsx4("select", { className: "zlm-filter-select", value: selectedFilterLayerId, onChange: (e) => setSelectedFilterLayerId(e.target.value), children: filterableLayers.map((layer) => /* @__PURE__ */ jsx4("option", { value: String(layer.mapLayer.layerId), children: layer.layerName ?? `Capa ${layer.mapLayer.layerId}` }, String(layer.mapLayer.layerId))) })
2759
+ ] }),
2760
+ /* @__PURE__ */ jsxs4("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
2761
+ "Campo",
2762
+ /* @__PURE__ */ jsx4("select", { className: "zlm-filter-select", value: selectedFilterField, onChange: (e) => {
2763
+ setSelectedFilterField(e.target.value);
2764
+ setSelectedFilterValue("");
2765
+ }, children: filterFields.map((field) => /* @__PURE__ */ jsx4("option", { value: field, children: field }, field)) })
2766
+ ] }),
2767
+ /* @__PURE__ */ jsxs4("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
2768
+ "Valor",
2769
+ /* @__PURE__ */ jsxs4(
2770
+ "select",
2771
+ {
2772
+ className: "zlm-filter-select",
2773
+ value: selectedFilterValue,
2774
+ onChange: (e) => {
2775
+ const value = e.target.value;
2776
+ setSelectedFilterValue(value);
2777
+ },
2778
+ children: [
2779
+ /* @__PURE__ */ jsx4("option", { value: "", children: "Seleccionar\u2026" }),
2780
+ activeCatalogValues.map((value) => /* @__PURE__ */ jsx4("option", { value, children: value }, value))
2781
+ ]
2782
+ }
2783
+ )
2784
+ ] }),
2785
+ loadingCatalog && /* @__PURE__ */ jsx4("div", { style: { color: "#64748b", fontSize: 12 }, children: "Cargando cat\xE1logo\u2026" }),
2786
+ !loadingCatalog && selectedFilterField && activeCatalogValues.length === 0 && /* @__PURE__ */ jsx4("div", { style: { color: "#64748b", fontSize: 12 }, children: "No hay cat\xE1logo disponible para este filtro." }),
2787
+ filterError && /* @__PURE__ */ jsx4("div", { style: { color: "#b91c1c", fontSize: 12 }, children: filterError }),
2788
+ appliedFilter && /* @__PURE__ */ jsxs4("div", { className: "zlm-badge", style: { alignSelf: "flex-start" }, children: [
2789
+ "Activo: ",
2790
+ appliedFilter.field,
2791
+ " = ",
2792
+ appliedFilter.value
2793
+ ] }),
2794
+ /* @__PURE__ */ jsxs4("div", { className: "zlm-filter-actions", style: { display: "flex", gap: 8 }, children: [
2795
+ /* @__PURE__ */ jsx4(
2796
+ "button",
2797
+ {
2798
+ type: "button",
2799
+ className: "zlm-panel-toggle",
2800
+ disabled: !selectedFilterValue || applyingFilter || !onApplyLayerFilter,
2801
+ onClick: handleApplyFilter,
2802
+ children: applyingFilter ? "Aplicando\u2026" : "Aplicar"
2803
+ }
2804
+ ),
2805
+ /* @__PURE__ */ jsx4(
2806
+ "button",
2807
+ {
2808
+ type: "button",
2809
+ className: "zlm-panel-toggle",
2810
+ disabled: applyingFilter,
2811
+ onClick: handleClearFilter,
2812
+ children: "Limpiar"
2813
+ }
2814
+ )
2815
+ ] })
2816
+ ] }) }),
2464
2817
  showUploadTab && activeTab === "upload" && /* @__PURE__ */ jsx4("div", { style: { color: "#475569", fontSize: 13 }, children: "Pr\xF3ximamente podr\xE1s subir capas desde este panel." })
2465
2818
  ] })
2466
2819
  ] });
@@ -2499,11 +2852,11 @@ var ZenitFeatureFilterPanel = ({
2499
2852
  };
2500
2853
 
2501
2854
  // src/react/ai/FloatingChatBox.tsx
2502
- import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef7, useState as useState5 } from "react";
2855
+ import { useCallback as useCallback4, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef8, useState as useState5 } from "react";
2503
2856
  import { createPortal as createPortal2 } from "react-dom";
2504
2857
 
2505
2858
  // src/react/hooks/use-chat.ts
2506
- import { useCallback as useCallback3, useRef as useRef6, useState as useState4 } from "react";
2859
+ import { useCallback as useCallback3, useRef as useRef7, useState as useState4 } from "react";
2507
2860
 
2508
2861
  // src/ai/chat.service.ts
2509
2862
  var DEFAULT_ERROR_MESSAGE = "No fue posible completar la solicitud al asistente.";
@@ -2663,7 +3016,7 @@ var useSendMessageStream = (config) => {
2663
3016
  const [streamingText, setStreamingText] = useState4("");
2664
3017
  const [completeResponse, setCompleteResponse] = useState4(null);
2665
3018
  const [error, setError] = useState4(null);
2666
- const requestIdRef = useRef6(0);
3019
+ const requestIdRef = useRef7(0);
2667
3020
  const reset = useCallback3(() => {
2668
3021
  setIsStreaming(false);
2669
3022
  setStreamingText("");
@@ -2875,7 +3228,7 @@ var MarkdownRenderer = ({ content, className }) => {
2875
3228
  };
2876
3229
 
2877
3230
  // src/react/ai/FloatingChatBox.tsx
2878
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3231
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2879
3232
  var ChatIcon = () => /* @__PURE__ */ jsx7("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx7("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
2880
3233
  var CloseIcon = () => /* @__PURE__ */ jsxs6("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2881
3234
  /* @__PURE__ */ jsx7("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
@@ -2961,6 +3314,17 @@ var styles = {
2961
3314
  width: 480,
2962
3315
  height: 640
2963
3316
  },
3317
+ panelMobileFullscreen: {
3318
+ position: "fixed",
3319
+ top: 0,
3320
+ left: 0,
3321
+ right: 0,
3322
+ bottom: 0,
3323
+ width: "100%",
3324
+ height: "100%",
3325
+ borderRadius: 0,
3326
+ margin: 0
3327
+ },
2964
3328
  // Header with green gradient
2965
3329
  header: {
2966
3330
  padding: "14px 16px",
@@ -3210,16 +3574,16 @@ var FloatingChatBox = ({
3210
3574
  const [errorMessage, setErrorMessage] = useState5(null);
3211
3575
  const [isFocused, setIsFocused] = useState5(false);
3212
3576
  const [isMobile, setIsMobile] = useState5(false);
3213
- const messagesEndRef = useRef7(null);
3214
- const messagesContainerRef = useRef7(null);
3215
- const chatBoxRef = useRef7(null);
3577
+ const messagesEndRef = useRef8(null);
3578
+ const messagesContainerRef = useRef8(null);
3579
+ const chatBoxRef = useRef8(null);
3216
3580
  const chatConfig = useMemo4(() => {
3217
3581
  if (!baseUrl) return void 0;
3218
3582
  return { baseUrl, accessToken, getAccessToken };
3219
3583
  }, [accessToken, baseUrl, getAccessToken]);
3220
3584
  const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
3221
3585
  const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
3222
- useEffect6(() => {
3586
+ useEffect7(() => {
3223
3587
  if (open && isMobile) {
3224
3588
  setExpanded(true);
3225
3589
  }
@@ -3229,7 +3593,7 @@ var FloatingChatBox = ({
3229
3593
  messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
3230
3594
  }
3231
3595
  }, []);
3232
- useEffect6(() => {
3596
+ useEffect7(() => {
3233
3597
  if (open && messages.length === 0) {
3234
3598
  setMessages([
3235
3599
  {
@@ -3240,10 +3604,10 @@ var FloatingChatBox = ({
3240
3604
  ]);
3241
3605
  }
3242
3606
  }, [open, messages.length]);
3243
- useEffect6(() => {
3607
+ useEffect7(() => {
3244
3608
  scrollToBottom();
3245
3609
  }, [messages, streamingText, scrollToBottom]);
3246
- useEffect6(() => {
3610
+ useEffect7(() => {
3247
3611
  if (!open) return;
3248
3612
  if (isMobile && expanded) return;
3249
3613
  const handleClickOutside = (event) => {
@@ -3256,7 +3620,7 @@ var FloatingChatBox = ({
3256
3620
  document.removeEventListener("mousedown", handleClickOutside);
3257
3621
  };
3258
3622
  }, [open, isMobile, expanded]);
3259
- useEffect6(() => {
3623
+ useEffect7(() => {
3260
3624
  if (typeof window === "undefined") return;
3261
3625
  const mediaQuery = window.matchMedia("(max-width: 768px)");
3262
3626
  const updateMobile = () => setIsMobile(mediaQuery.matches);
@@ -3274,7 +3638,7 @@ var FloatingChatBox = ({
3274
3638
  }
3275
3639
  };
3276
3640
  }, []);
3277
- useEffect6(() => {
3641
+ useEffect7(() => {
3278
3642
  if (typeof document === "undefined") return;
3279
3643
  if (!open || !isMobile) return;
3280
3644
  document.body.style.overflow = "hidden";
@@ -3486,22 +3850,6 @@ var FloatingChatBox = ({
3486
3850
  box-sizing: border-box;
3487
3851
  }
3488
3852
  @media (max-width: 768px) {
3489
- .zenit-chat-panel.zenit-chat-panel--fullscreen {
3490
- position: fixed !important;
3491
- left: 0 !important;
3492
- right: 0 !important;
3493
- top: 4rem !important;
3494
- bottom: 0 !important;
3495
- width: 100% !important;
3496
- max-width: 100% !important;
3497
- height: auto !important;
3498
- border-radius: 0 !important;
3499
- display: flex !important;
3500
- flex-direction: column !important;
3501
- overflow: hidden !important;
3502
- z-index: 100000 !important;
3503
- padding-top: env(safe-area-inset-top);
3504
- }
3505
3853
  .zenit-chat-panel.zenit-chat-panel--fullscreen .zenit-ai-body {
3506
3854
  flex: 1;
3507
3855
  min-height: 0;
@@ -3521,16 +3869,20 @@ var FloatingChatBox = ({
3521
3869
  "div",
3522
3870
  {
3523
3871
  ref: chatBoxRef,
3524
- className: `zenit-chat-panel${expanded ? " zenit-chat-panel--expanded" : ""}${isMobile ? " zenit-chat-panel--fullscreen" : ""}`,
3525
- style: {
3526
- ...styles.panel,
3527
- ...expanded ? styles.panelExpanded : styles.panelNormal
3528
- },
3872
+ className: `zenit-chat-panel${expanded && !isMobile ? " zenit-chat-panel--expanded" : ""}${isMobile ? " zenit-chat-panel--fullscreen" : ""}`,
3873
+ style: (() => {
3874
+ const desktopStyle = expanded ? styles.panelExpanded : styles.panelNormal;
3875
+ const mobileStyle = styles.panelMobileFullscreen;
3876
+ return {
3877
+ ...styles.panel,
3878
+ ...isMobile ? mobileStyle : desktopStyle
3879
+ };
3880
+ })(),
3529
3881
  children: [
3530
3882
  /* @__PURE__ */ jsxs6("header", { style: styles.header, children: [
3531
3883
  /* @__PURE__ */ jsx7("h3", { style: styles.title, children: "Asistente Zenit AI" }),
3532
3884
  /* @__PURE__ */ jsxs6("div", { style: styles.headerButtons, children: [
3533
- /* @__PURE__ */ jsx7(
3885
+ !isMobile && /* @__PURE__ */ jsx7(
3534
3886
  "button",
3535
3887
  {
3536
3888
  type: "button",
@@ -3604,7 +3956,7 @@ var FloatingChatBox = ({
3604
3956
  ...styles.messageBubble,
3605
3957
  ...styles.assistantMessage
3606
3958
  },
3607
- children: streamingText ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
3959
+ children: streamingText ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
3608
3960
  /* @__PURE__ */ jsx7(MarkdownRenderer, { content: streamingText }),
3609
3961
  /* @__PURE__ */ jsx7("span", { style: styles.cursor })
3610
3962
  ] }) : /* @__PURE__ */ jsxs6("div", { style: styles.thinkingText, children: [
@@ -3671,7 +4023,7 @@ var FloatingChatBox = ({
3671
4023
  },
3672
4024
  onClick: () => setOpen((prev) => !prev),
3673
4025
  "aria-label": open ? "Cerrar asistente" : "Abrir asistente Zenit AI",
3674
- children: open ? /* @__PURE__ */ jsx7(CloseIcon, {}) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
4026
+ children: open ? /* @__PURE__ */ jsx7(CloseIcon, {}) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
3675
4027
  /* @__PURE__ */ jsx7(ChatIcon, {}),
3676
4028
  !isMobile && /* @__PURE__ */ jsx7("span", { children: "Asistente IA" })
3677
4029
  ] })
@@ -3722,4 +4074,4 @@ export {
3722
4074
  useSendMessageStream,
3723
4075
  FloatingChatBox
3724
4076
  };
3725
- //# sourceMappingURL=chunk-PCTRVN4O.mjs.map
4077
+ //# sourceMappingURL=chunk-URDEEWUZ.mjs.map