@bacano/sdk 1.1.12 → 1.1.15
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/CONTENT_TYPE_MANAGEMENT.md +284 -29
- package/README.md +18 -13
- package/content-management-types.d.ts +291 -0
- package/content-management.cjs +107 -5
- package/content-management.d.ts +2 -132
- package/content-management.js +107 -5
- package/package.json +1 -1
- package/skills/bacano-figma-web/SKILL.md +30 -10
- package/skills/bacano-figma-web/references/bacano-mapping.md +3 -3
- package/skills/bacano-figma-web/references/content-media.md +57 -0
- package/skills/bacano-figma-web/references/content-workflow.md +50 -14
- package/skills/bacano-figma-web/references/extraction-contract.md +3 -0
- package/skills/bacano-figma-web/references/implementation-quality.md +67 -0
- package/skills/bacano-figma-web/references/visual-validation.md +4 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# Gestión
|
|
1
|
+
# Gestión segura de Content para agentes
|
|
2
2
|
|
|
3
3
|
`@bacano/sdk/content-management` permite que una herramienta administrativa o
|
|
4
|
-
un agente autorizado consulte
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
un agente autorizado consulte y mantenga **tipos, componentes, grupos, listas
|
|
5
|
+
de opciones, entradas e imágenes** de Bacano Content. Cada capacidad requiere
|
|
6
|
+
un permiso explícito. El acceso no autoriza productos, ventas, clientes,
|
|
7
|
+
configuración general, GraphQL, SQL ni acceso directo a Storage.
|
|
7
8
|
|
|
8
9
|
Este contrato es independiente del cliente público del storefront. Debe usarse
|
|
9
10
|
solo desde un proceso local o servidor confiable; nunca desde el navegador ni
|
|
@@ -13,17 +14,34 @@ dentro del bundle del sitio web.
|
|
|
13
14
|
|
|
14
15
|
1. En Bacano, abre **Sitios web → Contenido → Tipos** o **Componentes**.
|
|
15
16
|
2. Selecciona **Conectar agente**.
|
|
16
|
-
3. Elige 15, 30 o 60 minutos y genera el acceso.
|
|
17
|
+
3. Elige el alcance, 15, 30 o 60 minutos y genera el acceso.
|
|
17
18
|
4. Copia el token al entorno local del agente.
|
|
18
19
|
|
|
19
|
-
El token está firmado, pertenece a una sola empresa y
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
El token está firmado con un secreto exclusivo, pertenece a una sola empresa y
|
|
21
|
+
usuario y está respaldado por una concesión revocable. Cada petición comprueba
|
|
22
|
+
la firma, vigencia, concesión, scopes exactos y que el usuario continúa
|
|
23
|
+
vinculado a la empresa con `websites.manage`. Al vencer, revocarse o perder ese
|
|
24
|
+
permiso deja de funcionar.
|
|
25
|
+
|
|
26
|
+
Scopes disponibles:
|
|
27
|
+
|
|
28
|
+
- `content_types:manage`: tipos, componentes, listas de opciones, referencias y
|
|
29
|
+
sus ciclos de vida.
|
|
30
|
+
- `content_entries:manage`: grupos, entradas en borrador y sus ciclos de vida.
|
|
31
|
+
- `content_entries:publish`: publicar y modificar contenido publicado; requiere
|
|
32
|
+
también `content_entries:manage`.
|
|
33
|
+
- `content_media:upload`: consultar imágenes de la empresa y cargar JPEG, PNG o
|
|
34
|
+
WebP. Requiere también `content_entries:manage`; no permite borrar archivos.
|
|
23
35
|
|
|
24
36
|
No guardes el token en Git, Figma, Content, logs, variables públicas ni archivos
|
|
25
37
|
que se empaqueten con el storefront.
|
|
26
38
|
|
|
39
|
+
Para desplegar Bacano, la API requiere
|
|
40
|
+
`CONTENT_MANAGEMENT_TOKEN_SECRET`: un secreto aleatorio de al menos 32
|
|
41
|
+
caracteres, exclusivo de esta capacidad y distinto de `API_SECRET`, JWT y los
|
|
42
|
+
secretos de Hasura. Debe configurarse como secreto del entorno; nunca se entrega
|
|
43
|
+
al agente. Rotarlo invalida inmediatamente todos los accesos temporales activos.
|
|
44
|
+
|
|
27
45
|
## Crear el cliente
|
|
28
46
|
|
|
29
47
|
```ts
|
|
@@ -35,7 +53,7 @@ const content = createContentManagementClient({
|
|
|
35
53
|
});
|
|
36
54
|
|
|
37
55
|
const context = await content.getContext();
|
|
38
|
-
console.log(context.companyId, context.expiresAt);
|
|
56
|
+
console.log(context.companyId, context.scopes, context.expiresAt);
|
|
39
57
|
```
|
|
40
58
|
|
|
41
59
|
El cliente ofrece:
|
|
@@ -47,6 +65,77 @@ El cliente ofrece:
|
|
|
47
65
|
- `createType(input)` y `updateType(idOrKey, input)`.
|
|
48
66
|
- `inspectLifecycle(idOrKey, operation)`.
|
|
49
67
|
- `applyLifecycle(idOrKey, operation)`.
|
|
68
|
+
- `listEntries(filters)`, `getEntry(idOrKey, locale?)`.
|
|
69
|
+
- `createEntry(input)` y `updateEntry(idOrKey, input)`.
|
|
70
|
+
- `inspectEntryLifecycle(idOrKey, operation)`.
|
|
71
|
+
- `applyEntryLifecycle(idOrKey, operation)`.
|
|
72
|
+
- `listGroups()`, `getGroup(idOrKey)`, `createGroup(input)` y
|
|
73
|
+
`updateGroup(idOrKey, input)`.
|
|
74
|
+
- `inspectGroupLifecycle(idOrKey, operation)` y
|
|
75
|
+
`applyGroupLifecycle(idOrKey, operation)`.
|
|
76
|
+
- `listOptionLists()`, `getOptionList(idOrKey)`, `createOptionList(input)` y
|
|
77
|
+
`updateOptionList(idOrKey, input)`.
|
|
78
|
+
- `inspectOptionListLifecycle(idOrKey, operation)` y
|
|
79
|
+
`applyOptionListLifecycle(idOrKey, operation)`.
|
|
80
|
+
- `listMedia(filters)` y `uploadImage(input)` para reutilizar o cargar imágenes
|
|
81
|
+
cuando el acceso incluya `content_media:upload`.
|
|
82
|
+
|
|
83
|
+
## Idioma, nombres y keys
|
|
84
|
+
|
|
85
|
+
Antes de proponer una estructura, resolver su idioma en este orden:
|
|
86
|
+
|
|
87
|
+
1. El idioma solicitado expresamente por el usuario.
|
|
88
|
+
2. La convención predominante del proyecto existente.
|
|
89
|
+
3. El idioma principal del website.
|
|
90
|
+
4. Si todavía existe una ambigüedad material, preguntar al usuario.
|
|
91
|
+
|
|
92
|
+
Para un website en español, los nombres visibles y las keys nuevas se crean en
|
|
93
|
+
español salvo instrucción contraria. Esta regla aplica a identificadores
|
|
94
|
+
definidos por la empresa; los valores reservados del SDK como `entry`,
|
|
95
|
+
`component`, `draft`, `website_content`, `text` o `repeater` no se traducen ni
|
|
96
|
+
se reemplazan por valores inventados.
|
|
97
|
+
|
|
98
|
+
Usar estas convenciones:
|
|
99
|
+
|
|
100
|
+
| Elemento | Convención | Ejemplo |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| Nombre visible | Lenguaje natural, con tildes | `Preguntas frecuentes` |
|
|
103
|
+
| Key de tipo, componente, sección o campo | `snake_case`, sin tildes | `preguntas_frecuentes` |
|
|
104
|
+
| Key o slug público de una entrada | `kebab-case`, sin tildes | `preguntas-frecuentes-inicio` |
|
|
105
|
+
|
|
106
|
+
Las keys deben describir el significado del contenido, no detalles accidentales
|
|
107
|
+
de su implementación visual. Preferir `titulo`, `descripcion`,
|
|
108
|
+
`imagen_principal`, `enlace_contacto`, `preguntas`, `planes` o `pasos`. Evitar
|
|
109
|
+
identificadores opacos como `data`, `content`, `items`, `field1`,
|
|
110
|
+
`step1_title` o `component_2` cuando existe un nombre semántico claro.
|
|
111
|
+
|
|
112
|
+
Para elementos repetidos, modelar un repetible y, cuando corresponda, un
|
|
113
|
+
componente semántico. Por ejemplo, usar `pasos` con un componente `paso` que
|
|
114
|
+
contenga `titulo` y `descripcion`, en vez de crear `paso1_titulo`,
|
|
115
|
+
`paso1_descripcion`, `paso2_titulo` y campos sucesivos.
|
|
116
|
+
|
|
117
|
+
Antes de crear algo nuevo:
|
|
118
|
+
|
|
119
|
+
1. Consultar `listTypes()` y `getReferences()`.
|
|
120
|
+
2. Reutilizar una estructura solo cuando conserva el mismo significado y la
|
|
121
|
+
misma forma, no únicamente porque algunos campos coinciden.
|
|
122
|
+
3. Dar un nombre neutral a una estructura reutilizable, por ejemplo
|
|
123
|
+
`seccion_preguntas_frecuentes`.
|
|
124
|
+
4. Incluir el contexto en el nombre cuando la estructura sea realmente
|
|
125
|
+
exclusiva, por ejemplo `calculadora_eficiencia_inicio`.
|
|
126
|
+
5. Evitar nombres demasiado genéricos como `contenido` y nombres que quedarían
|
|
127
|
+
engañosos al reutilizarse, como `contenedor_principal_servicios` para una
|
|
128
|
+
página de blog.
|
|
129
|
+
|
|
130
|
+
Una key activa es un contrato con el storefront. Nunca traducir, regenerar ni
|
|
131
|
+
renombrar automáticamente keys existentes. La convención de idioma aplica a
|
|
132
|
+
estructuras nuevas; cambiar una key utilizada requiere tratarlo como una
|
|
133
|
+
migración explícita y presentar su impacto. Tampoco crear sufijos como `_2` o
|
|
134
|
+
`-2` sin investigar primero el identificador existente.
|
|
135
|
+
|
|
136
|
+
Antes de ejecutar `createType()`, presentar al usuario el nombre, key, idioma,
|
|
137
|
+
uso, campos, componentes y decisión de creación o reutilización. Corregir
|
|
138
|
+
mezclas de idiomas y errores ortográficos antes de materializar el blueprint.
|
|
50
139
|
|
|
51
140
|
## Flujo seguro para un agente
|
|
52
141
|
|
|
@@ -71,13 +160,13 @@ como una eliminación estructural y puede requerir
|
|
|
71
160
|
|
|
72
161
|
```ts
|
|
73
162
|
await content.createType({
|
|
74
|
-
key: "
|
|
163
|
+
key: "boton_llamada_accion",
|
|
75
164
|
name: "Botón de llamada a la acción",
|
|
76
165
|
kind: "component",
|
|
77
166
|
status: "draft",
|
|
78
167
|
fields: [
|
|
79
168
|
{
|
|
80
|
-
key: "
|
|
169
|
+
key: "texto",
|
|
81
170
|
label: "Texto",
|
|
82
171
|
type: "text",
|
|
83
172
|
required: true,
|
|
@@ -85,7 +174,7 @@ await content.createType({
|
|
|
85
174
|
settings: { isVisible: true, maxLength: 80 },
|
|
86
175
|
},
|
|
87
176
|
{
|
|
88
|
-
key: "
|
|
177
|
+
key: "enlace",
|
|
89
178
|
label: "Enlace",
|
|
90
179
|
type: "link",
|
|
91
180
|
required: true,
|
|
@@ -96,38 +185,38 @@ await content.createType({
|
|
|
96
185
|
});
|
|
97
186
|
|
|
98
187
|
await content.createType({
|
|
99
|
-
key: "
|
|
188
|
+
key: "hero_inicio",
|
|
100
189
|
name: "Hero del inicio",
|
|
101
190
|
kind: "entry",
|
|
102
191
|
status: "draft",
|
|
103
192
|
usages: ["website_content"],
|
|
104
|
-
sections: [{ key: "
|
|
193
|
+
sections: [{ key: "contenido", title: "Contenido", sortOrder: 1 }],
|
|
105
194
|
fields: [
|
|
106
195
|
{
|
|
107
|
-
key: "
|
|
196
|
+
key: "titulo",
|
|
108
197
|
label: "Título",
|
|
109
198
|
type: "text",
|
|
110
199
|
required: true,
|
|
111
200
|
sortOrder: 1,
|
|
112
|
-
sectionKey: "
|
|
201
|
+
sectionKey: "contenido",
|
|
113
202
|
settings: { isVisible: true, maxLength: 120 },
|
|
114
203
|
},
|
|
115
204
|
{
|
|
116
|
-
key: "
|
|
205
|
+
key: "acciones",
|
|
117
206
|
label: "Acciones",
|
|
118
207
|
type: "repeater",
|
|
119
208
|
required: false,
|
|
120
209
|
sortOrder: 2,
|
|
121
|
-
sectionKey: "
|
|
210
|
+
sectionKey: "contenido",
|
|
122
211
|
settings: { isVisible: true, minRows: 0, maxRows: 3 },
|
|
123
212
|
children: [
|
|
124
213
|
{
|
|
125
|
-
key: "
|
|
214
|
+
key: "boton",
|
|
126
215
|
label: "Botón",
|
|
127
216
|
type: "component",
|
|
128
217
|
required: true,
|
|
129
218
|
sortOrder: 1,
|
|
130
|
-
componentKey: "
|
|
219
|
+
componentKey: "boton_llamada_accion",
|
|
131
220
|
settings: { isVisible: true },
|
|
132
221
|
},
|
|
133
222
|
],
|
|
@@ -166,10 +255,10 @@ otros usos editoriales son `website_content` y `product_additional`.
|
|
|
166
255
|
## Ciclo de vida
|
|
167
256
|
|
|
168
257
|
```ts
|
|
169
|
-
const preview = await content.inspectLifecycle("
|
|
258
|
+
const preview = await content.inspectLifecycle("hero_inicio", "delete");
|
|
170
259
|
|
|
171
260
|
if (preview.allowed && userConfirmed) {
|
|
172
|
-
await content.applyLifecycle("
|
|
261
|
+
await content.applyLifecycle("hero_inicio", "delete");
|
|
173
262
|
}
|
|
174
263
|
```
|
|
175
264
|
|
|
@@ -178,15 +267,181 @@ eliminar cuando existe contenido histórico. El backend aplica las mismas reglas
|
|
|
178
267
|
de dependencias, keys, componentes, secciones y cambios destructivos que el
|
|
179
268
|
backoffice; el SDK no crea un camino alterno.
|
|
180
269
|
|
|
270
|
+
## Entradas
|
|
271
|
+
|
|
272
|
+
Antes de crear una entrada, confirma que su tipo existe y que la estructura
|
|
273
|
+
aprobada ya está activa. Las entradas nacen en `draft` salvo que el usuario haya
|
|
274
|
+
concedido explícitamente `content_entries:publish`.
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
const existing = await content.listEntries({
|
|
278
|
+
contentType: "hero_inicio",
|
|
279
|
+
locale: "es-CO",
|
|
280
|
+
limit: 100,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const current = existing.entries.find(
|
|
284
|
+
(entry) => entry.key === "hero-principal-inicio",
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
if (!current) {
|
|
288
|
+
await content.createEntry({
|
|
289
|
+
contentType: "hero_inicio",
|
|
290
|
+
key: "hero-principal-inicio",
|
|
291
|
+
name: "Hero principal del inicio",
|
|
292
|
+
locale: "es-CO",
|
|
293
|
+
status: "draft",
|
|
294
|
+
content: {
|
|
295
|
+
titulo: "Encuentra lo mejor para cada etapa",
|
|
296
|
+
descripcion: "Compra productos seleccionados para tu familia.",
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
await content.updateEntry(current.id, {
|
|
301
|
+
contentType: current.contentType.id,
|
|
302
|
+
key: current.key,
|
|
303
|
+
name: current.name,
|
|
304
|
+
locale: current.locale,
|
|
305
|
+
status: "draft",
|
|
306
|
+
content: {
|
|
307
|
+
...current.content,
|
|
308
|
+
titulo: "Encuentra lo mejor para cada etapa",
|
|
309
|
+
},
|
|
310
|
+
expectedUpdatedAt: current.updatedAt,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Reglas obligatorias:
|
|
316
|
+
|
|
317
|
+
1. Inventariar por tipo, key y locale antes de crear; nunca fusionar por nombre.
|
|
318
|
+
2. Usar `expectedUpdatedAt` al actualizar. Un conflicto exige volver a consultar
|
|
319
|
+
y presentar el cambio; no se sobrescribe silenciosamente.
|
|
320
|
+
3. Enviar el documento completo que debe permanecer. `updateEntry()` reemplaza
|
|
321
|
+
el contenido de la entrada después de aplicar las validaciones oficiales.
|
|
322
|
+
4. Reutilizar únicamente `fileId` pertenecientes a la misma empresa. Con
|
|
323
|
+
`content_media:upload`, resolverlos mediante `listMedia()` o
|
|
324
|
+
`uploadImage()`; nunca relacionar URLs arbitrarias.
|
|
325
|
+
5. Rich text usa el JSON controlado por Bacano, nunca HTML crudo.
|
|
326
|
+
6. Publicar, retirar o modificar una entrada publicada requiere
|
|
327
|
+
`content_entries:publish` y aprobación humana explícita.
|
|
328
|
+
7. Inspeccionar el ciclo de vida antes de archivar, restaurar o eliminar.
|
|
329
|
+
|
|
330
|
+
La API reutiliza el mismo servicio de guardado del backoffice: campos
|
|
331
|
+
obligatorios, componentes anidados, repetibles, listas, referencias, rich text,
|
|
332
|
+
media y pertenencia a la empresa se validan de forma autoritativa.
|
|
333
|
+
|
|
334
|
+
## Imágenes
|
|
335
|
+
|
|
336
|
+
Antes de cargar, busca por nombre o texto alternativo para reutilizar la
|
|
337
|
+
biblioteca existente:
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
import { readFile } from "node:fs/promises";
|
|
341
|
+
|
|
342
|
+
const existing = await content.listMedia({ search: "hero juguetes" });
|
|
343
|
+
|
|
344
|
+
const hero = existing.items[0] ?? await content.uploadImage({
|
|
345
|
+
file: await readFile("./hero-juguetes.png"),
|
|
346
|
+
fileName: "Hero juguetes para bebé",
|
|
347
|
+
altText: "Niña jugando con juguetes educativos",
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
console.log(hero.fileId, hero.width, hero.height);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
`uploadImage()` envía los bytes al endpoint administrativo de Bacano. El
|
|
354
|
+
servidor comprueba el contenido real, corrige orientación, elimina metadatos no
|
|
355
|
+
necesarios, normaliza a WebP y registra nombre, MIME, tamaño y dimensiones. El
|
|
356
|
+
límite de entrada es 10 MB y 100 megapíxeles. No se admiten animaciones,
|
|
357
|
+
archivos corruptos, SVG, video ni documentos en esta primera versión.
|
|
358
|
+
|
|
359
|
+
El nombre entregado debe describir el asset; Bacano lo normaliza a un filename
|
|
360
|
+
SEO seguro y agrega un sufijo corto para evitar colisiones. Un SHA-256 de los
|
|
361
|
+
bytes WebP normalizados permite reutilizar duplicados dentro de la misma
|
|
362
|
+
empresa. Nunca se deduplica entre empresas.
|
|
363
|
+
|
|
364
|
+
El `altText` de la biblioteca es solo un fallback. Al asociar una imagen a una
|
|
365
|
+
entrada, conserva el texto alternativo contextual que corresponda a ese uso;
|
|
366
|
+
una misma imagen puede significar cosas distintas en dos secciones. Para una
|
|
367
|
+
imagen decorativa el contenido final debe usar `alt=""`.
|
|
368
|
+
|
|
369
|
+
El agente no puede reemplazar ni borrar físicamente media. Una edición genera
|
|
370
|
+
otro asset y cambia explícitamente el `fileId` de la entrada. Esto evita romper
|
|
371
|
+
productos, servicios, branding u otras entradas que todavía usen el archivo.
|
|
372
|
+
|
|
373
|
+
## Grupos y listas de opciones
|
|
374
|
+
|
|
375
|
+
Los grupos organizan entradas y usan el scope `content_entries:manage`. Las
|
|
376
|
+
listas proporcionan valores controlados para campos `select`, usan
|
|
377
|
+
`content_types:manage` y conservan sus relaciones padre-hijo.
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
const group = await content.createGroup({
|
|
381
|
+
key: "pagina-principal",
|
|
382
|
+
name: "Página principal",
|
|
383
|
+
status: "active",
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const materials = await content.createOptionList({
|
|
387
|
+
key: "materiales",
|
|
388
|
+
name: "Materiales",
|
|
389
|
+
status: "draft",
|
|
390
|
+
isExtensible: false,
|
|
391
|
+
items: [
|
|
392
|
+
{ value: "algodon", label: "Algodón", sortOrder: 1 },
|
|
393
|
+
{ value: "lino", label: "Lino", sortOrder: 2 },
|
|
394
|
+
],
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
await content.updateOptionList(materials.id, {
|
|
398
|
+
key: materials.key,
|
|
399
|
+
name: materials.name,
|
|
400
|
+
status: "active",
|
|
401
|
+
isExtensible: materials.isExtensible,
|
|
402
|
+
items: materials.items.map((item) => ({
|
|
403
|
+
id: item.id,
|
|
404
|
+
value: item.value,
|
|
405
|
+
label: item.label,
|
|
406
|
+
parentItemId: item.parentItemId,
|
|
407
|
+
sortOrder: item.sortOrder,
|
|
408
|
+
isActive: item.isActive,
|
|
409
|
+
metadata: item.metadata,
|
|
410
|
+
})),
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
`updateGroup()` y `updateOptionList()` reciben el documento completo que debe
|
|
415
|
+
permanecer. Las keys existentes son inmutables. Para retirar elementos,
|
|
416
|
+
archivar, restaurar o eliminar, primero usa la operación `inspect*Lifecycle()`.
|
|
417
|
+
El backend bloquea desactivar o retirar opciones que todavía usan listas
|
|
418
|
+
dependientes y exige `confirmDestructiveChanges` cuando corresponde; nunca
|
|
419
|
+
omitas opciones existentes por accidente en una actualización.
|
|
420
|
+
|
|
421
|
+
## Seguridad y auditoría
|
|
422
|
+
|
|
423
|
+
- El `companyId` y el usuario provienen del token; no se aceptan desde el body.
|
|
424
|
+
- Cada consulta y mutación está filtrada por la empresa firmada.
|
|
425
|
+
- UUIDs o keys de otra empresa responden como inexistentes y no se modifican.
|
|
426
|
+
- Las concesiones pueden revocarse individualmente desde Bacano.
|
|
427
|
+
- Emisión, revocación y mutaciones quedan en una auditoría inmutable sin guardar
|
|
428
|
+
el bearer token ni el documento completo.
|
|
429
|
+
- Las respuestas del endpoint usan `no-store`, límites de tamaño y rate limit.
|
|
430
|
+
- Las cargas tienen un límite adicional por concesión; no aceptan importación
|
|
431
|
+
por URL ni exponen credenciales o endpoints internos de Storage.
|
|
432
|
+
- Los tokens v2 no son compatibles con tokens anteriores y tienen como máximo
|
|
433
|
+
60 minutos de vigencia.
|
|
434
|
+
|
|
181
435
|
## Límites de esta versión
|
|
182
436
|
|
|
183
|
-
-
|
|
184
|
-
|
|
185
|
-
- No
|
|
437
|
+
- La carga administrativa solo admite imágenes raster JPEG, PNG y WebP. SVG,
|
|
438
|
+
animaciones, video y documentos continúan en el backoffice.
|
|
439
|
+
- No elimina ni reemplaza físicamente archivos de la biblioteca.
|
|
186
440
|
- No funciona desde el cliente público del storefront.
|
|
187
441
|
- No acepta GraphQL directo ni credenciales administrativas permanentes.
|
|
442
|
+
- No previsualiza borradores desde el storefront público.
|
|
188
443
|
|
|
189
444
|
El resultado esperado del flujo Figma es: el agente propone el blueprint, el
|
|
190
|
-
usuario habilita
|
|
191
|
-
|
|
192
|
-
|
|
445
|
+
usuario habilita únicamente los scopes necesarios, el agente crea o ajusta la
|
|
446
|
+
estructura y prepara entradas en borrador, y una persona revisa antes de
|
|
447
|
+
publicar.
|
package/README.md
CHANGED
|
@@ -21,20 +21,24 @@ React hooks are available from `@bacano/sdk/react` and require `react >= 18`
|
|
|
21
21
|
as a peer dependency. The core client has no runtime dependencies and works
|
|
22
22
|
without React.
|
|
23
23
|
|
|
24
|
-
### Gestión de
|
|
24
|
+
### Gestión de Bacano Content para agentes
|
|
25
25
|
|
|
26
|
-
Un agente autorizado puede consultar
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
Un agente autorizado puede consultar y mantener tipos, componentes, grupos,
|
|
27
|
+
listas de opciones y entradas con el entrypoint administrativo
|
|
28
|
+
`@bacano/sdk/content-management`. Cada capacidad exige un scope explícito. El
|
|
29
|
+
acceso se genera temporalmente desde Bacano, está limitado a una empresa y
|
|
30
|
+
nunca debe incluirse en el storefront:
|
|
30
31
|
|
|
31
32
|
```ts
|
|
32
33
|
import { createContentManagementClient } from "@bacano/sdk/content-management";
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
Lee [`CONTENT_TYPE_MANAGEMENT.md`](./CONTENT_TYPE_MANAGEMENT.md) antes de usarlo.
|
|
36
|
-
La guía contiene el flujo de autorización, contratos de campos,
|
|
37
|
-
cambios destructivos y límites de seguridad.
|
|
37
|
+
La guía contiene el flujo de autorización, contratos de campos, entradas y
|
|
38
|
+
media, estados, referencias, cambios destructivos y límites de seguridad. Con
|
|
39
|
+
el scope `content_media:upload`, un agente puede buscar imágenes existentes y
|
|
40
|
+
cargar JPEG, PNG o WebP mediante el contrato oficial; Bacano valida los bytes,
|
|
41
|
+
normaliza a WebP y registra el resultado en la biblioteca de la empresa.
|
|
38
42
|
|
|
39
43
|
### Auditoría del storefront
|
|
40
44
|
|
|
@@ -62,8 +66,8 @@ el usuario o agente invoca la skill.
|
|
|
62
66
|
### Implementación desde Figma
|
|
63
67
|
|
|
64
68
|
El SDK también incluye `bacano-figma-web`, la skill oficial para convertir un
|
|
65
|
-
diseño Figma en una web informativa o
|
|
66
|
-
First y contratos públicos de Bacano:
|
|
69
|
+
diseño Figma en una web informativa, tienda o sitio híbrido que conserve el
|
|
70
|
+
cascarón, Static First y contratos públicos de Bacano:
|
|
67
71
|
|
|
68
72
|
```bash
|
|
69
73
|
pnpm exec bacano-figma-web install --agent all
|
|
@@ -82,10 +86,11 @@ la presencia del token REST. El OAuth del MCP remoto debe confirmarse dentro del
|
|
|
82
86
|
agente con las herramientas de Figma. La extracción se conserva como snapshot
|
|
83
87
|
normalizado. Cuando el equipo de Content y desarrollo trabajan en tiempos
|
|
84
88
|
distintos, la skill genera un blueprint de entrega y permite fixtures locales
|
|
85
|
-
con el mismo contrato de los componentes.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
con el mismo contrato de los componentes. Con autorización temporal y scopes
|
|
90
|
+
explícitos, puede crear o ajustar tipos, componentes, grupos, listas y entradas
|
|
91
|
+
mediante el entrypoint administrativo oficial. Las entradas nacen en borrador;
|
|
92
|
+
publicar exige permiso y aprobación humana. Un fixture activo en producción
|
|
93
|
+
bloquea la aprobación del sitio.
|
|
89
94
|
|
|
90
95
|
El snapshot puede validarse con:
|
|
91
96
|
|