@bacano/sdk 1.1.10 → 1.1.13
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 +401 -0
- package/README.md +25 -4
- package/content-management-types.d.ts +260 -0
- package/content-management.cjs +199 -0
- package/content-management.d.ts +14 -0
- package/content-management.js +178 -0
- package/package.json +6 -1
- package/skills/bacano-figma-web/SKILL.md +26 -9
- package/skills/bacano-figma-web/references/bacano-mapping.md +3 -3
- package/skills/bacano-figma-web/references/content-workflow.md +62 -18
- 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
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Gestión segura de Content para agentes
|
|
2
|
+
|
|
3
|
+
`@bacano/sdk/content-management` permite que una herramienta administrativa o
|
|
4
|
+
un agente autorizado consulte y mantenga **tipos, componentes, grupos, listas
|
|
5
|
+
de opciones y entradas** de Bacano Content. Cada capacidad requiere un permiso explícito. El acceso no
|
|
6
|
+
autoriza productos, ventas, clientes, configuración general, GraphQL, SQL ni
|
|
7
|
+
Storage.
|
|
8
|
+
|
|
9
|
+
Este contrato es independiente del cliente público del storefront. Debe usarse
|
|
10
|
+
solo desde un proceso local o servidor confiable; nunca desde el navegador ni
|
|
11
|
+
dentro del bundle del sitio web.
|
|
12
|
+
|
|
13
|
+
## Activar un acceso temporal
|
|
14
|
+
|
|
15
|
+
1. En Bacano, abre **Sitios web → Contenido → Tipos** o **Componentes**.
|
|
16
|
+
2. Selecciona **Conectar agente**.
|
|
17
|
+
3. Elige el alcance, 15, 30 o 60 minutos y genera el acceso.
|
|
18
|
+
4. Copia el token al entorno local del agente.
|
|
19
|
+
|
|
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`: reservado para una fase posterior; no se concede
|
|
34
|
+
mientras la carga administrativa de media no esté implementada.
|
|
35
|
+
|
|
36
|
+
No guardes el token en Git, Figma, Content, logs, variables públicas ni archivos
|
|
37
|
+
que se empaqueten con el storefront.
|
|
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
|
+
|
|
45
|
+
## Crear el cliente
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { createContentManagementClient } from "@bacano/sdk/content-management";
|
|
49
|
+
|
|
50
|
+
const content = createContentManagementClient({
|
|
51
|
+
apiUrl: process.env.BACANO_API_URL!,
|
|
52
|
+
accessToken: process.env.BACANO_CONTENT_ACCESS_TOKEN!,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const context = await content.getContext();
|
|
56
|
+
console.log(context.companyId, context.scopes, context.expiresAt);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
El cliente ofrece:
|
|
60
|
+
|
|
61
|
+
- `getContext()` para confirmar empresa, alcance y vencimiento.
|
|
62
|
+
- `getReferences()` para consultar categorías, subcategorías, listas de opciones
|
|
63
|
+
y unidades de medida válidas.
|
|
64
|
+
- `listTypes()` y `getType(idOrKey)`.
|
|
65
|
+
- `createType(input)` y `updateType(idOrKey, input)`.
|
|
66
|
+
- `inspectLifecycle(idOrKey, operation)`.
|
|
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
|
+
|
|
81
|
+
## Idioma, nombres y keys
|
|
82
|
+
|
|
83
|
+
Antes de proponer una estructura, resolver su idioma en este orden:
|
|
84
|
+
|
|
85
|
+
1. El idioma solicitado expresamente por el usuario.
|
|
86
|
+
2. La convención predominante del proyecto existente.
|
|
87
|
+
3. El idioma principal del website.
|
|
88
|
+
4. Si todavía existe una ambigüedad material, preguntar al usuario.
|
|
89
|
+
|
|
90
|
+
Para un website en español, los nombres visibles y las keys nuevas se crean en
|
|
91
|
+
español salvo instrucción contraria. Esta regla aplica a identificadores
|
|
92
|
+
definidos por la empresa; los valores reservados del SDK como `entry`,
|
|
93
|
+
`component`, `draft`, `website_content`, `text` o `repeater` no se traducen ni
|
|
94
|
+
se reemplazan por valores inventados.
|
|
95
|
+
|
|
96
|
+
Usar estas convenciones:
|
|
97
|
+
|
|
98
|
+
| Elemento | Convención | Ejemplo |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| Nombre visible | Lenguaje natural, con tildes | `Preguntas frecuentes` |
|
|
101
|
+
| Key de tipo, componente, sección o campo | `snake_case`, sin tildes | `preguntas_frecuentes` |
|
|
102
|
+
| Key o slug público de una entrada | `kebab-case`, sin tildes | `preguntas-frecuentes-inicio` |
|
|
103
|
+
|
|
104
|
+
Las keys deben describir el significado del contenido, no detalles accidentales
|
|
105
|
+
de su implementación visual. Preferir `titulo`, `descripcion`,
|
|
106
|
+
`imagen_principal`, `enlace_contacto`, `preguntas`, `planes` o `pasos`. Evitar
|
|
107
|
+
identificadores opacos como `data`, `content`, `items`, `field1`,
|
|
108
|
+
`step1_title` o `component_2` cuando existe un nombre semántico claro.
|
|
109
|
+
|
|
110
|
+
Para elementos repetidos, modelar un repetible y, cuando corresponda, un
|
|
111
|
+
componente semántico. Por ejemplo, usar `pasos` con un componente `paso` que
|
|
112
|
+
contenga `titulo` y `descripcion`, en vez de crear `paso1_titulo`,
|
|
113
|
+
`paso1_descripcion`, `paso2_titulo` y campos sucesivos.
|
|
114
|
+
|
|
115
|
+
Antes de crear algo nuevo:
|
|
116
|
+
|
|
117
|
+
1. Consultar `listTypes()` y `getReferences()`.
|
|
118
|
+
2. Reutilizar una estructura solo cuando conserva el mismo significado y la
|
|
119
|
+
misma forma, no únicamente porque algunos campos coinciden.
|
|
120
|
+
3. Dar un nombre neutral a una estructura reutilizable, por ejemplo
|
|
121
|
+
`seccion_preguntas_frecuentes`.
|
|
122
|
+
4. Incluir el contexto en el nombre cuando la estructura sea realmente
|
|
123
|
+
exclusiva, por ejemplo `calculadora_eficiencia_inicio`.
|
|
124
|
+
5. Evitar nombres demasiado genéricos como `contenido` y nombres que quedarían
|
|
125
|
+
engañosos al reutilizarse, como `contenedor_principal_servicios` para una
|
|
126
|
+
página de blog.
|
|
127
|
+
|
|
128
|
+
Una key activa es un contrato con el storefront. Nunca traducir, regenerar ni
|
|
129
|
+
renombrar automáticamente keys existentes. La convención de idioma aplica a
|
|
130
|
+
estructuras nuevas; cambiar una key utilizada requiere tratarlo como una
|
|
131
|
+
migración explícita y presentar su impacto. Tampoco crear sufijos como `_2` o
|
|
132
|
+
`-2` sin investigar primero el identificador existente.
|
|
133
|
+
|
|
134
|
+
Antes de ejecutar `createType()`, presentar al usuario el nombre, key, idioma,
|
|
135
|
+
uso, campos, componentes y decisión de creación o reutilización. Corregir
|
|
136
|
+
mezclas de idiomas y errores ortográficos antes de materializar el blueprint.
|
|
137
|
+
|
|
138
|
+
## Flujo seguro para un agente
|
|
139
|
+
|
|
140
|
+
1. Ejecutar `getContext()` y confirmar la empresa con el usuario.
|
|
141
|
+
2. Ejecutar `listTypes()` y `getReferences()` antes de proponer cambios.
|
|
142
|
+
3. Reutilizar tipos, componentes, listas y keys existentes cuando correspondan.
|
|
143
|
+
4. Crear primero los componentes que serán referenciados por otros tipos.
|
|
144
|
+
5. Crear o actualizar en `draft`.
|
|
145
|
+
6. Volver a consultar el tipo y presentar un resumen del resultado.
|
|
146
|
+
7. Activarlo únicamente cuando el usuario haya aprobado la estructura.
|
|
147
|
+
8. Para archivar, restaurar o eliminar, ejecutar primero
|
|
148
|
+
`inspectLifecycle()` y mostrar dependencias y bloqueos.
|
|
149
|
+
9. Ejecutar `applyLifecycle()` solo después de una confirmación explícita.
|
|
150
|
+
|
|
151
|
+
`updateType()` reemplaza la definición completa. Antes de usarlo debes obtener
|
|
152
|
+
el tipo con `getType()`, conservar los IDs de campos existentes y enviar todos
|
|
153
|
+
los campos y secciones que deban permanecer. La omisión de un campo se interpreta
|
|
154
|
+
como una eliminación estructural y puede requerir
|
|
155
|
+
`confirmDestructiveChanges: true`.
|
|
156
|
+
|
|
157
|
+
## Ejemplo: componente y tipo editorial
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
await content.createType({
|
|
161
|
+
key: "boton_llamada_accion",
|
|
162
|
+
name: "Botón de llamada a la acción",
|
|
163
|
+
kind: "component",
|
|
164
|
+
status: "draft",
|
|
165
|
+
fields: [
|
|
166
|
+
{
|
|
167
|
+
key: "texto",
|
|
168
|
+
label: "Texto",
|
|
169
|
+
type: "text",
|
|
170
|
+
required: true,
|
|
171
|
+
sortOrder: 1,
|
|
172
|
+
settings: { isVisible: true, maxLength: 80 },
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
key: "enlace",
|
|
176
|
+
label: "Enlace",
|
|
177
|
+
type: "link",
|
|
178
|
+
required: true,
|
|
179
|
+
sortOrder: 2,
|
|
180
|
+
settings: { isVisible: true },
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
await content.createType({
|
|
186
|
+
key: "hero_inicio",
|
|
187
|
+
name: "Hero del inicio",
|
|
188
|
+
kind: "entry",
|
|
189
|
+
status: "draft",
|
|
190
|
+
usages: ["website_content"],
|
|
191
|
+
sections: [{ key: "contenido", title: "Contenido", sortOrder: 1 }],
|
|
192
|
+
fields: [
|
|
193
|
+
{
|
|
194
|
+
key: "titulo",
|
|
195
|
+
label: "Título",
|
|
196
|
+
type: "text",
|
|
197
|
+
required: true,
|
|
198
|
+
sortOrder: 1,
|
|
199
|
+
sectionKey: "contenido",
|
|
200
|
+
settings: { isVisible: true, maxLength: 120 },
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
key: "acciones",
|
|
204
|
+
label: "Acciones",
|
|
205
|
+
type: "repeater",
|
|
206
|
+
required: false,
|
|
207
|
+
sortOrder: 2,
|
|
208
|
+
sectionKey: "contenido",
|
|
209
|
+
settings: { isVisible: true, minRows: 0, maxRows: 3 },
|
|
210
|
+
children: [
|
|
211
|
+
{
|
|
212
|
+
key: "boton",
|
|
213
|
+
label: "Botón",
|
|
214
|
+
type: "component",
|
|
215
|
+
required: true,
|
|
216
|
+
sortOrder: 1,
|
|
217
|
+
componentKey: "boton_llamada_accion",
|
|
218
|
+
settings: { isVisible: true },
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Ajustes de campos
|
|
227
|
+
|
|
228
|
+
Todos los campos aceptan `helpText`, `defaultValue`, `isPrivate` e
|
|
229
|
+
`isVisible`. Usa además los ajustes correspondientes al tipo:
|
|
230
|
+
|
|
231
|
+
| Tipo | Ajustes principales |
|
|
232
|
+
| --- | --- |
|
|
233
|
+
| `text`, `textarea`, `slug` | `minLength`, `maxLength`, `pattern` |
|
|
234
|
+
| `rich_text`, `rich_text_full` | `richTextMode` |
|
|
235
|
+
| `media` | `mediaMultiple`, `acceptedMediaKinds` |
|
|
236
|
+
| `date` | `dateMode`: `date` o `datetime` |
|
|
237
|
+
| `number` | `min`, `max`, `step`, `precision` |
|
|
238
|
+
| `measure` | `measureDimensionCode`, `measureAllowedUnitCodes`, `measurePublicUnitCode` |
|
|
239
|
+
| `select` | `selectSource`, `selectOptions`, `selectSourceKey`, `selectMultiple` |
|
|
240
|
+
| `reference` | `referenceType`, `referenceContentTypeKey`, `referenceMultiple` |
|
|
241
|
+
| `repeater` | `minRows`, `maxRows` y `children` |
|
|
242
|
+
| `component` | `componentKey` |
|
|
243
|
+
|
|
244
|
+
Para una lista administrable usa `selectSource: "option_list"` y una key
|
|
245
|
+
devuelta por `getReferences().optionLists`. Para medidas usa exclusivamente los
|
|
246
|
+
códigos devueltos por `getReferences().measureDimensions`; no inventes unidades.
|
|
247
|
+
|
|
248
|
+
Una ficha técnica usa `usages: ["product_specification"]` y al menos un alcance
|
|
249
|
+
en `specificationScopes`. Sus campos técnicos pueden declarar
|
|
250
|
+
`isFilterable: true` únicamente cuando las reglas de Bacano lo permitan. Los
|
|
251
|
+
otros usos editoriales son `website_content` y `product_additional`.
|
|
252
|
+
|
|
253
|
+
## Ciclo de vida
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const preview = await content.inspectLifecycle("hero_inicio", "delete");
|
|
257
|
+
|
|
258
|
+
if (preview.allowed && userConfirmed) {
|
|
259
|
+
await content.applyLifecycle("hero_inicio", "delete");
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Nunca ignores `dependencies`, `allowed` o `errors`. Archivar es preferible a
|
|
264
|
+
eliminar cuando existe contenido histórico. El backend aplica las mismas reglas
|
|
265
|
+
de dependencias, keys, componentes, secciones y cambios destructivos que el
|
|
266
|
+
backoffice; el SDK no crea un camino alterno.
|
|
267
|
+
|
|
268
|
+
## Entradas
|
|
269
|
+
|
|
270
|
+
Antes de crear una entrada, confirma que su tipo existe y que la estructura
|
|
271
|
+
aprobada ya está activa. Las entradas nacen en `draft` salvo que el usuario haya
|
|
272
|
+
concedido explícitamente `content_entries:publish`.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
const existing = await content.listEntries({
|
|
276
|
+
contentType: "hero_inicio",
|
|
277
|
+
locale: "es-CO",
|
|
278
|
+
limit: 100,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const current = existing.entries.find(
|
|
282
|
+
(entry) => entry.key === "hero-principal-inicio",
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (!current) {
|
|
286
|
+
await content.createEntry({
|
|
287
|
+
contentType: "hero_inicio",
|
|
288
|
+
key: "hero-principal-inicio",
|
|
289
|
+
name: "Hero principal del inicio",
|
|
290
|
+
locale: "es-CO",
|
|
291
|
+
status: "draft",
|
|
292
|
+
content: {
|
|
293
|
+
titulo: "Encuentra lo mejor para cada etapa",
|
|
294
|
+
descripcion: "Compra productos seleccionados para tu familia.",
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
} else {
|
|
298
|
+
await content.updateEntry(current.id, {
|
|
299
|
+
contentType: current.contentType.id,
|
|
300
|
+
key: current.key,
|
|
301
|
+
name: current.name,
|
|
302
|
+
locale: current.locale,
|
|
303
|
+
status: "draft",
|
|
304
|
+
content: {
|
|
305
|
+
...current.content,
|
|
306
|
+
titulo: "Encuentra lo mejor para cada etapa",
|
|
307
|
+
},
|
|
308
|
+
expectedUpdatedAt: current.updatedAt,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Reglas obligatorias:
|
|
314
|
+
|
|
315
|
+
1. Inventariar por tipo, key y locale antes de crear; nunca fusionar por nombre.
|
|
316
|
+
2. Usar `expectedUpdatedAt` al actualizar. Un conflicto exige volver a consultar
|
|
317
|
+
y presentar el cambio; no se sobrescribe silenciosamente.
|
|
318
|
+
3. Enviar el documento completo que debe permanecer. `updateEntry()` reemplaza
|
|
319
|
+
el contenido de la entrada después de aplicar las validaciones oficiales.
|
|
320
|
+
4. Reutilizar únicamente `fileId` ya existentes y pertenecientes a la misma
|
|
321
|
+
empresa. Este acceso no sube media ni acepta URLs arbitrarias.
|
|
322
|
+
5. Rich text usa el JSON controlado por Bacano, nunca HTML crudo.
|
|
323
|
+
6. Publicar, retirar o modificar una entrada publicada requiere
|
|
324
|
+
`content_entries:publish` y aprobación humana explícita.
|
|
325
|
+
7. Inspeccionar el ciclo de vida antes de archivar, restaurar o eliminar.
|
|
326
|
+
|
|
327
|
+
La API reutiliza el mismo servicio de guardado del backoffice: campos
|
|
328
|
+
obligatorios, componentes anidados, repetibles, listas, referencias, rich text,
|
|
329
|
+
media y pertenencia a la empresa se validan de forma autoritativa.
|
|
330
|
+
|
|
331
|
+
## Grupos y listas de opciones
|
|
332
|
+
|
|
333
|
+
Los grupos organizan entradas y usan el scope `content_entries:manage`. Las
|
|
334
|
+
listas proporcionan valores controlados para campos `select`, usan
|
|
335
|
+
`content_types:manage` y conservan sus relaciones padre-hijo.
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
const group = await content.createGroup({
|
|
339
|
+
key: "pagina-principal",
|
|
340
|
+
name: "Página principal",
|
|
341
|
+
status: "active",
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const materials = await content.createOptionList({
|
|
345
|
+
key: "materiales",
|
|
346
|
+
name: "Materiales",
|
|
347
|
+
status: "draft",
|
|
348
|
+
isExtensible: false,
|
|
349
|
+
items: [
|
|
350
|
+
{ value: "algodon", label: "Algodón", sortOrder: 1 },
|
|
351
|
+
{ value: "lino", label: "Lino", sortOrder: 2 },
|
|
352
|
+
],
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
await content.updateOptionList(materials.id, {
|
|
356
|
+
key: materials.key,
|
|
357
|
+
name: materials.name,
|
|
358
|
+
status: "active",
|
|
359
|
+
isExtensible: materials.isExtensible,
|
|
360
|
+
items: materials.items.map((item) => ({
|
|
361
|
+
id: item.id,
|
|
362
|
+
value: item.value,
|
|
363
|
+
label: item.label,
|
|
364
|
+
parentItemId: item.parentItemId,
|
|
365
|
+
sortOrder: item.sortOrder,
|
|
366
|
+
isActive: item.isActive,
|
|
367
|
+
metadata: item.metadata,
|
|
368
|
+
})),
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
`updateGroup()` y `updateOptionList()` reciben el documento completo que debe
|
|
373
|
+
permanecer. Las keys existentes son inmutables. Para retirar elementos,
|
|
374
|
+
archivar, restaurar o eliminar, primero usa la operación `inspect*Lifecycle()`.
|
|
375
|
+
El backend bloquea desactivar o retirar opciones que todavía usan listas
|
|
376
|
+
dependientes y exige `confirmDestructiveChanges` cuando corresponde; nunca
|
|
377
|
+
omitas opciones existentes por accidente en una actualización.
|
|
378
|
+
|
|
379
|
+
## Seguridad y auditoría
|
|
380
|
+
|
|
381
|
+
- El `companyId` y el usuario provienen del token; no se aceptan desde el body.
|
|
382
|
+
- Cada consulta y mutación está filtrada por la empresa firmada.
|
|
383
|
+
- UUIDs o keys de otra empresa responden como inexistentes y no se modifican.
|
|
384
|
+
- Las concesiones pueden revocarse individualmente desde Bacano.
|
|
385
|
+
- Emisión, revocación y mutaciones quedan en una auditoría inmutable sin guardar
|
|
386
|
+
el bearer token ni el documento completo.
|
|
387
|
+
- Las respuestas del endpoint usan `no-store`, límites de tamaño y rate limit.
|
|
388
|
+
- Los tokens v2 no son compatibles con tokens anteriores y tienen como máximo
|
|
389
|
+
60 minutos de vigencia.
|
|
390
|
+
|
|
391
|
+
## Límites de esta versión
|
|
392
|
+
|
|
393
|
+
- No carga archivos ni crea media; puede relacionar media existente validada.
|
|
394
|
+
- No funciona desde el cliente público del storefront.
|
|
395
|
+
- No acepta GraphQL directo ni credenciales administrativas permanentes.
|
|
396
|
+
- No previsualiza borradores desde el storefront público.
|
|
397
|
+
|
|
398
|
+
El resultado esperado del flujo Figma es: el agente propone el blueprint, el
|
|
399
|
+
usuario habilita únicamente los scopes necesarios, el agente crea o ajusta la
|
|
400
|
+
estructura y prepara entradas en borrador, y una persona revisa antes de
|
|
401
|
+
publicar.
|
package/README.md
CHANGED
|
@@ -21,6 +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 Bacano Content para agentes
|
|
25
|
+
|
|
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:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createContentManagementClient } from "@bacano/sdk/content-management";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Lee [`CONTENT_TYPE_MANAGEMENT.md`](./CONTENT_TYPE_MANAGEMENT.md) antes de usarlo.
|
|
37
|
+
La guía contiene el flujo de autorización, contratos de campos y entradas,
|
|
38
|
+
estados, referencias, cambios destructivos y límites de seguridad. La carga de
|
|
39
|
+
media continúa en el backoffice hasta que exista un contrato administrativo
|
|
40
|
+
oficial para ella.
|
|
41
|
+
|
|
24
42
|
### Auditoría del storefront
|
|
25
43
|
|
|
26
44
|
El SDK incluye la skill oficial `bacano-web`. Instalarla al iniciar un
|
|
@@ -47,8 +65,8 @@ el usuario o agente invoca la skill.
|
|
|
47
65
|
### Implementación desde Figma
|
|
48
66
|
|
|
49
67
|
El SDK también incluye `bacano-figma-web`, la skill oficial para convertir un
|
|
50
|
-
diseño Figma en una web informativa o
|
|
51
|
-
First y contratos públicos de Bacano:
|
|
68
|
+
diseño Figma en una web informativa, tienda o sitio híbrido que conserve el
|
|
69
|
+
cascarón, Static First y contratos públicos de Bacano:
|
|
52
70
|
|
|
53
71
|
```bash
|
|
54
72
|
pnpm exec bacano-figma-web install --agent all
|
|
@@ -67,8 +85,11 @@ la presencia del token REST. El OAuth del MCP remoto debe confirmarse dentro del
|
|
|
67
85
|
agente con las herramientas de Figma. La extracción se conserva como snapshot
|
|
68
86
|
normalizado. Cuando el equipo de Content y desarrollo trabajan en tiempos
|
|
69
87
|
distintos, la skill genera un blueprint de entrega y permite fixtures locales
|
|
70
|
-
con el mismo contrato de los componentes.
|
|
71
|
-
|
|
88
|
+
con el mismo contrato de los componentes. Con autorización temporal y scopes
|
|
89
|
+
explícitos, puede crear o ajustar tipos, componentes, grupos, listas y entradas
|
|
90
|
+
mediante el entrypoint administrativo oficial. Las entradas nacen en borrador;
|
|
91
|
+
publicar exige permiso y aprobación humana. Un fixture activo en producción
|
|
92
|
+
bloquea la aprobación del sitio.
|
|
72
93
|
|
|
73
94
|
El snapshot puede validarse con:
|
|
74
95
|
|