lf-pagebuilder-vue 0.0.68 → 0.0.70

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/README.md CHANGED
@@ -1,537 +1,529 @@
1
- # lf-pagebuilder-vue
2
-
3
- Editor visual para construir páginas HTML usando los componentes de `libreria-astro-lefebvre`.
4
-
5
- ---
6
-
7
- ## ⚡ Referencia rápida — Todas las opciones
8
-
9
- ### Modo 1 — Componente Vue
10
-
11
- ```bash
12
- npm install lf-pagebuilder-vue
13
- ```
14
-
15
- ```vue
16
- <template>
17
- <Pagebuilder
18
- :isProduction="false"
19
- :debugMode="false"
20
- :submitForm="false"
21
- inputId="mi-input-hidden"
22
- :excludeComponentTypes="['SEO', 'Footer']"
23
- :visibleSections="['Header', 'Body', 'Footer']"
24
- allowRenderMode="open"
25
- limboToken="eyJ..."
26
- client:only="vue"
27
- />
28
- </template>
29
-
30
- <script setup>
31
- import { Pagebuilder } from 'lf-pagebuilder-vue';
32
- import 'lf-pagebuilder-vue/styles';
33
- </script>
34
- ```
35
-
36
- | Prop | Tipo | Default | Descripción |
37
- |------|------|---------|-------------|
38
- | `isProduction` | `boolean` | `false` | `true` = API producción · `false` = API desarrollo |
39
- | `debugMode` | `boolean` | `false` | Muestra panel de import/export JSON y botón de carga desde localStorage |
40
- | `submitForm` | `boolean` | `false` | Al guardar, busca el `<form>` padre y hace `.submit()` automáticamente |
41
- | `inputId` | `string \| null` | `null` | ID del `<input hidden>` donde se vuelca el JSON de configuración al guardar |
42
- | `excludeComponentTypes` | `string[]` | `[]` | Categorías de componentes que NO aparecerán en el selector. "Repetidor" siempre se excluye. |
43
- | `visibleSections` | `string[]` | | Secciones visibles. Si no se indica, se muestran todas. Valores posibles: `Header`, `Body`, `Footer`, `Sidebar` |
44
- | `allowRenderMode` | `'open' \| 'fullpage' \| 'onlybody'` | `'open'` | Controla el modo de renderizado. `open` = el usuario elige · `fullpage` = forzado página completa · `onlybody` = forzado solo body |
45
- | `limboToken` | `string` | | Token JWT para el gestor de imágenes Limbo. Obtenerlo server-side con `fetchLimboToken()` |
46
-
47
- > **Categorías disponibles para `excludeComponentTypes`:**
48
- > `Call to Action`, `Contenido`, `Separador`, `Texto`, `Cabecera`, `Footer`, `Imagen`, `Repetidor`, `Formulario`, `Título`, `SEO`
49
-
50
- ---
51
-
52
- ### Modo 2Script IIFE (Symfony / Twig / cualquier backend)
53
-
54
- ```html
55
- <link rel="stylesheet" href="/build/lf-pagebuilder-iife.css">
56
- <script src="/build/lf-pagebuilder-iife.iife.js"></script>
57
-
58
- <input
59
- type="hidden"
60
- class="js-lf-pagebuilder-vue-input"
61
- name="page_config"
62
- id="mi-input"
63
- data-is-production="false"
64
- data-debug-mode="false"
65
- data-submit-form="false"
66
- data-exclude-component-types="SEO,Footer,Cabecera"
67
- data-visible-sections="Header,Body,Footer"
68
- data-allow-render-mode="open"
69
- />
70
- ```
71
-
72
- | Atributo `data-*` | Valores | Default | Descripción |
73
- |-------------------|---------|---------|-------------|
74
- | `data-is-production` | `"true"` \| `"false"` | `"false"` | Entorno de la API de renderizado |
75
- | `data-debug-mode` | `"true"` \| `"false"` | `"false"` | Muestra panel de import/export JSON y botón de carga desde localStorage |
76
- | `data-submit-form` | `"true"` \| `"false"` | `"false"` | Al guardar, busca el `<form>` padre y hace `.submit()` automáticamente |
77
- | `data-exclude-component-types` | `string` (separado por comas) | — | Categorías de componentes a excluir. Ej: `"SEO,Footer,Cabecera"` |
78
- | `data-visible-sections` | `string` (separado por comas) | — | Secciones visibles. Si no se indica, se muestran todas. Ej: `"Header,Body"` |
79
- | `data-allow-render-mode` | `"open"` \| `"fullpage"` \| `"onlybody"` | `"open"` | Controla el modo de renderizado. `open` = el usuario elige · `fullpage` = forzado página completa · `onlybody` = forzado solo body |
80
-
81
- > El `id` del input se usa como clave de localStorage y como referencia interna. Si no tiene `id`, se genera uno automáticamente.
82
-
83
- ---
84
-
85
- ## Instalación
86
-
87
- ```bash
88
- npm i lf-pagebuilder-vue
89
- ```
90
-
91
- Luego en tu `package.json`, cambia la versión a `latest` para tener siempre la última:
92
-
93
- ```json
94
- {
95
- "dependencies": {
96
- "lf-pagebuilder-vue": "latest"
97
- }
98
- }
99
- ```
100
-
101
- ## ¿Qué es?
102
-
103
- El Pagebuilder permite al usuario arrastrar y configurar componentes de la librería de componentes Astro de Lefebvre para diseñar páginas de forma visual.
104
-
105
- **El Pagebuilder NO renderiza la página final.** Genera un JSON de configuración que se envía a una API externa renderizadora, que es la encargada de generar el HTML final.
106
-
107
- ## Formas de uso
108
-
109
- ### 1. Como script IIFE (Symfony u otros backends)
110
-
111
- En la carpeta `dist-symfony/` están los archivos compilados:
112
- - `lf-pagebuilder-iife.iife.js`
113
- - `lf-pagebuilder-iife.css`
114
-
115
- **Instalación:**
116
-
117
- Copiar estos archivos a la carpeta pública del proyecto. En Symfony, puedes configurar el build para que los copie automáticamente a `public/build/` o copiarlos manualmente.
118
-
119
- **Uso:**
120
-
121
- ```html
122
- <link rel="stylesheet" href="/build/lf-pagebuilder-iife.css">
123
- <script src="/build/lf-pagebuilder-iife.iife.js"></script>
124
-
125
- <!-- El Pagebuilder se monta automáticamente en elementos con el selector: -->
126
- <div class="XXXXXXXX" data-is-production="false"></div>
127
- ```
128
-
129
- El atributo `data-is-production` indica el entorno:
130
- - `"true"` → Usa API de producción (`https://render-api.lefebvre.es`)
131
- - `"false"` Usa API de desarrollo (`https://led-dev-astro-render-api-dev.eu.els.local`)
132
-
133
- #### Data attributes disponibles
134
-
135
- Se pueden pasar opciones de configuración al Pagebuilder mediante atributos `data-*` en el input:
136
-
137
- | Atributo | Tipo | Default | Descripción |
138
- |----------|------|---------|-------------|
139
- | `data-is-production` | `"true"` \| `"false"` | `"false"` | Entorno de la API de renderizado |
140
- | `data-debug-mode` | `"true"` \| `"false"` | `"false"` | Activa el modo debug |
141
- | `data-submit-form` | `"true"` \| `"false"` | `"false"` | Envía el formulario automáticamente |
142
- | `data-exclude-component-types` | `string` | - | Categorías de componentes a excluir, separadas por coma |
143
-
144
- **Ejemplo completo:**
145
-
146
- ```html
147
- <input
148
- type="hidden"
149
- class="js-lf-pagebuilder-vue-input"
150
- name="page_config"
151
- data-is-production="true"
152
- data-debug-mode="false"
153
- data-submit-form="true"
154
- data-exclude-component-types="SEO, Footer, Cabecera"
155
- />
156
- ```
157
-
158
- > **Nota sobre `data-exclude-component-types`:** La categoría "Repetidor" siempre se excluye automáticamente. Las categorías adicionales que pases se suman a esta exclusión. Las categorías disponibles son: `Call to Action`, `Contenido`, `Separador`, `Texto`, `Cabecera`, `Footer`, `Imagen`, `Repetidor`, `Formulario`, `Título`, `SEO`.
159
-
160
- ---
161
-
162
- ### 2. Como componente Vue
163
-
164
- ```bash
165
- npm install lf-pagebuilder-vue
166
- ```
167
-
168
- ```vue
169
- <template>
170
- <Pagebuilder :isProduction="false" />
171
- </template>
172
-
173
- <script setup>
174
- import { Pagebuilder } from 'lf-pagebuilder-vue';
175
- </script>
176
- ```
177
-
178
- **Props:**
179
-
180
- | Prop | Tipo | Default | Descripción |
181
- |------|------|---------|-------------|
182
- | `limboToken` | `string` | - | Token JWT de Limbo, obtenido server-side con `fetchLimboToken()` |
183
- | `isProduction` | `boolean` | `false` | `true` = API producción, `false` = API desarrollo |
184
- | `debugMode` | `boolean` | `false` | Activa el modo debug |
185
- | `submitForm` | `boolean` | `false` | Envía el formulario automáticamente |
186
- | `inputId` | `string \| null` | `null` | ID del input hidden donde guardar la configuración |
187
- | `excludeComponentTypes` | `string[]` | `[]` | Categorías de componentes a excluir (además de "Repetidor" que siempre se excluye) |
188
-
189
- **Ejemplo con exclusión de componentes:**
190
-
191
- ```vue
192
- <template>
193
- <Pagebuilder
194
- :isProduction="false"
195
- :excludeComponentTypes="['SEO', 'Footer', 'Cabecera']"
196
- />
197
- </template>
198
-
199
- <script setup>
200
- import { Pagebuilder } from 'lf-pagebuilder-vue';
201
- </script>
202
- ```
203
-
204
- ---
205
-
206
- ## Instalación y dependencias
207
-
208
- ### Dependencias requeridas en el proyecto padre
209
-
210
- El proyecto que use este Pagebuilder solo necesita instalar `lf-pagebuilder-vue`. La librería de componentes (`libreria-astro-lefebvre`) se instala automáticamente como dependencia transitiva:
211
-
212
- ```bash
213
- npm install lf-pagebuilder-vue@latest vue@^3.3.0 vuedraggable@^4.1.0 @vueup/vue-quill@^1.2.0
214
- ```
215
-
216
- | Dependencia | Tipo | Descripción |
217
- |-------------|------|-------------|
218
- | `lf-pagebuilder-vue` | directa | Page Builder (incluye `libreria-astro-lefebvre` como dep) |
219
- | `vue` | peerDep | Framework Vue 3 |
220
- | `vuedraggable` | peerDep | Drag & drop para Vue 3 |
221
- | `@vueup/vue-quill` | peerDep | Editor de texto enriquecido |
222
-
223
- ### Configuración en proyectos Astro
224
-
225
- El proyecto debe tener las integraciones de Vue y Tailwind:
226
-
227
- ```bash
228
- npm install @astrojs/vue tailwindcss @tailwindcss/vite
229
- ```
230
-
231
- En `astro.config.mjs`:
232
-
233
- ```js
234
- import { defineConfig } from 'astro/config';
235
- import vue from '@astrojs/vue';
236
- import tailwindcss from '@tailwindcss/vite';
237
-
238
- export default defineConfig({
239
- integrations: [vue()],
240
-
241
- vite: {
242
- plugins: [tailwindcss()],
243
- ssr: {
244
- noExternal: ['libreria-astro-lefebvre', 'lf-pagebuilder-vue'],
245
- },
246
- },
247
- });
248
- ```
249
-
250
- ### Integración con Limbo (gestión de imágenes)
251
-
252
- Si se quiere usar el sistema de imágenes Limbo desde el Pagebuilder:
253
-
254
- **1. Variables de entorno** (`.env`):
255
-
256
- ```env
257
- PUBLIC_LIMBO_PUBLIC_KEY=pk_tu_public_key
258
- IS_PROD=FALSE
259
- ```
260
-
261
- **2. Obtener token server-side y pasarlo al componente:**
262
-
263
- ```astro
264
- ---
265
- import { Pagebuilder } from 'lf-pagebuilder-vue';
266
- import { fetchLimboToken } from 'lf-pagebuilder-vue/limbo';
267
-
268
- let limboToken = '';
269
- try {
270
- const result = await fetchLimboToken({
271
- publicKey: import.meta.env.PUBLIC_LIMBO_PUBLIC_KEY || '',
272
- isProduction: import.meta.env.IS_PROD === 'TRUE'
273
- });
274
- limboToken = result.token;
275
- } catch (e) {
276
- console.error('[Pagebuilder] Error obteniendo token de Limbo:', e);
277
- }
278
- ---
279
-
280
- <Pagebuilder
281
- limboToken={limboToken}
282
- isProduction={import.meta.env.IS_PROD === 'TRUE'}
283
- inputId="mi-input"
284
- client:only="vue"
285
- />
286
- ```
287
-
288
- El token se obtiene en el frontmatter (server-side), evitando exponer la Public Key al cliente. La URL de la API de Limbo se determina automáticamente según `isProduction`. Los assets de `limbo-component` se cargan internamente desde el paquete npm (no se usa CDN externo).
289
-
290
- Para más detalles, consulta [README_LIMBO.md](README_LIMBO.md).
291
-
292
- ---
293
-
294
- ## Build
295
-
296
- El proyecto tiene dos builds diferentes:
297
-
298
- ### Build estándar (librería npm)
299
-
300
- ```bash
301
- npm run build
302
- ```
303
-
304
- Genera en `dist/`:
305
- - `index.js` - ES Module
306
- - `index.cjs` - CommonJS
307
- - `index.d.ts` - Tipos TypeScript
308
- - `styles.css` - Estilos
309
-
310
- Este es el build que se publica en npm.
311
-
312
- ### Build Symfony (IIFE)
313
-
314
- ```bash
315
- npm run build:symfony
316
- ```
317
-
318
- Genera en `dist-symfony/`:
319
- - `lf-pagebuilder-iife.iife.js` - Script autocontenido
320
- - `lf-pagebuilder-iife.css` - Estilos
321
-
322
- Este build incluye Vue y todas las dependencias empaquetadas. Se puede usar directamente con `<script>` sin necesidad de bundler.
323
-
324
- ### Build completo
325
-
326
- ```bash
327
- npm run build:all
328
- ```
329
-
330
- Ejecuta ambos builds.
331
-
332
- ---
333
-
334
- ## ⚠️ Desarrollo local
335
-
336
- > **IMPORTANTE**: Lee esta sección completa antes de trabajar con este proyecto localmente.
337
-
338
- ### El problema del `package.json`
339
-
340
- Este proyecto tiene **dos configuraciones de `package.json`** porque durante el desarrollo local NO queremos compilar constantemente:
341
-
342
- | Archivo | Apunta a | Uso |
343
- |---------|----------|-----|
344
- | `package.dev.json` | `./src/` (código fuente) | Desarrollo local |
345
- | `package.prod.json` | `./dist/` (compilado) | Publicar en npm |
346
-
347
- **Durante el desarrollo**, el `package.json` debe apuntar a los archivos fuente para que los cambios se reflejen inmediatamente sin hacer build.
348
-
349
- **Para publicar en npm**, debe apuntar a los archivos compilados.
350
-
351
- #### Cambiar entre modos
352
-
353
- Puedes copiar el archivo o copiar y pegar el contenido manualmente:
354
-
355
- ```bash
356
- # Para DESARROLLO: copiar configuración de desarrollo
357
- cp package.dev.json package.json
358
-
359
- # Para PRODUCCIÓN/PUBLICAR: copiar configuración de producción
360
- cp package.prod.json package.json
361
- npm run build
362
- npm publish
363
- ```
364
-
365
- > ⚠️ **NUNCA publiques con la configuración de desarrollo.** El paquete no funcionará porque `./src/` no se incluye en el paquete publicado.
366
-
367
- #### ⚠️ MUY IMPORTANTE: Mantener sincronizados los 3 archivos
368
-
369
- Los archivos `package.json`, `package.dev.json` y `package.prod.json` deben estar sincronizados:
370
-
371
- - **Nueva dependencia**: Si añades una dependencia, añádela también en `package.dev.json` y `package.prod.json`
372
- - **Cambio de versión**: Cuando subas la versión del paquete, actualízala en los 3 archivos
373
- - **Cualquier otro cambio**: Scripts, keywords, etc. deben reflejarse en los 3
374
-
375
- ```bash
376
- # Ejemplo: después de añadir una dependencia
377
- npm install nueva-dependencia
378
-
379
- # Ahora editar manualmente package.dev.json y package.prod.json
380
- # para añadir "nueva-dependencia" en las mismas secciones
381
- ```
382
-
383
- ---
384
-
385
- ### Trabajar con `npm link`
386
-
387
- Para probar cambios localmente en otro proyecto sin publicar:
388
-
389
- #### Paso 1: Crear el enlace en esta librería
390
-
391
- ```bash
392
- cd /ruta/a/lf-pagebuilder-vue
393
- npm link
394
- ```
395
-
396
- #### Paso 2: Usar el enlace en tu proyecto
397
-
398
- ```bash
399
- cd /ruta/a/tu-proyecto
400
- npm link lf-pagebuilder-vue
401
- ```
402
-
403
- ---
404
-
405
- ### ⚠️ CRÍTICO: Múltiples librerías con `npm link`
406
-
407
- **Este proyecto depende de `libreria-astro-lefebvre`.** Si necesitas trabajar con ambas librerías localmente (muy común), hay una trampa importante:
408
-
409
- ```bash
410
- # ❌ INCORRECTO - El segundo comando ROMPE el primero
411
- npm link lf-pagebuilder-vue
412
- npm link libreria-astro-lefebvre
413
-
414
- # CORRECTO - Linkear TODAS las librerías en UN SOLO comando
415
- npm link lf-pagebuilder-vue libreria-astro-lefebvre
416
- ```
417
-
418
- #### Flujo completo para trabajar con ambas librerías
419
-
420
- ```bash
421
- # 1. En lf-pagebuilder-vue
422
- cd /ruta/a/lf-pagebuilder-vue
423
- cp package.dev.json package.json # Modo desarrollo
424
- npm link
425
-
426
- # 2. En libreria-astro-lefebvre
427
- cd /ruta/a/libreria-astro-lefebvre
428
- npm link
429
-
430
- # 3. En tu proyecto consumidor - AMBAS EN UN SOLO COMANDO
431
- cd /ruta/a/tu-proyecto
432
- npm link lf-pagebuilder-vue libreria-astro-lefebvre
433
- ```
434
-
435
- #### Deshacer los enlaces
436
-
437
- ```bash
438
- cd /ruta/a/tu-proyecto
439
- npm unlink lf-pagebuilder-vue libreria-astro-lefebvre
440
- npm install
441
- ```
442
-
443
- #### Si algo no funciona
444
-
445
- ```bash
446
- # Nuclear option: borrar todo y reinstalar
447
- cd /ruta/a/tu-proyecto
448
- rm -rf node_modules package-lock.json
449
- npm install
450
- npm link lf-pagebuilder-vue libreria-astro-lefebvre
451
- ```
452
-
453
- ---
454
-
455
- ### Watch mode
456
-
457
- Para que los cambios se reflejen automáticamente mientras desarrollas:
458
-
459
- ```bash
460
- npm run dev
461
- ```
462
-
463
- Esto ejecuta `vite build --watch` y recompila automáticamente cuando hay cambios.
464
-
465
- > **Nota**: Si usas `package.dev.json` (apuntando a `./src/`), no necesitas watch mode porque los cambios en el código fuente se reflejan directamente.
466
-
467
- ---
468
-
469
- ### 📘 Nota para los no familiarizados con npm
470
-
471
- Este proyecto tiene **dos lugares separados**:
472
-
473
- #### 1. GitHub (código fuente)
474
-
475
- ```
476
- https://github.com/Lefebvre-El-Derecho-SA/lf-pagebuilder-vue
477
- ```
478
-
479
- Aquí es donde se **edita el código**. Los cambios que hagas aquí NO se reflejan automáticamente en los proyectos que usan el paquete.
480
-
481
- #### 2. npm (paquete distribuido)
482
-
483
- ```
484
- https://www.npmjs.com/package/lf-pagebuilder-vue
485
- ```
486
-
487
- Aquí es donde las webs **descargan el paquete** cuando hacen `npm install`. Esta versión va **separada de GitHub**.
488
-
489
- #### ¿Cómo actualizar el paquete en npm?
490
-
491
- Para que los cambios en GitHub lleguen a npm, hay que **publicar una nueva versión**:
492
-
493
- 1. **Subir la versión** en `package.json` (y en `package.dev.json` y `package.prod.json`)
494
-
495
- > ⚠️ Si no subes la versión, npm rechazará la publicación.
496
-
497
- 2. **Asegurarte de usar la configuración de producción**:
498
- ```bash
499
- cp package.prod.json package.json
500
- ```
501
-
502
- 3. **Compilar y publicar**:
503
- ```bash
504
- npm run build
505
- npm publish --access public
506
- ```
507
-
508
- #### Configurar acceso a npm (primera vez)
509
-
510
- Para poder publicar, necesitas pertenecer a la organización de npm:
511
-
512
- 1. **Solicitar acceso** al equipo `desarrollowebteamlef` en:
513
- ```
514
- https://www.npmjs.com/settings/desarrollowebteamlef/packages
515
- ```
516
-
517
- 2. **Crear un token de acceso** en npm:
518
- - Ve a tu perfil de npm → Access Tokens → Generate New Token
519
- - Selecciona "Bypass 2FA" (si aplica)
520
- - Pon expiración de 90 días
521
- - Copia el token generado
522
-
523
- 3. **Configurar el token** en tu máquina:
524
-
525
- Edita (o crea) el archivo `~/.npmrc`:
526
- ```bash
527
- # Linux/Mac
528
- nano ~/.npmrc
529
-
530
- # Añadir esta línea con tu token:
531
- //registry.npmjs.org/:_authToken=TU_TOKEN_AQUI
532
- ```
533
-
534
- 4. **Ya puedes publicar**:
535
- ```bash
536
- npm publish --access public
537
- ```
1
+ # lf-pagebuilder-vue
2
+
3
+ Editor visual para construir páginas HTML usando los componentes de `libreria-astro-lefebvre`.
4
+
5
+ ---
6
+
7
+ ## ⚡ Referencia rápida — Todas las opciones
8
+
9
+ ### Modo 1 — Componente Vue
10
+
11
+ ```bash
12
+ npm install lf-pagebuilder-vue
13
+ ```
14
+
15
+ ```vue
16
+ <template>
17
+ <Pagebuilder
18
+ :isProduction="false"
19
+ :debugMode="false"
20
+ :submitForm="false"
21
+ inputId="mi-input-hidden"
22
+ :excludeComponentTypes="['SEO', 'Footer']"
23
+ :excludeComponentTags="['boton', 'destacado']"
24
+ :includeOnlyComponentTags="['articulo']"
25
+ :visibleSections="['Header', 'Body', 'Footer']"
26
+ allowRenderMode="open"
27
+ limboToken="eyJ..."
28
+ ateneaToken="eyJ..."
29
+ :ledithorAiTools="['improve', 'improve-stream']"
30
+ client:only="vue"
31
+ />
32
+ </template>
33
+
34
+ <script setup>
35
+ import { Pagebuilder } from 'lf-pagebuilder-vue';
36
+ import 'lf-pagebuilder-vue/styles';
37
+ </script>
38
+ ```
39
+
40
+ | Prop | Tipo | Default | Descripción |
41
+ |------|------|---------|-------------|
42
+ | `isProduction` | `boolean` | `false` | `true` = API producción · `false` = API desarrollo |
43
+ | `debugMode` | `boolean` | `false` | Muestra panel de import/export JSON y botón de carga desde localStorage |
44
+ | `submitForm` | `boolean` | `false` | Al guardar, busca el `<form>` padre y hace `.submit()` automáticamente |
45
+ | `inputId` | `string \| null` | `null` | ID del `<input hidden>` donde se vuelca el JSON al guardar y del que se lee el estado inicial |
46
+ | `excludeComponentTypes` | `string[]` | `[]` | Categorías de componentes que NO aparecerán en el selector. "Repetidor" siempre se excluye. |
47
+ | `excludeComponentTags` | `string[]` | `[]` | Tags de componentes a excluir (lista negra). Un componente con cualquiera de estos tags no aparecerá. |
48
+ | `includeOnlyComponentTags` | `string[]` | `[]` | Solo muestra componentes que tengan al menos uno de estos tags (lista blanca). La exclusión tiene prioridad. |
49
+ | `visibleSections` | `string[]` | — | Secciones visibles. Si no se indica, se muestran todas. Valores: `Header`, `Body`, `Footer`, `Sidebar` |
50
+ | `allowRenderMode` | `'open' \| 'fullpage' \| 'onlybody'` | `'open'` | `open` = el usuario elige · `fullpage` = forzado página completa · `onlybody` = forzado solo body |
51
+ | `limboToken` | `string` | — | Token JWT para el gestor de imágenes Limbo. Obtenerlo server-side con `fetchLimboToken()` |
52
+ | `ateneaToken` | `string` | | Token JWT de Atenea para las funcionalidades de IA en el editor de texto (LedithorEditor). Obtenerlo server-side con `fetchAteneaToken()`. Sin este token el botón de IA no aparece. |
53
+ | `ledithorAiTools` | `string[]` | — | Herramientas de IA a habilitar en el editor de texto. Solo se aplican si también se proporciona `ateneaToken`. Ej: `['improve', 'improve-stream']` |
54
+ | `viewOnly` | `boolean` | `false` | Modo solo visualización: oculta toda la UI de edición (toolbar, panel de componentes). Usar para la vista pública. |
55
+
56
+ > **Categorías disponibles para `excludeComponentTypes`:**
57
+ > `Call to Action`, `Contenido`, `Separador`, `Texto`, `Cabecera`, `Footer`, `Imagen`, `Repetidor`, `Formulario`, `Título`, `SEO`
58
+
59
+ ---
60
+
61
+ ### Modo 2 — Script IIFE (Symfony / Twig / cualquier backend)
62
+
63
+ ```html
64
+ <link rel="stylesheet" href="/build/lf-pagebuilder-iife.css">
65
+ <script src="/build/lf-pagebuilder-iife.iife.js"></script>
66
+
67
+ <!-- EDITOR (crear) -->
68
+ <input
69
+ type="hidden"
70
+ class="js-lf-pagebuilder-vue-input"
71
+ name="pagebuilder[pageConfig]"
72
+ id="pagebuilder-new"
73
+ value=""
74
+ data-is-production="false"
75
+ data-debug-mode="true"
76
+ data-submit-form="true"
77
+ data-allow-render-mode="open"
78
+ data-limbo-token="eyJ..."
79
+ data-atenea-token="eyJ..."
80
+ data-ledithor-ai-tools='["improve","improve-stream"]'
81
+ />
82
+
83
+ <!-- EDITOR (editar) -->
84
+ <input
85
+ type="hidden"
86
+ class="js-lf-pagebuilder-vue-input"
87
+ name="pagebuilder[pageConfig]"
88
+ id="pagebuilder-123"
89
+ value="{JSON existente del pageConfig}"
90
+ data-is-production="false"
91
+ data-submit-form="true"
92
+ data-exclude-component-types="SEO,Footer,Cabecera"
93
+ data-limbo-token="eyJ..."
94
+ />
95
+
96
+ <!-- VISTA PÚBLICA (solo lectura) -->
97
+ <div id="lf-pagebuilder-render"></div>
98
+ <script>
99
+ window.LfPagebuilder.render({
100
+ el: '#lf-pagebuilder-render',
101
+ pageConfig: {{ pageConfig|raw }}, {# string JSON o objeto #}
102
+ isProduction: true
103
+ });
104
+ </script>
105
+ ```
106
+
107
+ | Atributo `data-*` | Valores | Default | Descripción |
108
+ |-------------------|---------|---------|-------------|
109
+ | `data-is-production` | `"true"` \| `"false"` | `"false"` | Entorno de la API de renderizado |
110
+ | `data-debug-mode` | `"true"` \| `"false"` | `"false"` | Muestra panel de import/export JSON y botón de carga desde localStorage |
111
+ | `data-submit-form` | `"true"` \| `"false"` | `"false"` | Al guardar, busca el `<form>` padre y hace `.submit()` automáticamente |
112
+ | `data-exclude-component-types` | `string` (comas) | — | Categorías de componentes a excluir. Ej: `"SEO,Footer,Cabecera"` |
113
+ | `data-exclude-component-tags` | `string` (comas) | — | Tags de componentes a excluir (lista negra). Ej: `"boton,destacado"` |
114
+ | `data-include-only-component-tags` | `string` (comas) | — | Solo muestra componentes con estos tags (lista blanca). La exclusión tiene prioridad. |
115
+ | `data-visible-sections` | `string` (comas) | — | Secciones visibles. Ej: `"Header,Body"` |
116
+ | `data-allow-render-mode` | `"open"` \| `"fullpage"` \| `"onlybody"` | `"open"` | Controla el modo de renderizado |
117
+ | `data-limbo-token` | `string` | | Token JWT de Limbo para el gestor de imágenes |
118
+ | `data-atenea-token` | `string` | — | Token JWT de Atenea para las funcionalidades de IA. Sin este token el botón de IA no aparece. |
119
+ | `data-ledithor-ai-tools` | `string` (JSON array) | — | Herramientas de IA a habilitar. Solo se aplican si hay `data-atenea-token`. Ej: `'["improve","improve-stream"]'` |
120
+
121
+ > El `id` del input se usa como clave de localStorage y como referencia interna. Si no tiene `id`, se genera uno automáticamente.
122
+
123
+ > **Compatibilidad de atributos:** El componente acepta también los tokens como atributos planos (sin prefijo `data-`): `limboToken="..."`, `ateneaToken="..."`, `ledithorAiTools='[...]'`. La forma recomendada es siempre `data-*`.
124
+
125
+ ---
126
+
127
+ ### API global `window.LfPagebuilder`
128
+
129
+ | Método | Descripción |
130
+ |--------|-------------|
131
+ | `LfPagebuilder.init(config?)` | Inicializa el editor en todos los elementos `.js-lf-pagebuilder-vue-input` de la página |
132
+ | `LfPagebuilder.render({ el, pageConfig, isProduction? })` | Renderiza la página en modo solo visualización (sin UI de edición) |
133
+ | `LfPagebuilder.destroyAll()` | Destruye todas las instancias activas |
134
+ | `LfPagebuilder.getInstances()` | Devuelve todas las instancias activas |
135
+ | `LfPagebuilder.setComponents(components)` | Reemplaza la lista de componentes disponibles |
136
+ | `LfPagebuilder.addComponent(component)` | Añade un componente a la lista disponible |
137
+
138
+ El IIFE se auto-inicializa en `DOMContentLoaded` si hay elementos con el selector `.js-lf-pagebuilder-vue-input` en el DOM. No es necesario llamar a `init()` manualmente.
139
+
140
+ ---
141
+
142
+ ## Integración con Symfony/Twig Contrato completo
143
+
144
+ ### Cómo funciona el intercambio de datos con el formulario
145
+
146
+ El componente actúa como un **"enhanced textarea"**:
147
+
148
+ 1. **Lee su valor inicial de `input.value`** — en `onMounted`, el editor lee el JSON del `value` del input y lo carga como estado inicial del canvas. Si está vacío, el editor empieza en blanco (modo crear). Si contiene JSON, lo inicializa con esa configuración (modo editar).
149
+
150
+ 2. **Escribe en `input.value` al guardar** — cuando el usuario pulsa "Guardar" dentro del editor, el JSON actualizado se escribe en `input.value`.
151
+
152
+ 3. **Envía el formulario si `data-submit-form="true"`** — después de escribir en `input.value`, busca el `<form>` padre y llama a `.submit()`. Esto permite que Symfony valide y persista el `pageConfig`.
153
+
154
+ ```twig
155
+ {# Crear #}
156
+ <form method="post" action="{{ path('landing_create') }}">
157
+ <input type="hidden"
158
+ id="pagebuilder-new"
159
+ name="pagebuilder[pageConfig]"
160
+ class="js-lf-pagebuilder-vue-input"
161
+ value=""
162
+ data-is-production="{{ app.environment == 'prod' ? 'true' : 'false' }}"
163
+ data-submit-form="true"
164
+ data-limbo-token="{{ limboToken }}"
165
+ data-atenea-token="{{ ateneaToken }}"
166
+ data-ledithor-ai-tools='["improve","improve-stream"]'
167
+ />
168
+ {# El CSRF token y otros campos del formulario van aquí #}
169
+ </form>
170
+
171
+ {# Editar #}
172
+ <form method="post" action="{{ path('landing_edit', {id: landing.id}) }}">
173
+ <input type="hidden"
174
+ id="pagebuilder-{{ landing.id }}"
175
+ name="pagebuilder[pageConfig]"
176
+ class="js-lf-pagebuilder-vue-input"
177
+ value="{{ landing.pageConfig|json_encode }}"
178
+ data-is-production="{{ app.environment == 'prod' ? 'true' : 'false' }}"
179
+ data-submit-form="true"
180
+ data-exclude-component-types="SEO,Footer,Cabecera"
181
+ data-limbo-token="{{ limboToken }}"
182
+ />
183
+ </form>
184
+
185
+ {# Vista pública #}
186
+ <div id="lf-pagebuilder-render"></div>
187
+ <script>
188
+ window.LfPagebuilder.render({
189
+ el: '#lf-pagebuilder-render',
190
+ pageConfig: {{ landing.pageConfig|raw }},
191
+ isProduction: {{ app.environment == 'prod' ? 'true' : 'false' }}
192
+ });
193
+ </script>
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Instalación
199
+
200
+ ```bash
201
+ npm i lf-pagebuilder-vue
202
+ ```
203
+
204
+ Luego en tu `package.json`, cambia la versión a `latest` para tener siempre la última:
205
+
206
+ ```json
207
+ {
208
+ "dependencies": {
209
+ "lf-pagebuilder-vue": "latest"
210
+ }
211
+ }
212
+ ```
213
+
214
+ ## ¿Qué es?
215
+
216
+ El Pagebuilder permite al usuario arrastrar y configurar componentes de la librería de componentes Astro de Lefebvre para diseñar páginas de forma visual.
217
+
218
+ **El Pagebuilder NO renderiza la página final.** Genera un JSON de configuración que se envía a una API externa renderizadora, que es la encargada de generar el HTML final.
219
+
220
+ ---
221
+
222
+ ## Integración con Limbo (gestión de imágenes)
223
+
224
+ Si se quiere usar el sistema de imágenes Limbo desde el Pagebuilder:
225
+
226
+ **Variables de entorno** (`.env`):
227
+
228
+ ```env
229
+ PUBLIC_LIMBO_PUBLIC_KEY=pk_tu_public_key
230
+ IS_PROD=FALSE
231
+ ```
232
+
233
+ **Obtener token server-side y pasarlo al componente:**
234
+
235
+ ```astro
236
+ ---
237
+ import { Pagebuilder } from 'lf-pagebuilder-vue';
238
+ import { fetchLimboToken } from 'lf-pagebuilder-vue/limbo';
239
+
240
+ let limboToken = '';
241
+ try {
242
+ const result = await fetchLimboToken({
243
+ publicKey: import.meta.env.PUBLIC_LIMBO_PUBLIC_KEY || '',
244
+ isProduction: import.meta.env.IS_PROD === 'TRUE'
245
+ });
246
+ limboToken = result.token;
247
+ } catch (e) {
248
+ console.error('[Pagebuilder] Error obteniendo token de Limbo:', e);
249
+ }
250
+ ---
251
+
252
+ <Pagebuilder
253
+ limboToken={limboToken}
254
+ isProduction={import.meta.env.IS_PROD === 'TRUE'}
255
+ inputId="mi-input"
256
+ client:only="vue"
257
+ />
258
+ ```
259
+
260
+ El token se obtiene en el frontmatter (server-side), evitando exponer la Public Key al cliente.
261
+
262
+ ### `fetchLimboToken` — opciones
263
+
264
+ ```ts
265
+ import { fetchLimboToken } from 'lf-pagebuilder-vue/limbo';
266
+
267
+ const { token, expires_in } = await fetchLimboToken({
268
+ publicKey: 'pk_xxx', // requerido
269
+ isProduction: false, // opcional, default: false
270
+ limboApiUrl: 'https://...' // opcional, sobreescribe isProduction
271
+ });
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Integración con Atenea (IA en el editor de texto)
277
+
278
+ Si se quiere usar las funcionalidades de IA de LedithorEditor (botón "Improve", etc.):
279
+
280
+ **Variables de entorno** (`.env`):
281
+
282
+ ```env
283
+ ATENEA_USERNAME=ledithor
284
+ ATENEA_APIKEY=tu_apikey_aqui
285
+ IS_PROD=FALSE
286
+ ```
287
+
288
+ **Obtener token server-side y pasarlo al componente:**
289
+
290
+ ```astro
291
+ ---
292
+ import { Pagebuilder } from 'lf-pagebuilder-vue';
293
+ import { fetchAteneaToken } from 'lf-pagebuilder-vue/atenea';
294
+
295
+ let ateneaToken = '';
296
+ try {
297
+ const result = await fetchAteneaToken({
298
+ username: import.meta.env.ATENEA_USERNAME || '',
299
+ apiKey: import.meta.env.ATENEA_APIKEY || '',
300
+ isProduction: import.meta.env.IS_PROD === 'TRUE'
301
+ });
302
+ ateneaToken = result.token;
303
+ } catch (e) {
304
+ console.error('[Pagebuilder] Error obteniendo token de Atenea:', e);
305
+ }
306
+ ---
307
+
308
+ <Pagebuilder
309
+ ateneaToken={ateneaToken}
310
+ :ledithorAiTools="['improve', 'improve-stream']"
311
+ client:only="vue"
312
+ />
313
+ ```
314
+
315
+ > Si `ateneaToken` no se proporciona (o está vacío), el botón de IA no aparece en el editor de texto. `ledithorAiTools` solo tiene efecto si hay token.
316
+
317
+ ### `fetchAteneaToken` — opciones
318
+
319
+ ```ts
320
+ import { fetchAteneaToken } from 'lf-pagebuilder-vue/atenea';
321
+
322
+ const { token, expiresAt } = await fetchAteneaToken({
323
+ username: 'ledithor', // requerido
324
+ apiKey: 'tu_apikey', // requerido
325
+ isProduction: false, // opcional, default: false
326
+ ateneaApiUrl: 'https://...' // opcional, sobreescribe isProduction
327
+ });
328
+ // expiresAt: Unix timestamp (segundos) del claim `exp` del JWT
329
+ ```
330
+
331
+ | Parámetro | Tipo | Default | Descripción |
332
+ |-----------|------|---------|-------------|
333
+ | `username` | `string` | — | Usuario de Atenea |
334
+ | `apiKey` | `string` | — | API Key del usuario |
335
+ | `isProduction` | `boolean` | `false` | `true` → `https://atenea.lefebvre.es` · `false` → URL de desarrollo |
336
+ | `ateneaApiUrl` | `string` | | URL base personalizada (sobreescribe `isProduction`) |
337
+
338
+ ---
339
+
340
+ ## Funcionalidades del editor
341
+
342
+ ### Color de fondo en filas y columnas
343
+
344
+ Cada fila y cada columna del editor puede tener un color de fondo personalizado con soporte de canal alpha (opacidad). El color se configura desde el panel de configuración de la fila o columna y se exporta junto al resto de la configuración del layout.
345
+
346
+ El color se almacena en el JSON de configuración como un valor `rgba(r, g, b, a)` dentro de la propiedad `backgroundColor`:
347
+
348
+ ```json
349
+ {
350
+ "rows": [
351
+ {
352
+ "config": {
353
+ "backgroundColor": "rgba(255, 200, 100, 0.75)"
354
+ },
355
+ "columns": [
356
+ {
357
+ "config": {
358
+ "backgroundColor": "rgba(240, 240, 240, 1)"
359
+ },
360
+ "components": []
361
+ }
362
+ ]
363
+ }
364
+ ]
365
+ }
366
+ ```
367
+
368
+ ### Resaltado de elemento en edición
369
+
370
+ Cuando se abre el panel de configuración de una fila, columna o componente, el elemento correspondiente se resalta visualmente con un borde ámbar y un halo de sombra. El resaltado desaparece al cerrar el panel.
371
+
372
+ ---
373
+
374
+ ## Build
375
+
376
+ El proyecto tiene dos builds:
377
+
378
+ ### Build estándar (librería npm)
379
+
380
+ ```bash
381
+ npm run build
382
+ ```
383
+
384
+ Genera en `dist/`:
385
+ - `index.js` ES Module
386
+ - `index.cjs` — CommonJS
387
+ - `index.d.ts` Tipos TypeScript
388
+ - `limbo.js` / `limbo.cjs` — Utilidades de Limbo (server-side)
389
+ - `atenea.js` / `atenea.cjs` Utilidades de Atenea (server-side)
390
+ - `styles.css` — Estilos
391
+
392
+ ### Build Symfony (IIFE)
393
+
394
+ ```bash
395
+ npm run build:symfony
396
+ ```
397
+
398
+ Genera en `dist-symfony/`:
399
+ - `lf-pagebuilder-iife.iife.js` — Script autocontenido (incluye Vue y todas las dependencias)
400
+ - `lf-pagebuilder-iife.css` — Estilos con utilidades Tailwind prefijadas (`mecano:`)
401
+
402
+ ### Build completo
403
+
404
+ ```bash
405
+ npm run build:all
406
+ ```
407
+
408
+ Ejecuta ambos builds.
409
+
410
+ ---
411
+
412
+ ## ⚠️ Desarrollo local
413
+
414
+ > **IMPORTANTE**: Lee esta sección completa antes de trabajar con este proyecto localmente.
415
+
416
+ ### Trabajar con `npm link`
417
+
418
+ Para probar cambios localmente en otro proyecto sin publicar:
419
+
420
+ ```bash
421
+ # 1. En lf-pagebuilder-vue
422
+ cd /ruta/a/lf-pagebuilder-vue
423
+ npm link
424
+
425
+ # 2. En tu proyecto consumidor
426
+ cd /ruta/a/tu-proyecto
427
+ npm link lf-pagebuilder-vue
428
+ ```
429
+
430
+ ### ⚠️ CRÍTICO: Múltiples librerías con `npm link`
431
+
432
+ **Este proyecto depende de `libreria-astro-lefebvre`.** Si necesitas trabajar con ambas librerías localmente:
433
+
434
+ ```bash
435
+ # INCORRECTO - El segundo comando ROMPE el primero
436
+ npm link lf-pagebuilder-vue
437
+ npm link libreria-astro-lefebvre
438
+
439
+ # CORRECTO - Linkear TODAS en UN SOLO comando
440
+ npm link lf-pagebuilder-vue libreria-astro-lefebvre
441
+ ```
442
+
443
+ #### Flujo completo para trabajar con ambas librerías
444
+
445
+ ```bash
446
+ # 1. En lf-pagebuilder-vue
447
+ cd /ruta/a/lf-pagebuilder-vue
448
+ npm link
449
+
450
+ # 2. En libreria-astro-lefebvre
451
+ cd /ruta/a/libreria-astro-lefebvre
452
+ npm link
453
+
454
+ # 3. En tu proyecto consumidor — AMBAS EN UN SOLO COMANDO
455
+ cd /ruta/a/tu-proyecto
456
+ npm link lf-pagebuilder-vue libreria-astro-lefebvre
457
+ ```
458
+
459
+ #### Deshacer los enlaces
460
+
461
+ ```bash
462
+ cd /ruta/a/tu-proyecto
463
+ npm unlink lf-pagebuilder-vue libreria-astro-lefebvre
464
+ npm install
465
+ ```
466
+
467
+ ### Watch mode
468
+
469
+ ```bash
470
+ npm run dev
471
+ ```
472
+
473
+ Ejecuta `vite build --watch` y recompila automáticamente cuando hay cambios.
474
+
475
+ ---
476
+
477
+ ### 📘 Publicar en npm
478
+
479
+ Para publicar una nueva versión:
480
+
481
+ 1. **Subir la versión** en `package.json`
482
+ 2. **Compilar y publicar**:
483
+ ```bash
484
+ npm run build:all
485
+ npm publish --access public
486
+ ```
487
+
488
+ #### Configurar acceso a npm (primera vez)
489
+
490
+ 1. Solicitar acceso al equipo `desarrollowebteamlef` en npm
491
+ 2. Crear un token de acceso en tu perfil de npm Access Tokens
492
+ 3. Añadir el token en `~/.npmrc`:
493
+ ```
494
+ //registry.npmjs.org/:_authToken=TU_TOKEN_AQUI
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Instalación y dependencias
500
+
501
+ ### Dependencias requeridas en el proyecto padre
502
+
503
+ ```bash
504
+ npm install lf-pagebuilder-vue@latest vue@^3.3.0 vuedraggable@^4.1.0
505
+ ```
506
+
507
+ ### Configuración en proyectos Astro
508
+
509
+ ```bash
510
+ npm install @astrojs/vue tailwindcss @tailwindcss/vite
511
+ ```
512
+
513
+ En `astro.config.mjs`:
514
+
515
+ ```js
516
+ import { defineConfig } from 'astro/config';
517
+ import vue from '@astrojs/vue';
518
+ import tailwindcss from '@tailwindcss/vite';
519
+
520
+ export default defineConfig({
521
+ integrations: [vue()],
522
+ vite: {
523
+ plugins: [tailwindcss()],
524
+ ssr: {
525
+ noExternal: ['libreria-astro-lefebvre', 'lf-pagebuilder-vue'],
526
+ },
527
+ },
528
+ });
529
+ ```