venuekit 0.0.0-stage → 0.10.0
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/LICENSE +21 -0
- package/README.md +325 -2
- package/dist/chunk-47ILVPKZ.cjs +1311 -0
- package/dist/chunk-5H5QKXPV.js +1311 -0
- package/dist/index.cjs +180 -0
- package/dist/index.d.cts +256 -0
- package/dist/index.d.ts +256 -0
- package/dist/index.js +180 -0
- package/dist/place-types-CeTjPHTF.d.cts +670 -0
- package/dist/place-types-CeTjPHTF.d.ts +670 -0
- package/dist/react.cjs +5966 -0
- package/dist/react.d.cts +584 -0
- package/dist/react.d.ts +584 -0
- package/dist/react.js +5966 -0
- package/package.json +88 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Juan Cruz Fernández
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,326 @@
|
|
|
1
|
-
#
|
|
1
|
+
# VenueKit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Editor y mapa de butacas para venta de entradas: butacas numeradas, mesas y lugares en el
|
|
4
|
+
mismo plano. Se instala como **un solo paquete**, `venuekit`, con dos entradas:
|
|
5
|
+
|
|
6
|
+
- `venuekit` — el núcleo: el plano (Layout v2), su validación, la expansión a unidades
|
|
7
|
+
vendibles, el candado y los tipos de lugar. **No toca el DOM ni React**: es lo que importa
|
|
8
|
+
el servidor.
|
|
9
|
+
- `venuekit/react` — `<VenueDesigner />` (el editor del productor) y `<SeatMap />` (el mapa
|
|
10
|
+
del comprador). Abre con `"use client"`.
|
|
11
|
+
|
|
12
|
+
Los dos comparten un único chunk con el núcleo, así que `instanceof LayoutError` y las
|
|
13
|
+
constantes valen entre una entrada y otra.
|
|
14
|
+
|
|
15
|
+
## Instalar
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install venuekit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Peers: `react` y `react-dom` ≥ 18 (sólo para `venuekit/react`; el núcleo no los necesita). Dependencia: `zod` ^4.
|
|
22
|
+
|
|
23
|
+
Para probar un cambio sin publicar: `npm run pack:local` deja `dist-packs/venuekit-<versión>.tgz`, que el host instala con `npm i <ruta al .tgz>` (detalle en `docs/consumo-local.md`).
|
|
24
|
+
|
|
25
|
+
## `venuekit` (servidor y cliente, sin DOM)
|
|
26
|
+
|
|
27
|
+
Un plano (Layout v2) es `{ meta, elements }`. `meta` lleva `id`, `name`, `version`, `width`,
|
|
28
|
+
`height` y la imagen de fondo (`background`, o `null`). `elements` son bloques de butacas
|
|
29
|
+
(`seatBlock`), mesas (`table`) y referencias del lugar (`landmark`: escenario, baños, barra…).
|
|
30
|
+
Cada bloque o mesa puede llevar un tipo de lugar (`placeType: { ref, name }`).
|
|
31
|
+
|
|
32
|
+
Hay dos formas de "sin asignar", y las dos significan lo mismo para vender: `placeType: null`
|
|
33
|
+
(nunca tuvo tipo) y `{ ref: "", name: "VIP" }` (no está atada a ningún tipo, pero recuerda
|
|
34
|
+
cómo se llamaba el que tenía, para re-enlazarla por nombre). No compares `placeType` a mano:
|
|
35
|
+
leé `expand(layout)[i].placeTypeRef`, que vale `null` en los dos casos.
|
|
36
|
+
|
|
37
|
+
El plano llega de afuera, así que el esquema tiene topes (exportados como constantes):
|
|
38
|
+
`MAX_UNITS` unidades vendibles, `MAX_ELEMENTS` piezas, ids de hasta `MAX_ID_LENGTH`,
|
|
39
|
+
nombres hasta `MAX_NAME_LENGTH`, `ref` hasta `MAX_REF_LENGTH`, número de mesa hasta
|
|
40
|
+
`MAX_TABLE_LABEL_LENGTH`, `background.src` (una URL) hasta `MAX_SRC_LENGTH`; la primera fila
|
|
41
|
+
con letras va de 1 a `MAX_LETTERS_START` letras (con números, hasta 9 dígitos) y `skip`
|
|
42
|
+
tiene hasta `MAX_SKIP` entradas. Un plano fuera de esos topes no pasa `migrate`.
|
|
43
|
+
|
|
44
|
+
Numeración de un bloque (`rowLabels` / `seatLabels`, campos opcionales nuevos en 0.10; un
|
|
45
|
+
plano viejo sin ellos vale igual que antes): `pattern` (sólo con números) es `"sequential"`
|
|
46
|
+
(1, 2, 3…, el default), `"odd"` (1, 3, 5…), `"even"` (2, 4, 6…) o `"center"` (desde el
|
|
47
|
+
centro). Con impares o pares se arranca en el primero de esa clase que no sea menor que
|
|
48
|
+
`start`. «Desde el centro»: la primera va en el medio y las siguientes alternan alejándose
|
|
49
|
+
(la 2.ª a la izquierda, la 3.ª a la derecha…); con una fila par, la primera va en la del
|
|
50
|
+
medio de la derecha y la segunda en la de la izquierda. Empezando en 1: `6 4 2 1 3 5 7`
|
|
51
|
+
(fila impar) y `6 4 2 1 3 5` (par); `direction: "rtl"` lo espeja (impares a la izquierda).
|
|
52
|
+
`skip` saca números o letras de la secuencia en todos los patrones. `alphabet: "es"` (sólo
|
|
53
|
+
con letras) suma la Ñ después de la N; sin eso no hay Ñ, como siempre. Después de la Z sigue
|
|
54
|
+
AA, AB…
|
|
55
|
+
|
|
56
|
+
| Export | Para qué |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `layoutSchema` | Esquema zod del plano v2. |
|
|
59
|
+
| `migrate(input)` | La puerta de entrada de un plano de afuera: lo valida (esquema y topes) y lo devuelve como Layout v2. Sólo acepta v2: la v1 nunca se publicó y da `LayoutError`, igual que cualquier plano que no sirva. |
|
|
60
|
+
| `CURRENT_VERSION` | Versión actual del plano (hoy `2`). |
|
|
61
|
+
| `expand(layout)` | Las unidades vendibles con sus IDs: butaca `"<bloque>:<fila>:<butaca>"`, lugar de mesa `"<mesa>:<n>"`, mesa entera = id de la mesa. Mover o girar no cambia IDs. |
|
|
62
|
+
| `findLockViolations(antes, despues, bloqueados)` | Qué unidades vendidas o retenidas desaparecerían, se renombrarían o cambiarían de lugar físico con el cambio (desde 0.10 también una renumeración que corre los nombres: empezar en B, saltear, invertir el sentido, cambiar el patrón). Mover, girar, escalar o curvar la pieza entera se puede. Para validar en el servidor lo mismo que el editor rechaza. |
|
|
63
|
+
| `summarize(layout)` | Conteos del plano: butacas, mesas y lugares, en total y por tipo de lugar. Por tipo trae también `duplicates: { seats, tables }`: butacas (`"fila:butaca"`, p. ej. `"A:1"`) y números de mesa repetidos adentro de ese tipo. Con algo ahí, el host no debería dejar publicar. |
|
|
64
|
+
| `rebindPlaceTypes(layout, opciones)` | Re-enlaza un plano guardado a los tipos de lugar del evento actual, por nombre. Lo que no encuentra queda sin asignar. |
|
|
65
|
+
| `canAssign(pieza, opcion)` | Si una pieza admite ese tipo de lugar. |
|
|
66
|
+
| `PlaceTypeOption` | `{ ref, label, color, accepts: "seats" \| "tableWhole" \| "tablePerPlace" \| "zone", capacitySteps? }`. `capacitySteps`: los cupos acumulados de los lotes, en orden de venta (lotes de 30, 50 y 100 → `[30, 80, 180]`); sin eso, sin tope. `"zone"`: una entrada general, que se marca como zona y no pone lugares. |
|
|
67
|
+
| `placeTypeCapacity` / `nextBlockUnits` / `capacityViolations` | El tope de un tipo (el mayor cupo), cuántas butacas trae el próximo bloque (de a tramos: `[30, 80, 180]` → bloques de 30, 50 y 100) y qué tipos pasa un cambio de su cupo. Para validar en el servidor lo mismo que el editor rechaza. |
|
|
68
|
+
| `landmarks` | Catálogo de referencias del lugar. |
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { migrate, expand, findLockViolations, rebindPlaceTypes } from "venuekit"
|
|
72
|
+
|
|
73
|
+
const layout = rebindPlaceTypes(migrate(JSON.parse(saved)), placeTypeOptions)
|
|
74
|
+
const units = expand(layout) // validar ids contra el plano
|
|
75
|
+
const broken = findLockViolations(current, next, soldOrHeldIds) // [] si no rompe nada vendido
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## `venuekit/react`
|
|
79
|
+
|
|
80
|
+
### `<VenueDesigner />`
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { migrate, rebindPlaceTypes } from "venuekit"
|
|
84
|
+
import { VenueDesigner, designerEs } from "venuekit/react"
|
|
85
|
+
|
|
86
|
+
// Un objeto nuevo en `layout` es otro plano: el editor lo carga y lo vuelve a encuadrar.
|
|
87
|
+
// Si el host lo arma en cada render (aunque sea igual), memorizalo.
|
|
88
|
+
const saved = useMemo(() => rebindPlaceTypes(migrate(raw), placeTypes), [raw, placeTypes])
|
|
89
|
+
|
|
90
|
+
<div style={{ height: "100dvh" }}>
|
|
91
|
+
<VenueDesigner
|
|
92
|
+
layout={saved} // Layout | null (null = plano en blanco con `name`)
|
|
93
|
+
placeTypes={placeTypes} // PlaceTypeOption[]: las entradas numeradas del evento
|
|
94
|
+
lockedUnitIds={locked} // ReadonlySet<string>: vendidas y retenidas por cualquiera
|
|
95
|
+
onChange={setDraft}
|
|
96
|
+
onDone={async (l) => { await save(l); close() }} // «Listo»
|
|
97
|
+
onClose={close} // «Volver» (opcional)
|
|
98
|
+
theme="dark" // "light" (por defecto) | "dark"
|
|
99
|
+
background={{ onPick: uploadImage, suggestedSrc: event.flyerUrl }}
|
|
100
|
+
messages={designerEs} // opcional: mergeDesignerMessages / DesignerOverrides
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
El editor va guiado por las entradas: a la izquierda «Tus entradas numeradas» muestra una
|
|
106
|
+
tarjeta por cada `placeTypes` con lo que ya tiene en el plano («40 butacas», «3 mesas»,
|
|
107
|
+
«24 lugares») o «Falta ubicar», y los botones para agregarle piezas (bloque de butacas; mesa
|
|
108
|
+
redonda o cuadrada; mesa con sillas). Cada pieza nace **ya asignada** a esa entrada, en el
|
|
109
|
+
centro de lo que se ve y corrida si ahí ya hay algo. La pieza elegida muestra sus datos en un
|
|
110
|
+
panel junto a ella (filas, butacas por fila, forma; número y personas de una mesa) y
|
|
111
|
+
«Numeración y pasillos» abre un diálogo con la vista previa del bloque: cómo se llaman las
|
|
112
|
+
filas (letras, con o sin Ñ, o números), dónde empiezan y qué letras se saltean; cómo se
|
|
113
|
+
numeran las butacas (seguidas, sólo impares, sólo pares o desde el centro), dónde empiezan,
|
|
114
|
+
de qué lado y qué números se saltean; y qué butacas se sacan. Lo del
|
|
115
|
+
diálogo se aplica todo junto al tocar «Listo» (un solo paso de deshacer, chequeado por el
|
|
116
|
+
candado); «Cancelar» no toca el plano. Arriba, «+ Referencia» (escenario, baños, barra…) e
|
|
117
|
+
«Imagen de fondo».
|
|
118
|
+
|
|
119
|
+
- `placeTypes`: de ahí sale el color de cada pieza y qué piezas admite cada tipo. Sin
|
|
120
|
+
ninguno, el panel pide cargar entradas numeradas primero.
|
|
121
|
+
- Cupo (nuevo en 0.4): con `capacitySteps`, la tarjeta dice «30 de 100 lugares», «Agregar
|
|
122
|
+
bloque» trae lo que falta para el próximo lote y, completo, se apaga con «Ya ubicaste los
|
|
123
|
+
100 lugares de Platea». Ningún cambio (filas, butacas, duplicar, numeración, lugares de
|
|
124
|
+
mesa, cambiar de tipo, un `layout` nuevo) puede pasarlo: se rechaza con «Platea tiene 100
|
|
125
|
+
lugares y ya ubicaste 100.». Deshacer siempre se puede. Si el host baja el cupo y sobran
|
|
126
|
+
lugares, la tarjeta lo dice y ofrece «Ajustar» (nuevo en 0.10): saca los que sobran
|
|
127
|
+
empezando por los últimos (última fila del último bloque hacia atrás; las últimas mesas),
|
|
128
|
+
en un paso de deshacer y sin tocar nunca lo vendido o retenido. Si los lotes del host
|
|
129
|
+
suman (30, 50 y 100 = 180), `capacitySteps` va con las sumas acumuladas: `[30, 80, 180]`.
|
|
130
|
+
- Generales (nuevo en 0.4): las opciones `accepts: "zone"` aparecen en «Tus entradas
|
|
131
|
+
generales» con «+ Marcar zona de X». La zona es un rectángulo con el nombre, de ancho y
|
|
132
|
+
alto ajustables; marcarla es opcional y nunca figura como «Falta ubicar». Desde 0.8 puede
|
|
133
|
+
ser curva («Forma: Recta / Curva», como un bloque): `zone.curve` (grados, default 0) la
|
|
134
|
+
vuelve una tribuna en arco, concéntrica con un bloque de la misma curva; el ancho se mide
|
|
135
|
+
sobre el arco central y el alto es el grosor.
|
|
136
|
+
- `sidePanel` (nuevo en 0.9): `(api: DesignerHostApi) => ReactNode`, el panel de entradas
|
|
137
|
+
del host en lugar de «Tus entradas numeradas» (a la izquierda; en el celular, dentro de
|
|
138
|
+
la hoja «Entradas»). `api`: `addPiece(kind, ref)` (como los botones de la librería: cupo
|
|
139
|
+
por tramos, centro de lo visible, sin encimar; devuelve el id o `null`),
|
|
140
|
+
`nextPieceUnits(ref)` (0 = cupo completo), `placed(ref)` (`{ units, zones, pieces }`),
|
|
141
|
+
`capacityText(ref)`, `capacityFullText(ref)`, `overCapacity(ref)` (cuántos sobran del
|
|
142
|
+
cupo, 0 si ninguno), `trimToCapacity(ref)` («Ajustar»: `{ removed }`, menos de lo que
|
|
143
|
+
sobraba si lo vendido no dejó; `null` si no sobra nada), `missingRefs`, `select(id)`, `reveal(id)`,
|
|
144
|
+
`selectedRef` y `summary`. `api` no cambia al arrastrar; pasá `sidePanel` estable
|
|
145
|
+
(`useCallback`) para que el panel no se redibuje en cada cuadro.
|
|
146
|
+
- `placeTypes` cambiantes (nuevo en 0.9): con el editor abierto se pueden agregar, renombrar,
|
|
147
|
+
recolorear o borrar entradas. El plano y la historia las siguen sin un paso de deshacer:
|
|
148
|
+
las piezas de una entrada borrada quedan sin asignar (`ref: ""`, con el nombre) y se
|
|
149
|
+
avisa por `onChange`.
|
|
150
|
+
- `lockedUnitIds`: lo vendido y lo retenido por cualquier comprador. Moverlas se puede; lo
|
|
151
|
+
que las borre, renumere o pase a otro tipo se rechaza con un aviso. Vale también para
|
|
152
|
+
Deshacer/Rehacer y para un `layout` nuevo del host: si toca algo de esto, no se carga y
|
|
153
|
+
se muestra el mismo aviso. **El host tiene que validar con `findLockViolations` antes de
|
|
154
|
+
pasar un plano nuevo**, y otra vez en el servidor al guardar.
|
|
155
|
+
- `onDone` (nuevo en 0.3): el botón «Listo». Puede ser async; si falla, se avisa. Si no está,
|
|
156
|
+
el botón dice «Guardar» y llama a `onSave` como antes.
|
|
157
|
+
- `onClose` (nuevo en 0.3): el botón «Volver» de arriba a la izquierda. Sin esto, no hay.
|
|
158
|
+
- `theme` (nuevo en 0.3): `"dark"` pone los valores por defecto oscuros de los tokens
|
|
159
|
+
`--vk-*` (los del diseño); `"light"` es el de siempre. Los tokens del host siguen ganando.
|
|
160
|
+
- Teclado: «Poner en el centro» (o Enter sobre el plano) pone la referencia armada; las
|
|
161
|
+
flechas mueven la elegida (Shift, de a más).
|
|
162
|
+
- Ordenar el plano (nuevo en 0.7): al mover, la pieza se pega a bordes y centros de las
|
|
163
|
+
demás y al centro de la hoja, con una guía; también al mismo espacio que ya hay entre dos
|
|
164
|
+
vecinas, con una cota. Las manijas pegan el borde a otras piezas y el ancho/alto al de
|
|
165
|
+
otra; en bloques y mesas, la escala de otra del mismo tipo (± 5 %). Alt/Option, sin imán.
|
|
166
|
+
- Varias piezas (nuevo en 0.7): Shift o ⌘/Ctrl + clic suma o saca; arrastrar en un lugar
|
|
167
|
+
vacío dibuja un recuadro que elige lo que toca (⌘/Ctrl + A, todas). Con el dedo, mantener
|
|
168
|
+
apretado en vacío y arrastrar es el recuadro; arrastrar sin esperar sigue moviendo el
|
|
169
|
+
plano. Con el mouse, el plano se mueve con espacio + arrastrar, el botón del medio o la
|
|
170
|
+
rueda (zoom). Con dos o más elegidas, el panel ofrece alinear, repartir con el mismo
|
|
171
|
+
espacio, igualar ancho/alto o tamaño, duplicar y borrar; todo es un paso de deshacer y
|
|
172
|
+
pasa por el mismo candado y cupo.
|
|
173
|
+
- `onChange` se llama con cada cambio.
|
|
174
|
+
- `background.onPick` lo resuelve el host (sube el archivo y devuelve la URL, o `null`);
|
|
175
|
+
`suggestedSrc` ofrece con un toque la imagen del evento.
|
|
176
|
+
- En un contenedor de 768 px o menos (el celular) el lienzo ocupa todo: «Tus entradas», la
|
|
177
|
+
pieza elegida, las referencias y la imagen van en hojas desde abajo, y la numeración en una
|
|
178
|
+
hoja de todo el alto. Se mide el contenedor, no la ventana.
|
|
179
|
+
|
|
180
|
+
### `<SeatMap />`
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import { SeatMap } from "venuekit/react"
|
|
184
|
+
|
|
185
|
+
const [selection, setSelection] = useState<string[]>([])
|
|
186
|
+
|
|
187
|
+
<SeatMap
|
|
188
|
+
layout={layout}
|
|
189
|
+
placeTypes={[{ ref: "vip", label: "Platea VIP", color: "#7c3aed", priceLabel: "$ 25.000" }]}
|
|
190
|
+
focusRef="vip" // el tipo que eligió el comprador: el mapa encuadra ahí
|
|
191
|
+
takenUnitIds={soldOrHeldByOthers}
|
|
192
|
+
heldUnitIds={heldForThisBuyer}
|
|
193
|
+
selection={selection}
|
|
194
|
+
onSelectionChange={setSelection}
|
|
195
|
+
conflictUnitIds={conflicts} // lo que el host recibió en un 409
|
|
196
|
+
holdMinutes={10}
|
|
197
|
+
totalLabel="$ 50.000" // ya formateado: la librería no formatea plata
|
|
198
|
+
onContinue={goToCheckout}
|
|
199
|
+
/>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- `placeTypes`: pasá **todos** los tipos que hay en el plano, no sólo el elegido. Una pieza
|
|
203
|
+
cuyo tipo no está en la lista sale neutra y atenuada, como si no se vendiera.
|
|
204
|
+
- `takenUnitIds`: lo vendido **y lo retenido por otras personas**. `heldUnitIds` es sólo lo
|
|
205
|
+
retenido **para este comprador** (lo suyo, en otra pestaña o en un paso anterior).
|
|
206
|
+
- Otros props: `maxSelection`, `continueLabel`, `fill` (el mapa es la página: barra abajo),
|
|
207
|
+
`messages`, `className`, `style`.
|
|
208
|
+
|
|
209
|
+
### `<VenueOverview />` (nuevo en 0.4)
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<VenueOverview
|
|
213
|
+
layout={layout}
|
|
214
|
+
placeTypes={[
|
|
215
|
+
{ ref: "vip", label: "Platea VIP", color: "#7c3aed", priceLabel: "$ 25.000" },
|
|
216
|
+
{ ref: "general:campo", label: "Campo delantero", color: "#3fbf8f", available: false },
|
|
217
|
+
]}
|
|
218
|
+
onSelect={(ref) => openTicket(ref)} // tocó una zona o un lugar de ese tipo
|
|
219
|
+
/>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
El mapa entero para quien compra, encuadrado en todo el plano y sin atenuar nada. Tocar una
|
|
223
|
+
zona o cualquier lugar de un tipo llama `onSelect(ref)`: no elige butacas (eso es
|
|
224
|
+
`<SeatMap />`). Lo que no es de ningún tipo de la lista va en gris y no se toca; con
|
|
225
|
+
`available: false` va atenuado, dice «Agotada» y no llama a `onSelect`. Debajo, un botón por
|
|
226
|
+
tipo (nombre, `priceLabel`, «Agotada»): el camino del teclado y del lector de pantalla.
|
|
227
|
+
Otros props: `messages`, `className`, `style`.
|
|
228
|
+
|
|
229
|
+
### Mapa entero en una tarjeta (nuevo en 0.6)
|
|
230
|
+
|
|
231
|
+
Desde 0.7 el mapa entero encuadra justo lo dibujado (margen de 4 % del lado corto, hasta
|
|
232
|
+
16 px) y la caja toma esa proporción, no la de la hoja. Los nombres de las piezas no bajan de
|
|
233
|
+
11 px: en una pieza angosta y alta van girados a lo largo y, si igual no entran, se cortan con «…».
|
|
234
|
+
|
|
235
|
+
Para mostrar el plano adentro de una tarjeta, al lado de la lista de entradas del host:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
{focus === null ? (
|
|
239
|
+
<VenueOverview
|
|
240
|
+
layout={layout}
|
|
241
|
+
placeTypes={types} // con priceLabel: va debajo del nombre de cada zona
|
|
242
|
+
onSelect={(ref) => (isZone(ref) ? addOne(ref) : setFocus(ref))}
|
|
243
|
+
hideList // sin la lista de abajo (sigue para teclado y lector)
|
|
244
|
+
showHint // «Tocá un sector para elegirlo…»
|
|
245
|
+
selectedRefs={refsWithSomething} // zonas pintadas más fuertes
|
|
246
|
+
takenUnitIds={sold}
|
|
247
|
+
selectedUnitIds={selection} // butacas elegidas, en miniatura
|
|
248
|
+
/>
|
|
249
|
+
) : (
|
|
250
|
+
<SeatMap
|
|
251
|
+
embedded // sin barra de total: la selección la muestra el host
|
|
252
|
+
onBack={() => setFocus(null)} // «← Todo el mapa»
|
|
253
|
+
onFocusChange={setFocus} // tocaron otro tipo en el plano atenuado
|
|
254
|
+
layout={layout} placeTypes={types} focusRef={focus}
|
|
255
|
+
takenUnitIds={sold} selection={selection} onSelectionChange={setSelection}
|
|
256
|
+
/>
|
|
257
|
+
)}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
- `<VenueOverview />`: `hideList`, `showHint`, `selectedRefs`, `takenUnitIds`,
|
|
261
|
+
`selectedUnitIds`, `height` (alto fijo; sin él sigue la proporción del plano). Encuadra el
|
|
262
|
+
plano entero cada vez que cambia el tamaño de la caja.
|
|
263
|
+
- `<SeatMap />`: `embedded`, `onBack`, `onFocusChange`, `height`. Encuadra `focusRef`
|
|
264
|
+
agrandado (sus zonas incluidas) con el resto del plano atenuado.
|
|
265
|
+
- El mapa del comprador pinta libre = color del tipo con el número oscuro, tuya = blanca con
|
|
266
|
+
borde del tipo, vendida = gris oscuro; referencias en un bloque oscuro con el nombre en
|
|
267
|
+
mayúsculas. Tokens: `--vk-mine`, `--vk-sold`, `--vk-sold-stroke`, `--vk-sold-fg`,
|
|
268
|
+
`--vk-unit-fg`, `--vk-landmark`, `--vk-landmark-stroke`, `--vk-landmark-fg`,
|
|
269
|
+
`--vk-zone-alpha-selected`. El editor no los usa.
|
|
270
|
+
|
|
271
|
+
### Textos y tema
|
|
272
|
+
|
|
273
|
+
- Mensajes en es-AR por defecto: `es` / `en` (mapa) y `designerEs` / `designerEn` (editor).
|
|
274
|
+
Se pisan parcialmente con `mergeMessages(overrides)` / `mergeDesignerMessages(overrides)`
|
|
275
|
+
o pasando `messages`.
|
|
276
|
+
- Tema por tokens CSS `--vk-*` (`--vk-accent`, `--vk-bg`, `--vk-fg`, `--vk-line`,
|
|
277
|
+
`--vk-muted`, `--vk-surface`, `--vk-radius`, `--vk-font`…) sobre `.vk-root`; están
|
|
278
|
+
declarados con `:where()`, así que una regla común del host los pisa.
|
|
279
|
+
- El editor suma `--vk-panel`, `--vk-raised` (paneles flotantes), `--vk-field`,
|
|
280
|
+
`--vk-canvas` / `--vk-canvas-dot` (fondo del lienzo), `--vk-primary-bg` (el botón «Listo»,
|
|
281
|
+
admite un degradé), `--vk-warn`, `--vk-ok`, `--vk-link`, `--vk-seg` / `--vk-seg-on`. El
|
|
282
|
+
lienzo lee además `--vk-shape`, `--vk-shape-fill`, `--vk-unassigned`, `--vk-taken`… La
|
|
283
|
+
fuente es la de `--vk-font`: pasale la del host.
|
|
284
|
+
|
|
285
|
+
## Qué es del host
|
|
286
|
+
|
|
287
|
+
La librería dibuja y valida el plano; no sabe de plata ni de ventas. El host se ocupa de:
|
|
288
|
+
|
|
289
|
+
- **Subir imágenes**: `background.onPick` devuelve la URL.
|
|
290
|
+
- **Precios**: entran como texto ya armado (`priceLabel`, `totalLabel`).
|
|
291
|
+
- **Guardar los planos por nombre** y devolverlos al editor.
|
|
292
|
+
- **Saber qué está vendido o retenido**: en el mapa, `takenUnitIds` (vendido + retenido por
|
|
293
|
+
otros) y `heldUnitIds` (retenido para este comprador); en el editor, `lockedUnitIds`
|
|
294
|
+
(vendido + retenido por cualquiera). Y volver a validar en el servidor con `findLockViolations`.
|
|
295
|
+
- **No publicar un plano con nombres repetidos**: `summarize(layout).byRef[ref].duplicates`.
|
|
296
|
+
- **Re-enlazar un plano guardado** al evento nuevo con `rebindPlaceTypes` antes de mostrarlo.
|
|
297
|
+
|
|
298
|
+
## Notas para el host
|
|
299
|
+
|
|
300
|
+
- **El editor necesita alto**: ocupa el de su contenedor, así que el contenedor tiene que
|
|
301
|
+
tener uno (`height: 100dvh`, un flex que crezca…). Sin alto, no se ve.
|
|
302
|
+
- **`layout` y `onChange` son seguros de devolver**: si pasás por `layout` lo que salió de
|
|
303
|
+
`onChange` (setState y de vuelta como prop) el editor lo reconoce y no lo trata como un
|
|
304
|
+
plano nuevo. Un objeto distinto sí se carga (si no toca lo vendido), y se puede deshacer:
|
|
305
|
+
por eso un `layout` armado de nuevo en cada render, aunque sea igual, recarga el editor.
|
|
306
|
+
Usá `useMemo` (ejemplo arriba).
|
|
307
|
+
- **Los ids en conflicto quedan vendidos**: lo que llega en `conflictUnitIds` se trata como
|
|
308
|
+
vendido mientras el mapa siga montado, aunque el host después lo libere.
|
|
309
|
+
|
|
310
|
+
## Desarrollo
|
|
311
|
+
|
|
312
|
+
Requiere Node ≥ 20.
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
npm install
|
|
316
|
+
npm test # vitest, en Node, sin navegador
|
|
317
|
+
npm run typecheck
|
|
318
|
+
npm run build # construye el paquete venuekit (dist/)
|
|
319
|
+
npm run dev -w playground # http://localhost:5173 — `/` el mapa, `/?designer` el editor
|
|
320
|
+
npx playwright install chromium webkit # una vez
|
|
321
|
+
npm run test:e2e # proyectos mobile (WebKit) y desktop (Chromium)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Estructura: `packages/core`, `packages/canvas` y `packages/react` son internos (privados);
|
|
325
|
+
`packages/venuekit` los empaqueta en el único paquete publicable. El playground y los tests
|
|
326
|
+
corren contra el fuente, sin build. Diseño: `docs/specs/`. Planes: `docs/plans/`.
|