@fundaciongenesisempresarial/zenit-sdk
v1.0.5
Published
TypeScript SDK for interacting with the Zenit backend APIs.
Readme
zenit-sdk
Introducción
zenit-sdk es una librería en TypeScript para consumir el backend de Zenit de forma sencilla. El core es agnóstico al framework y se enfoca en ofrecer clientes HTTP y helpers de autenticación. Además, incluye un componente React (ZenitMap) para integraciones de UI basado en Leaflet.
Instalación
npm install zenit-sdk
# o con yarn
yarn add zenit-sdkPara usar el componente React instala también sus peer dependencies en tu proyecto:
npm install react react-dom leaflet react-leaflet
# o con yarn
yarn add react react-dom leaflet react-leafletEntradas del SDK
- Core (Node/HTTP):
import { ZenitClient } from 'zenit-sdk'; - React (UI):
import { ZenitMap } from 'zenit-sdk/react';
Uso básico – Cliente de usuario (core)
import { ZenitClient } from 'zenit-sdk';
const client = new ZenitClient({
baseUrl: 'https://mi-zenit.com/api/v1'
});
async function demo() {
const login = await client.auth.login({
email: '<EMAIL>',
password: '<PASSWORD>'
});
console.log('User:', login.user);
const me = await client.auth.me();
console.log('Me:', me);
const valid = await client.auth.validate();
console.log('Validate:', valid);
const refreshed = await client.auth.refresh(login.refreshToken);
console.log('Refresh:', refreshed);
}Uso básico – SDK Token (core)
import { ZenitClient } from 'zenit-sdk';
const client = new ZenitClient({
baseUrl: 'https://mi-zenit.com/api/v1',
sdkToken: '<SDK_TOKEN>'
});
async function demoSdk() {
const validation = await client.sdkAuth.validateSdkToken();
console.log('SDK token validation:', validation);
const exchange = await client.sdkAuth.exchangeSdkToken();
console.log('SDK exchange:', exchange);
const me = await client.auth.me();
console.log('Me using SDK access token:', me);
}Runtime config (WebView / host) + filtros
El cliente ahora permite actualizar configuración y tokens en runtime sin recrear la app:
import {
ZenitClient,
mergeFilters,
normalizeFilters,
resolveRuntimeConfig,
} from 'zenit-sdk';
const runtime = resolveRuntimeConfig(window.__ZENIT_RUNTIME_CONFIG__);
const client = new ZenitClient({
baseUrl: runtime?.baseUrl ?? 'https://mi-zenit.com/api/v1',
accessToken: runtime?.accessToken,
sdkToken: runtime?.sdkToken,
});
client.setBaseUrl('https://mi-zenit.com/api/v2');
client.setAccessToken('token-2');
client.setSdkToken('sdk-2');
client.setAuth({ accessToken: 'token-3', sdkToken: 'sdk-3' });
client.setDefaultFilters(normalizeFilters({ CODREGION: "10" }));
const filters = mergeFilters(client.getDefaultFilters(), { CODDEPTO: 7 });
console.log('filters', filters);Todos los requests construyen Authorization dinámicamente, así que un setAccessToken() afecta a la siguiente
llamada sin reiniciar el cliente.
Uso básico – Componente React ZenitMap
import React from 'react';
import { ZenitClient } from 'zenit-sdk';
import { ZenitMap } from 'zenit-sdk/react';
import 'leaflet/dist/leaflet.css';
const client = new ZenitClient({
baseUrl: 'https://mi-zenit.com/api/v1',
sdkToken: '<SDK_TOKEN>'
});
export function App() {
return (
<div>
<h1>Demo ZenitMap</h1>
<ZenitMap client={client} mapId={123} />
</div>
);
}Si las capas del mapa incluyen layer.label, ZenitMap mostrará marcadores de etiqueta usando esa propiedad de las
features (respeta visibilidad y opacidad de la capa).
Ubicación del usuario en tiempo real (locationControl)
ZenitMap puede mostrar dos botones (Leaflet control) para que el usuario vea y siga su posición:
<ZenitMap
client={client}
mapId={123}
locationControl={{ position: 'bottomleft', zoom: 16 }}
/>- Botón "Mi ubicación": usa
navigator.geolocation.watchPositiony centra el mapa una vez sobre la posición. - Botón "Seguir" (icono flecha): activa modo seguimiento — el mapa se recentra automáticamente en cada actualización de GPS. Se desactiva solo cuando el usuario hace pan/zoom manual.
Modo silencioso (auto-follow sin UI)
Si solo quieres que el mapa siga al usuario en tiempo real sin botones visibles:
<ZenitMap
client={client}
mapId={123}
locationControl={{ autoFollow: true, hideControls: true, zoom: 16 }}
/>En este modo el SDK arranca watchPosition al montar el mapa, activa follow continuo y no muestra ningún botón. Si la geolocalización falla (permiso denegado y fallback IP inalcanzable) no se muestra nada y la app sigue funcionando.
Si el WebView no concede permiso GPS o el dispositivo no devuelve fix, el SDK degrada automáticamente a geolocalización por IP (precisión aproximada, ~ciudad) — en modo con controles se muestra el tooltip "Ubicación aproximada (IP)" y un círculo naranja.
También puedes consumirlo a más bajo nivel con el hook useGeolocation o el componente LocationControl directamente (zenit-sdk/react).
Configuración mínima del host (opcional, para precisión GPS real)
El feature funciona "transparente" con fallback IP sin tocar la app Flutter. Para obtener precisión GPS real dentro del WebView:
- iOS (WKWebView): agrega
NSLocationWhenInUseUsageDescriptionenInfo.plist(configuración única del proyecto). - Android (
webview_flutter): agregaACCESS_FINE_LOCATIONenAndroidManifest.xmle implementa el callbackonGeolocationPermissionsShowPrompten el WebChromeClient para conceder el permiso. (flutter_inappwebviewexpone este hook de forma directa.)
Sin esta configuración el SDK no falla: el usuario simplemente verá su ubicación aproximada vía IP.
Chat flotante FloatingChatBox (Zenit AI)
El SDK incluye un chat flotante para conversar con Zenit AI usando los endpoints de mapas. Solo necesitas entregar
baseUrl y mapId (el token es opcional, pero se puede inyectar con accessToken o getAccessToken).
import React from 'react';
import { FloatingChatBox } from 'zenit-sdk/react';
export function App() {
return (
<>
<FloatingChatBox
baseUrl="https://mi-zenit.com/api/v1"
mapId={123}
filteredLayerIds={[45, 98]}
filters={{ CODREGION: 10 }}
getAccessToken={() => localStorage.getItem('access_token') ?? ''}
/>
</>
);
}El chat funciona también en modo guest (userId nulo/omitido). Si no hay mapId, el componente muestra un estado
deshabilitado con el mensaje “Selecciona un mapa para usar el asistente”.
Panel reutilizable de capas y filtros ZenitLayerManager
El SDK incluye un panel listo para usar que administra visibilidad, opacidad y filtros por propiedades usando los endpoints getLayerFeaturesCatalog y filter-multiple. Puedes combinarlo con ZenitMap para mostrar el GeoJSON filtrado como overlay:
import React, { useState } from 'react';
import { ZenitClient, type FilterMultipleMetadata, type GeoJsonFeatureCollection } from 'zenit-sdk';
import { ZenitLayerManager, ZenitMap } from 'zenit-sdk/react';
import 'leaflet/dist/leaflet.css';
const client = new ZenitClient({ baseUrl: 'https://mi-zenit.com/api/v1', sdkToken: '<SDK_TOKEN>' });
export function App() {
const [overlay, setOverlay] = useState<GeoJsonFeatureCollection | null>(null);
const [metadata, setMetadata] = useState<FilterMultipleMetadata | undefined>();
const [layerControls, setLayerControls] = useState<
Array<{ layerId: number | string; visible: boolean; opacity: number }>
>([]);
return (
<div style={{ display: 'flex', height: 600 }}>
<ZenitLayerManager
client={client}
mapId={11}
onFilteredGeojson={(geojson, meta) => {
setOverlay(geojson);
setMetadata(meta);
}}
onLayerStatesChange={setLayerControls}
/>
<ZenitMap
client={client}
mapId={11}
showLayerPanel={false}
overlayGeojson={overlay}
layerControls={layerControls}
/>
<pre>{JSON.stringify(metadata, null, 2)}</pre>
</div>
);
}API de mapas y capas (core)
import { ZenitClient, getCatalogSupport, ZenitCatalogNotSupportedError } from 'zenit-sdk';
const client = new ZenitClient({ baseUrl: 'https://mi-zenit.com/api/v1', accessToken: '<JWT>' });
// Metadatos de mapa (incluye capas visibles si includeLayers=true)
const map = await client.maps.getMap(11, true);
// Metadatos de capa (las respuestas usan ApiResponse con `data` y metadata opcional)
const layer = await client.layers.getLayer(123);
console.log('Layer:', layer.data);
// GeoJSON completo
const geojson = await client.layers.getLayerGeoJson(123);
console.log('GeoJSON features:', geojson.data.features?.length ?? 0);
// GeoJSON limitado por bounding box
const geojsonBBox = await client.layers.getLayerGeoJsonBBox({
id: 123,
bbox: {
minLon: -58.6,
minLat: -34.7,
maxLon: -58.3,
maxLat: -34.4,
},
});
console.log('GeoJSON bbox features:', geojsonBBox.data.features?.length ?? 0);
// Intersección con una geometría
const intersected = await client.layers.getLayerGeoJsonIntersect({ id: 123, geometry, maxFeatures: 5000 });
console.log('GeoJSON intersected features:', intersected.data.features?.length ?? 0);
// Catálogo de propiedades de features de una capa
const catalog = await client.layers.getLayerFeaturesCatalog(17);
console.log('Catalogo de capa', catalog.data);
// Catálogo con validación previa
const support = getCatalogSupport({ layerType: 'polygon' });
if (support.supported) {
const safeCatalog = await client.layers.getLayerFeaturesCatalog(17, {
layerType: 'polygon',
strict: true, // fail-fast sin request si la geometría no está soportada
});
console.log('Catálogo soportado', safeCatalog.data);
}
// Filtrado simultáneo en múltiples capas multipolygon
const filtered = await client.layers.filterMultipleLayersFeatures({
layerIds: [32, 17],
filters: { CODREGION: 10 },
});
console.log('filter-multiple data', filtered.data);
// Filtrado resiliente con capas mixtas (multipolygon + otras)
const mixedFiltered = await client.layers.filterMultipleWithFallback({
layerIds: [32, 17, 99],
filters: { CODREGION: 10 },
layerMetas: [
{ layerId: 32, layerType: 'multipolygon' },
{ layerId: 17, layerType: 'polygon' },
{ layerId: 99, layerType: 'mixed' },
],
});
console.log('filter-multiple fallback perLayer', mixedFiltered.perLayer);
// Helper de alto nivel
const loaded = await client.layers.loadFilteredFeatures({
layerIds: [32, 17],
filters: { CODREGION: 10 },
});
console.log('GeoJSON filtrado', loaded.geojson.features.length, 'elementos');El endpoint de catálogo solo admite capas polygon/multipolygon. Usa getCatalogSupport para decidir si corresponde
llamarlo, o pasa { strict: true } a getLayerFeaturesCatalog para que el SDK arroje un ZenitCatalogNotSupportedError
antes de hacer la request.
Los métodos expuestos en client.layers permiten construir el panel de filtros sin dependencias externas:
getLayerFeaturesCatalog(layerId)carga el catálogo de propiedades para cada capa.filterMultipleLayersFeatures({ layerIds, filters })ejecutafilter-multipley retorna GeoJSON + metadata.loadFilteredFeatureses un helper tipado que entregageojson,metadataytotalFeatureslistos para usar.
Notas de filtrado:
layerIdsse envía como CSV ([32,17]->layerIds=32,17).- Los filtros son pares dinámicos
key/value. Valores en arreglos se serializan comoa,b,c. - Valores
null/undefinedo cadenas vacías no se envían en la query.
Ejecutar ejemplos con .env opcional
Los ejemplos usan ts-node y leen variables de entorno. Puedes definirlas en tu shell o en un archivo .env (no se publica):
ZENIT_BASE_URL=http://localhost:3200/api/v1
ZENIT_EMAIL=<EMAIL>
ZENIT_PASSWORD=<PASSWORD>
ZENIT_SDK_TOKEN=<SDK_TOKEN>
ZENIT_ACCESS_TOKEN=<ACCESS_TOKEN>
ZENIT_MAP_ID=11En PowerShell:
$env:ZENIT_BASE_URL="http://localhost:3200/api/v1"
$env:ZENIT_EMAIL="[email protected]"
$env:ZENIT_PASSWORD="secret"Comandos disponibles:
npm run example:auth
npm run example:sdk
npm run example:mapEl baseUrl por defecto de los ejemplos es http://localhost:3200/api/v1 si no se especifica.
