@infinitech.maps/st-map 1.0.18 → 1.0.20

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.
Files changed (3) hide show
  1. package/README.md +436 -0
  2. package/dist/index.js +17898 -17457
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,436 @@
1
+ # @infinitech.maps/st-map
2
+
3
+ SDK de React para renderizar mapas interactivos de SmartTicket con selección de asientos, mesas y secciones.
4
+
5
+ ## Instalación
6
+
7
+ ```bash
8
+ npm install @infinitech.maps/st-map
9
+ ```
10
+
11
+ **Peer dependencies:** `react >=18` y `react-dom >=18`.
12
+
13
+ ## Inicio rápido
14
+
15
+ ### Modo self-contained (recomendado)
16
+
17
+ El componente carga el mapa automáticamente desde la API usando `apiKey` y `cacheKey`:
18
+
19
+ ```jsx
20
+ import { STMap } from "@infinitech.maps/st-map";
21
+
22
+ function App() {
23
+ return (
24
+ <STMap
25
+ apiKey="tu-api-key"
26
+ cacheKey="uuid-del-mapa"
27
+ onSeatClick={(seat, shape, event, meta) => {
28
+ console.log(seat.id, seat.price);
29
+ }}
30
+ loadingFallback={<p>Cargando mapa...</p>}
31
+ errorFallback={(error) => <p>Error: {error.message}</p>}
32
+ />
33
+ );
34
+ }
35
+ ```
36
+
37
+ ### Modo legacy (datos directos)
38
+
39
+ Para pasar el JSON del mapa directamente:
40
+
41
+ ```jsx
42
+ import { STMap } from "@infinitech.maps/st-map";
43
+
44
+ function App() {
45
+ return (
46
+ <STMap
47
+ map={mapData}
48
+ onSeatClick={(seat, shape, event, meta) => {
49
+ console.log(seat.id, seat.price);
50
+ }}
51
+ />
52
+ );
53
+ }
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Props
59
+
60
+ ### Modo self-contained
61
+
62
+ | Prop | Tipo | Default | Descripción |
63
+ |------|------|---------|-------------|
64
+ | `apiKey` | `string` | — | **Requerido.** API key de autenticación. |
65
+ | `cacheKey` | `string` | — | **Requerido.** UUID del mapa en caché. |
66
+ | `baseUrl` | `string` | `"http://localhost:3001"` | URL base del Gateway. |
67
+ | `loadingFallback` | `ReactNode` | `null` | Componente a mostrar mientras carga el mapa. |
68
+ | `errorFallback` | `ReactNode \| (error) => ReactNode` | `null` | Componente o función a mostrar en caso de error. |
69
+
70
+ ### Modo legacy
71
+
72
+ | Prop | Tipo | Descripción |
73
+ |------|------|-------------|
74
+ | `map` | `object` | Objeto JSON del mapa (formato v2 o legacy). |
75
+
76
+ ### Callbacks
77
+
78
+ | Prop | Firma | Descripción |
79
+ |------|-------|-------------|
80
+ | `onSeatClick` | `(seat, shape, event, meta) => void` | Clic en un asiento, silla o mesa. |
81
+ | `onSectionClick` | `(shape, event) => void` | Clic en una sección/categoría completa. |
82
+ | `onEmptyAreaClick` | `() => void` | Clic en el área vacía del canvas. |
83
+ | `onReady` | `() => void` | El mapa terminó de renderizar. |
84
+
85
+ ### Personalización visual
86
+
87
+ | Prop | Tipo | Default | Descripción |
88
+ |------|------|---------|-------------|
89
+ | `prices` | `Record<string, number>` | — | Mapa de precios por UUID (ver [Resolución de precios](#resolución-jerárquica-de-precios)). |
90
+ | `selectedSeats` | `string[]` | — | Lista de UUIDs seleccionados (modo controlado). |
91
+ | `reservedSeats` | `string[]` | — | Lista de UUIDs de asientos reservados. |
92
+ | `selectedSeatColor` | `string` | `"#01FFFF"` | Color de asientos seleccionados. |
93
+ | `reservedSeatColor` | `string` | `"#FF0000"` | Color de asientos reservados. |
94
+ | `purchasedSeatColor` | `string` | `"#000000"` | Color de asientos comprados. |
95
+ | `hoverColor` | `string` | `"#444"` | Color al pasar el cursor. |
96
+ | `hoverTransition` | `number` | `0` | Duración de la transición hover en ms. |
97
+
98
+ ---
99
+
100
+ ## Callbacks
101
+
102
+ ### `onSeatClick(seat, shape, event, meta)`
103
+
104
+ Se invoca al hacer clic en cualquier elemento reservable (asiento, silla o mesa).
105
+
106
+ | Argumento | Descripción |
107
+ |-----------|-------------|
108
+ | `seat` | Objeto del asiento/mesa seleccionado. Contiene `id` (UUID), `price`, `status`, etc. |
109
+ | `shape` | Objeto de la forma padre (fila, mesa, sector). Contiene `category`, `section`, `sectorName`, etc. |
110
+ | `event` | Evento de Konva (canvas). |
111
+ | `meta` | Metadatos adicionales. Contiene `screenPosition` para posicionar popups. |
112
+
113
+ #### Propiedades de `seat`
114
+
115
+ | Propiedad | Tipo | Descripción |
116
+ |-----------|------|-------------|
117
+ | `seat.id` | `string` | **UUID** único del elemento seleccionado. |
118
+ | `seat.price` | `number \| undefined` | **Precio** resuelto. Presente solo si se pasó `prices` y existe un precio. |
119
+ | `seat.name` | `string` | Nombre visible del asiento (ej. `"A01"`). |
120
+ | `seat.row` | `string \| number` | Fila del asiento. |
121
+ | `seat.column` | `number` | Columna del asiento. |
122
+ | `seat.status` | `string` | Estado: `"vacant"`, `"reserved"`, `"purchased"`. |
123
+ | `seat.type` | `string` | Tipo de elemento: `"seat"` o `"table"`. |
124
+
125
+ ##### Propiedades exclusivas de mesas (`seat.type === "table"`)
126
+
127
+ | Propiedad | Tipo | Descripción |
128
+ |-----------|------|-------------|
129
+ | `seat.isTable` | `boolean` | `true` cuando es una mesa. |
130
+ | `seat.tableName` | `string` | Nombre de la mesa. |
131
+ | `seat.chairs` | `array` | Lista de sillas de la mesa. |
132
+ | `seat.totalChairs` | `number` | Total de sillas en la mesa. |
133
+ | `seat.vacantCount` | `number` | Cantidad de sillas vacantes. |
134
+ | `seat.reservedCount` | `number` | Cantidad de sillas reservadas. |
135
+ | `seat.purchasedCount` | `number` | Cantidad de sillas compradas. |
136
+ | `seat.available` | `number` | Alias de `vacantCount`. |
137
+
138
+ #### Propiedad `meta.screenPosition`
139
+
140
+ Contiene las coordenadas CSS del centro del elemento clicado, útil para posicionar tooltips o popups:
141
+
142
+ ```jsx
143
+ const handleSeatClick = (seat, shape, event, meta) => {
144
+ // Posicionar un popup sobre el asiento
145
+ setPopupPosition({
146
+ x: meta.screenPosition.x,
147
+ y: meta.screenPosition.y,
148
+ });
149
+ };
150
+ ```
151
+
152
+ ### Ejemplo completo: obtener UUID y precio
153
+
154
+ ```jsx
155
+ import { STMap } from "@infinitech.maps/st-map";
156
+
157
+ function MapView() {
158
+ const prices = {
159
+ "d5195a0a-2e6f-4167-a930-b26440d1e072": 150, // Categoría "VIP"
160
+ "702fdfc0-3a10-48a5-861c-0ddddac1bf7f": 50, // Categoría "General"
161
+ "58a5497e-eb75-445a-a89c-dc0fbe652d38": 500, // Asiento específico
162
+ };
163
+
164
+ const handleSeatClick = (seat, shape, event, meta) => {
165
+ console.log("UUID:", seat.id);
166
+ console.log("Precio:", seat.price ?? "Sin precio asignado");
167
+
168
+ if (seat.type === "table") {
169
+ console.log(`Mesa "${seat.tableName}" — ${seat.vacantCount}/${seat.totalChairs} disponibles`);
170
+ } else {
171
+ console.log(`Asiento ${seat.name}, fila ${seat.row}`);
172
+ }
173
+ };
174
+
175
+ return (
176
+ <STMap
177
+ apiKey="tu-api-key"
178
+ cacheKey="uuid-del-mapa"
179
+ prices={prices}
180
+ onSeatClick={handleSeatClick}
181
+ />
182
+ );
183
+ }
184
+ ```
185
+
186
+ ### `onSectionClick(shape, event)`
187
+
188
+ Se invoca al hacer clic en una sección/categoría completa (formas geométricas sin asientos individuales).
189
+
190
+ | Propiedad | Tipo | Descripción |
191
+ |-----------|------|-------------|
192
+ | `shape.id` | `string` | **UUID** de la sección. |
193
+ | `shape.price` | `number \| undefined` | **Precio** resuelto (si existe). |
194
+ | `shape.category` | `string` | ID de la categoría. |
195
+ | `shape.categoryName` | `string` | Nombre de la categoría. |
196
+ | `shape.section` | `string` | ID del sector. |
197
+ | `shape.sectorName` | `string` | Nombre del sector. |
198
+
199
+ ```jsx
200
+ <STMap
201
+ apiKey="tu-api-key"
202
+ cacheKey="uuid-del-mapa"
203
+ prices={prices}
204
+ onSeatClick={handleSeatClick}
205
+ onSectionClick={(shape, event) => {
206
+ console.log("UUID sección:", shape.id);
207
+ console.log("Precio:", shape.price ?? "Sin precio");
208
+ }}
209
+ />
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Resolución jerárquica de precios
215
+
216
+ Cuando se pasa la prop `prices`, el SDK resuelve el precio de forma jerárquica buscando en este orden:
217
+
218
+ 1. **Asiento individual** → `prices[seat.id]`
219
+ 2. **Sector** → `prices[shape.section]`
220
+ 3. **Categoría** → `prices[shape.category]`
221
+
222
+ El primer match encontrado se asigna como `seat.price` (o `shape.price` en secciones).
223
+
224
+ Si se proporcionan `prices` pero no se encuentra precio en ningún nivel, el SDK muestra una alerta y **no permite la selección** del elemento.
225
+
226
+ Si **no** se pasa la prop `prices`, no hay validación de precio y todos los elementos son seleccionables.
227
+
228
+ ---
229
+
230
+ ## Métodos imperativos (ref)
231
+
232
+ Usa `ref` para acceder a métodos imperativos del componente:
233
+
234
+ ```jsx
235
+ import { useRef } from "react";
236
+ import { STMap } from "@infinitech.maps/st-map";
237
+
238
+ function App() {
239
+ const mapRef = useRef();
240
+
241
+ return <STMap ref={mapRef} apiKey="tu-api-key" cacheKey="uuid-del-mapa" />;
242
+ }
243
+ ```
244
+
245
+ ### `resolveReservationIds(selections)`
246
+
247
+ Convierte los elementos clicados en IDs de asientos/sillas individuales listos para la API. Para mesas, extrae los IDs de las sillas vacantes; para asientos, devuelve `[id]`.
248
+
249
+ ```jsx
250
+ const seatIds = mapRef.current.resolveReservationIds([
251
+ { id: "uuid-mesa", quantity: 3 }, // extrae 3 sillas vacantes
252
+ { id: "uuid-asiento" }, // devuelve [uuid-asiento]
253
+ ]);
254
+ // → ["silla-1", "silla-2", "silla-3", "uuid-asiento"]
255
+ ```
256
+
257
+ | Parámetro | Tipo | Descripción |
258
+ |-----------|------|-------------|
259
+ | `selections` | `Array<{ id: string, quantity?: number }>` | Elementos a resolver. `quantity` solo aplica a mesas. |
260
+
261
+ ### `reserveSelected({ selections, ttlSeconds })`
262
+
263
+ Resuelve IDs y reserva en una sola llamada. Requiere `apiKey` y `cacheKey`.
264
+
265
+ ```jsx
266
+ const { seatIds } = await mapRef.current.reserveSelected({
267
+ selections: [{ id: "uuid-mesa", quantity: 2 }],
268
+ ttlSeconds: 300, // 5 minutos (default)
269
+ });
270
+ ```
271
+
272
+ ### `releaseAll({ seatIds })`
273
+
274
+ Libera asientos (vuelven a estado `"vacant"`). Requiere `apiKey` y `cacheKey`.
275
+
276
+ ```jsx
277
+ await mapRef.current.releaseAll({
278
+ seatIds: ["silla-1", "silla-2"],
279
+ });
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Selección controlada
285
+
286
+ Por defecto el SDK gestiona la selección internamente. Para controlarla externamente, pasa `selectedSeats`:
287
+
288
+ ```jsx
289
+ const [selected, setSelected] = useState([]);
290
+
291
+ const handleSeatClick = (seat) => {
292
+ setSelected((prev) =>
293
+ prev.includes(seat.id)
294
+ ? prev.filter((id) => id !== seat.id)
295
+ : [...prev, seat.id]
296
+ );
297
+ };
298
+
299
+ <STMap
300
+ apiKey="tu-api-key"
301
+ cacheKey="uuid-del-mapa"
302
+ selectedSeats={selected}
303
+ onSeatClick={handleSeatClick}
304
+ />
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Console log integrado
310
+
311
+ El SDK incluye un log automático en la consola del navegador cada vez que se hace clic en un elemento reservable:
312
+
313
+ ```js
314
+ // Asientos/mesas
315
+ [ST-Map] Seat click {
316
+ uuid: "58a5497e-eb75-445a-a89c-dc0fbe652d38",
317
+ price: 500,
318
+ row: "A",
319
+ column: 1,
320
+ category: "VIP",
321
+ section: "Sector 1"
322
+ }
323
+
324
+ // Secciones
325
+ [ST-Map] Section click {
326
+ uuid: "3b1633c9-57d0-4438-b952-95c213f98e29",
327
+ price: 200,
328
+ category: "General",
329
+ section: "Sector 2"
330
+ }
331
+ ```
332
+
333
+ Si un campo no existe (ej. fila/columna en mesas), se muestra `"N/A"`. Sin precio se muestra `"sin precio"`.
334
+
335
+ ---
336
+
337
+ ## STMapClient
338
+
339
+ Cliente HTTP para interactuar con la API de gestión de asientos. Funciona de forma independiente, sin necesidad del componente React.
340
+
341
+ ```js
342
+ import { STMapClient } from "@infinitech.maps/st-map";
343
+
344
+ const client = new STMapClient({ apiKey: "tu-api-key" });
345
+
346
+ // O con URL personalizada:
347
+ const client = new STMapClient({
348
+ apiKey: "tu-api-key",
349
+ baseUrl: "https://gateway.example.com",
350
+ });
351
+ ```
352
+
353
+ ### `getMap(cacheKey, options?)`
354
+
355
+ Carga el mapa desde la API.
356
+
357
+ ```js
358
+ const map = await client.getMap("uuid-del-mapa");
359
+ // Con versión específica:
360
+ const map = await client.getMap("uuid-del-mapa", { version: "v1" });
361
+ ```
362
+
363
+ ### `reserve({ cacheKey, seatIds, ttlSeconds?, version? })`
364
+
365
+ Reserva asientos. Estado → `"reserved"` con expiración.
366
+
367
+ ```js
368
+ const reserved = await client.reserve({
369
+ cacheKey: "uuid-del-mapa",
370
+ seatIds: ["seat-1", "seat-2"],
371
+ ttlSeconds: 300, // 5 minutos (default)
372
+ });
373
+ // → [{ id: "seat-1", status: "reserved", expiredAt: "..." }, ...]
374
+ ```
375
+
376
+ ### `purchase({ cacheKey, seatIds, version? })`
377
+
378
+ Compra asientos. Estado → `"purchased"` (permanente). Funciona sobre asientos vacantes o previamente reservados.
379
+
380
+ ```js
381
+ const purchased = await client.purchase({
382
+ cacheKey: "uuid-del-mapa",
383
+ seatIds: ["seat-1"],
384
+ });
385
+ // → [{ id: "seat-1", status: "purchased" }]
386
+ ```
387
+
388
+ ### `release({ cacheKey, seatIds, version? })`
389
+
390
+ Libera asientos. Estado → `"vacant"`. Idempotente: liberar un asiento ya vacante no genera error.
391
+
392
+ ```js
393
+ await client.release({
394
+ cacheKey: "uuid-del-mapa",
395
+ seatIds: ["seat-2"],
396
+ });
397
+ // → [{ id: "seat-2", status: "vacant" }]
398
+ ```
399
+
400
+ ### `getSeatStatus({ cacheKey, seatIds?, version? })`
401
+
402
+ Consulta el estado de asientos. Omitir `seatIds` devuelve **todos** los asientos del mapa.
403
+
404
+ ```js
405
+ // Estado de asientos específicos
406
+ const status = await client.getSeatStatus({
407
+ cacheKey: "uuid-del-mapa",
408
+ seatIds: ["seat-1", "seat-2"],
409
+ });
410
+ // → [{ id: "seat-1", status: "reserved", expiredAt: "..." }, ...]
411
+
412
+ // Estado de todos los asientos
413
+ const allStatus = await client.getSeatStatus({
414
+ cacheKey: "uuid-del-mapa",
415
+ });
416
+ ```
417
+
418
+ ### Métodos estáticos
419
+
420
+ #### `STMapClient.resolveReservationIds(element, quantity?)`
421
+
422
+ Resuelve los IDs individuales de un elemento devuelto por `onSeatClick`. Para mesas, extrae los IDs de las sillas vacantes; para asientos, devuelve `[id]`.
423
+
424
+ ```js
425
+ const ids = STMapClient.resolveReservationIds(clickedElement, 3);
426
+ await client.reserve({ cacheKey: "uuid-del-mapa", seatIds: ids });
427
+ ```
428
+
429
+ #### `STMapClient.vacantCount(element)`
430
+
431
+ Devuelve la cantidad de asientos vacantes para un elemento. Asientos individuales devuelven `0` o `1`; mesas devuelven la cantidad de sillas vacantes.
432
+
433
+ ```js
434
+ const available = STMapClient.vacantCount(clickedElement);
435
+ console.log(`${available} lugares disponibles`);
436
+ ```