veo-sdk 0.5.33 → 0.6.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.
@@ -1,5 +1,5 @@
1
- import { P as PreviewGuideInput } from './guide-preview-C73rKO0u.cjs';
2
- export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-C73rKO0u.cjs';
1
+ import { P as PreviewGuideInput } from './guide-preview-DylsEhrg.cjs';
2
+ export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-DylsEhrg.cjs';
3
3
 
4
4
  /** Dónde soltó el autor un bloque nuevo. */
5
5
  interface BlockDropPayload {
@@ -136,8 +136,14 @@ interface EditorChromeState {
136
136
  presentation: 'floating' | 'inline';
137
137
  }>;
138
138
  activeStep: number;
139
- /** Qué muestra el panel: ajustes de activación o el paso activo. */
140
- section: 'activation' | 'step' | 'badge' | 'target';
139
+ /**
140
+ * Qué muestra el panel. `step`, `block`, `add-block`, `row` y `structure`
141
+ * son vistas del paso activo (la fila de pasos lo marca); las pestañas del
142
+ * contenedor se «encienden» solo con `step` (contenedor) y `structure`.
143
+ */
144
+ section: 'activation' | 'step' | 'badge' | 'target' | 'block' | 'add-block' | 'row' | 'structure';
145
+ /** Editando un bloque del paso: su nombre («Encuesta Texto abierto») para el título del panel. */
146
+ blockLabel?: string | null;
141
147
  saving: boolean;
142
148
  dirty: boolean;
143
149
  /**
@@ -173,6 +179,21 @@ type PreviewStatus = {
173
179
  devices: 'desktop' | 'mobile';
174
180
  };
175
181
  /** Acciones que el usuario dispara en el chrome (barras) y el panel ejecuta. */
182
+ /** Bloque de la preview: paso + id local del panel (`StepBlock.editorId`). */
183
+ interface BlockRef {
184
+ stepIndex: number;
185
+ blockId: string;
186
+ }
187
+ type BlockToolbarAction = 'edit' | 'duplicate' | 'remove'
188
+ /** Mover: arriba/abajo dentro de su columna, izquierda/derecha entre columnas de su fila. */
189
+ | 'move-up' | 'move-down' | 'move-left' | 'move-right';
190
+ /** Barra de la FILA (hover o fila del bloque seleccionado): editar / duplicar / eliminar. */
191
+ type RowToolbarAction = 'edit' | 'duplicate' | 'remove';
192
+ /** Pestañas sobre el CONTENEDOR (hover): editar el paso o abrir la estructura de filas. */
193
+ type CardToolbarAction = 'edit-container' | 'edit-rows';
194
+ type BlockLayoutAction = 'row-before' | 'row-after' | 'col-before' | 'col-after'
195
+ /** Bloque apilado en la MISMA celda (fila+columna), antes/después del bloque. */
196
+ | 'stack-before' | 'stack-after';
176
197
  type ChromeAction = {
177
198
  action: 'select-activation';
178
199
  } | {
@@ -180,6 +201,14 @@ type ChromeAction = {
180
201
  index: number;
181
202
  } | {
182
203
  action: 'add-step';
204
+ }
205
+ /** Hover sobre la tarjeta de un paso: duplicar (se inserta después) o eliminar. */
206
+ | {
207
+ action: 'duplicate-step';
208
+ index: number;
209
+ } | {
210
+ action: 'remove-step';
211
+ index: number;
183
212
  } | {
184
213
  action: 'save';
185
214
  exit: boolean;
@@ -213,6 +242,47 @@ type BuilderEvent = {
213
242
  } | {
214
243
  type: 'block-dropped';
215
244
  payload: BlockDropPayload;
245
+ }
246
+ /** Clic simple (sin arrastre) sobre la tarjeta de la preview: el panel abre «Agregar bloque». */
247
+ | {
248
+ type: 'card-clicked';
249
+ payload: {
250
+ stepIndex: number;
251
+ };
252
+ }
253
+ /** Clic sobre un bloque de la preview: se selecciona en la app y el panel abre su editor. */
254
+ | {
255
+ type: 'block-clicked';
256
+ payload: BlockRef;
257
+ }
258
+ /** Barra del bloque seleccionado: editar / duplicar / eliminar. */
259
+ | {
260
+ type: 'block-action';
261
+ payload: BlockRef & {
262
+ action: BlockToolbarAction;
263
+ };
264
+ }
265
+ /** «+» en los bordes del bloque seleccionado: agregar fila arriba/abajo o columna izq/der. */
266
+ | {
267
+ type: 'block-layout';
268
+ payload: BlockRef & {
269
+ action: BlockLayoutAction;
270
+ };
271
+ }
272
+ /** Barra de una fila: `blockId` = primer bloque de la fila (la fila no tiene id propio). */
273
+ | {
274
+ type: 'row-action';
275
+ payload: BlockRef & {
276
+ action: RowToolbarAction;
277
+ };
278
+ }
279
+ /** Pestañas del contenedor (hover sobre la tarjeta): editar contenedor / editar filas. */
280
+ | {
281
+ type: 'card-action';
282
+ payload: {
283
+ stepIndex: number;
284
+ action: CardToolbarAction;
285
+ };
216
286
  } | {
217
287
  type: 'preview-status';
218
288
  payload: PreviewStatus;
@@ -240,6 +310,16 @@ type BuilderCommand = {
240
310
  label?: string;
241
311
  } | {
242
312
  type: 'close-preview';
313
+ }
314
+ /** El panel selecciona (o deselecciona con null) un bloque en la app. */
315
+ | {
316
+ type: 'select-block';
317
+ blockId: string | null;
318
+ }
319
+ /** El panel edita una fila: se marca en la app (por el id de un bloque de esa fila). */
320
+ | {
321
+ type: 'select-row';
322
+ blockId: string | null;
243
323
  } | {
244
324
  type: 'teardown';
245
325
  } | {
@@ -248,6 +328,14 @@ type BuilderCommand = {
248
328
  } | {
249
329
  type: 'panel-visibility';
250
330
  visible: boolean;
331
+ }
332
+ /** Tamaño compacto del panel (alto = contenido); `null` restaura el tamaño previo. */
333
+ | {
334
+ type: 'panel-size';
335
+ size: {
336
+ width: number;
337
+ height: number;
338
+ } | null;
251
339
  } | {
252
340
  type: 'panel-ready';
253
341
  };
package/dist/builder.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as PreviewGuideInput } from './guide-preview-C73rKO0u.js';
2
- export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-C73rKO0u.js';
1
+ import { P as PreviewGuideInput } from './guide-preview-DylsEhrg.js';
2
+ export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-DylsEhrg.js';
3
3
 
4
4
  /** Dónde soltó el autor un bloque nuevo. */
5
5
  interface BlockDropPayload {
@@ -136,8 +136,14 @@ interface EditorChromeState {
136
136
  presentation: 'floating' | 'inline';
137
137
  }>;
138
138
  activeStep: number;
139
- /** Qué muestra el panel: ajustes de activación o el paso activo. */
140
- section: 'activation' | 'step' | 'badge' | 'target';
139
+ /**
140
+ * Qué muestra el panel. `step`, `block`, `add-block`, `row` y `structure`
141
+ * son vistas del paso activo (la fila de pasos lo marca); las pestañas del
142
+ * contenedor se «encienden» solo con `step` (contenedor) y `structure`.
143
+ */
144
+ section: 'activation' | 'step' | 'badge' | 'target' | 'block' | 'add-block' | 'row' | 'structure';
145
+ /** Editando un bloque del paso: su nombre («Encuesta Texto abierto») para el título del panel. */
146
+ blockLabel?: string | null;
141
147
  saving: boolean;
142
148
  dirty: boolean;
143
149
  /**
@@ -173,6 +179,21 @@ type PreviewStatus = {
173
179
  devices: 'desktop' | 'mobile';
174
180
  };
175
181
  /** Acciones que el usuario dispara en el chrome (barras) y el panel ejecuta. */
182
+ /** Bloque de la preview: paso + id local del panel (`StepBlock.editorId`). */
183
+ interface BlockRef {
184
+ stepIndex: number;
185
+ blockId: string;
186
+ }
187
+ type BlockToolbarAction = 'edit' | 'duplicate' | 'remove'
188
+ /** Mover: arriba/abajo dentro de su columna, izquierda/derecha entre columnas de su fila. */
189
+ | 'move-up' | 'move-down' | 'move-left' | 'move-right';
190
+ /** Barra de la FILA (hover o fila del bloque seleccionado): editar / duplicar / eliminar. */
191
+ type RowToolbarAction = 'edit' | 'duplicate' | 'remove';
192
+ /** Pestañas sobre el CONTENEDOR (hover): editar el paso o abrir la estructura de filas. */
193
+ type CardToolbarAction = 'edit-container' | 'edit-rows';
194
+ type BlockLayoutAction = 'row-before' | 'row-after' | 'col-before' | 'col-after'
195
+ /** Bloque apilado en la MISMA celda (fila+columna), antes/después del bloque. */
196
+ | 'stack-before' | 'stack-after';
176
197
  type ChromeAction = {
177
198
  action: 'select-activation';
178
199
  } | {
@@ -180,6 +201,14 @@ type ChromeAction = {
180
201
  index: number;
181
202
  } | {
182
203
  action: 'add-step';
204
+ }
205
+ /** Hover sobre la tarjeta de un paso: duplicar (se inserta después) o eliminar. */
206
+ | {
207
+ action: 'duplicate-step';
208
+ index: number;
209
+ } | {
210
+ action: 'remove-step';
211
+ index: number;
183
212
  } | {
184
213
  action: 'save';
185
214
  exit: boolean;
@@ -213,6 +242,47 @@ type BuilderEvent = {
213
242
  } | {
214
243
  type: 'block-dropped';
215
244
  payload: BlockDropPayload;
245
+ }
246
+ /** Clic simple (sin arrastre) sobre la tarjeta de la preview: el panel abre «Agregar bloque». */
247
+ | {
248
+ type: 'card-clicked';
249
+ payload: {
250
+ stepIndex: number;
251
+ };
252
+ }
253
+ /** Clic sobre un bloque de la preview: se selecciona en la app y el panel abre su editor. */
254
+ | {
255
+ type: 'block-clicked';
256
+ payload: BlockRef;
257
+ }
258
+ /** Barra del bloque seleccionado: editar / duplicar / eliminar. */
259
+ | {
260
+ type: 'block-action';
261
+ payload: BlockRef & {
262
+ action: BlockToolbarAction;
263
+ };
264
+ }
265
+ /** «+» en los bordes del bloque seleccionado: agregar fila arriba/abajo o columna izq/der. */
266
+ | {
267
+ type: 'block-layout';
268
+ payload: BlockRef & {
269
+ action: BlockLayoutAction;
270
+ };
271
+ }
272
+ /** Barra de una fila: `blockId` = primer bloque de la fila (la fila no tiene id propio). */
273
+ | {
274
+ type: 'row-action';
275
+ payload: BlockRef & {
276
+ action: RowToolbarAction;
277
+ };
278
+ }
279
+ /** Pestañas del contenedor (hover sobre la tarjeta): editar contenedor / editar filas. */
280
+ | {
281
+ type: 'card-action';
282
+ payload: {
283
+ stepIndex: number;
284
+ action: CardToolbarAction;
285
+ };
216
286
  } | {
217
287
  type: 'preview-status';
218
288
  payload: PreviewStatus;
@@ -240,6 +310,16 @@ type BuilderCommand = {
240
310
  label?: string;
241
311
  } | {
242
312
  type: 'close-preview';
313
+ }
314
+ /** El panel selecciona (o deselecciona con null) un bloque en la app. */
315
+ | {
316
+ type: 'select-block';
317
+ blockId: string | null;
318
+ }
319
+ /** El panel edita una fila: se marca en la app (por el id de un bloque de esa fila). */
320
+ | {
321
+ type: 'select-row';
322
+ blockId: string | null;
243
323
  } | {
244
324
  type: 'teardown';
245
325
  } | {
@@ -248,6 +328,14 @@ type BuilderCommand = {
248
328
  } | {
249
329
  type: 'panel-visibility';
250
330
  visible: boolean;
331
+ }
332
+ /** Tamaño compacto del panel (alto = contenido); `null` restaura el tamaño previo. */
333
+ | {
334
+ type: 'panel-size';
335
+ size: {
336
+ width: number;
337
+ height: number;
338
+ } | null;
251
339
  } | {
252
340
  type: 'panel-ready';
253
341
  };
package/dist/builder.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import './chunk-FZ72JUAR.mjs';
2
- import { initBuilderMode } from './chunk-3ZOKCQ54.mjs';
3
- export { createBuilderSession, initBuilderMode, startElementPicker, stopElementPicker } from './chunk-3ZOKCQ54.mjs';
4
- export { closeGuidePreview, previewGuide } from './chunk-M6PB5UMW.mjs';
1
+ import './chunk-GMXQMZ4X.mjs';
2
+ import { initBuilderMode } from './chunk-N4VFMZFA.mjs';
3
+ export { createBuilderSession, initBuilderMode, startElementPicker, stopElementPicker } from './chunk-N4VFMZFA.mjs';
4
+ export { closeGuidePreview, previewGuide } from './chunk-O6USA4TU.mjs';
5
5
  import './chunk-PTG3BTJE.mjs';
6
6
 
7
7
  // src/builder.ts
@@ -1,4 +1,4 @@
1
- import { hasDocument, hasWindow, clearPreviewToken, DEFAULT_TRACKER_MAX_RETRIES, DEFAULT_TRACKER_BATCH_SIZE, DEFAULT_TRACKER_FLUSH_INTERVAL_MS, isDeviceAllowed, stepMatchesPage, CompositeRenderer, ModalRenderer, BannerRenderer, WalkthroughRenderer, BadgeRenderer, InlineFormRenderer, FormRenderer, InlineCustomRenderer, InlineRenderer, CustomRenderer, TooltipRenderer, previewGuide, FREQUENCY_CACHE_KEY_PREFIX, FREQUENCY_CACHE_TTL_MS, FREQUENCY_CACHE_MAX_ENTRIES, WALKTHROUGH_STATE_KEY_PREFIX, WALKTHROUGH_ABANDONMENT_TIMEOUT_MS } from './chunk-M6PB5UMW.mjs';
1
+ import { hasDocument, hasWindow, isAutomaticActivation, activationDelayMs, clearPreviewToken, DEFAULT_TRACKER_MAX_RETRIES, DEFAULT_TRACKER_BATCH_SIZE, DEFAULT_TRACKER_FLUSH_INTERVAL_MS, isDeviceAllowed, isSnoozed, stepMatchesPage, CompositeRenderer, ModalRenderer, BannerRenderer, WalkthroughRenderer, BadgeRenderer, InlineFormRenderer, FormRenderer, InlineCustomRenderer, InlineRenderer, CustomRenderer, TooltipRenderer, previewGuide, FREQUENCY_CACHE_KEY_PREFIX, FREQUENCY_CACHE_TTL_MS, FREQUENCY_CACHE_MAX_ENTRIES, WALKTHROUGH_STATE_KEY_PREFIX, WALKTHROUGH_ABANDONMENT_TIMEOUT_MS } from './chunk-O6USA4TU.mjs';
2
2
 
3
3
  // src/plugins/plugin.ts
4
4
  function definePlugin(plugin) {
@@ -26,7 +26,7 @@ async function runPreviewMode(opts) {
26
26
  } catch (err) {
27
27
  if (opts.debug) console.warn("[veo] preview-resolve fall\xF3:", err);
28
28
  }
29
- const submitToBackend = async (answers) => {
29
+ const submitToBackend = async (answers, meta) => {
30
30
  if (!guide) return false;
31
31
  const veo = window.veo;
32
32
  const endUserId = veo?.getEndUserId?.() ?? veo?.getAnonymousId?.() ?? null;
@@ -35,7 +35,12 @@ async function runPreviewMode(opts) {
35
35
  const res = await fetch(`${opts.apiUrl}/v1/guides/${guide.guideId}/form-response`, {
36
36
  method: "POST",
37
37
  headers: { "Content-Type": "application/json", "X-Api-Key": opts.apiKey },
38
- body: JSON.stringify({ endUserId, answers })
38
+ body: JSON.stringify({
39
+ endUserId,
40
+ answers,
41
+ ...meta ? { stepPosition: meta.stepIndex } : {},
42
+ ...meta?.pollId ? { pollId: meta.pollId } : {}
43
+ })
39
44
  });
40
45
  return res.ok;
41
46
  } catch {
@@ -81,7 +86,7 @@ async function runPreviewMode(opts) {
81
86
  clearWaits();
82
87
  const rules = guide.activationRules;
83
88
  const selector = rules.selector?.trim();
84
- if ((rules.trigger === "on_click" || rules.trigger === "on_hover") && selector) {
89
+ if ((rules.trigger === "on_click" || rules.trigger === "on_hover") && selector && !isAutomaticActivation(rules)) {
85
90
  const verb = rules.trigger === "on_hover" ? "pasa el mouse por" : "haz clic en";
86
91
  chip.setLabel(`${baseLabel} \xB7 ${verb} el elemento objetivo`);
87
92
  const onFire = (e) => {
@@ -104,7 +109,7 @@ async function runPreviewMode(opts) {
104
109
  };
105
110
  return;
106
111
  }
107
- const delayMs = rules.trigger === "delayed" ? rules.delayMs ?? 0 : 0;
112
+ const delayMs = activationDelayMs(rules);
108
113
  if (delayMs > 0) {
109
114
  chip.setLabel(`${baseLabel} \xB7 aparece en ${Math.round(delayMs / 1e3)} s`);
110
115
  delayTimer = setTimeout(() => {
@@ -750,6 +755,12 @@ var GuidesController = class {
750
755
  * en cada `checkGuides` con {@link pruneComposite}.
751
756
  */
752
757
  this.activeComposite = /* @__PURE__ */ new Map();
758
+ /**
759
+ * Progreso de esta sesión, por guía: pasos vistos y completado, según las
760
+ * interacciones que ESTE SDK emitió (aunque el lote aún no haya viajado al
761
+ * backend). Se mezcla con la respuesta del backend en `fetchProgress`.
762
+ */
763
+ this.sessionProgress = /* @__PURE__ */ new Map();
753
764
  this.debug = config.debug === true;
754
765
  this.apiUrl = config.apiUrl;
755
766
  this.apiKey = config.apiKey;
@@ -814,12 +825,15 @@ var GuidesController = class {
814
825
  if (this.debug) console.log(`[veo] guides: ${guides.length} eligible at ${pageUrl}`);
815
826
  this.disarmTriggers();
816
827
  for (const guide of guides) {
817
- const trigger = guide.activationRules.trigger;
818
- if ((trigger === "on_click" || trigger === "on_hover") && guide.activationRules.selector) {
828
+ const rules = guide.activationRules;
829
+ const trigger = rules.trigger;
830
+ if ((trigger === "on_click" || trigger === "on_hover") && rules.selector) {
819
831
  this.armInteractionTrigger(guide, endUserId, sessionId, pageUrl);
820
- continue;
832
+ if (!isAutomaticActivation(rules)) continue;
821
833
  }
822
- await this.showGuide(guide, endUserId, sessionId, pageUrl);
834
+ await this.showGuide(guide, endUserId, sessionId, pageUrl, {
835
+ badgeOpen: trigger !== "manual"
836
+ });
823
837
  }
824
838
  }
825
839
  /**
@@ -827,12 +841,17 @@ var GuidesController = class {
827
841
  * tipo). Cuerpo original del loop de `checkGuides`, extraído para que los
828
842
  * disparadores por interacción pasen por LOS MISMOS chequeos al disparar.
829
843
  */
830
- async showGuide(guide, endUserId, sessionId, pageUrl) {
844
+ async showGuide(guide, endUserId, sessionId, pageUrl, opts = {}) {
845
+ const badgeOpen = opts.badgeOpen !== false;
831
846
  if (this.frequencyCache.shouldFilter(guide.guideId, guide.displayFrequency)) {
832
847
  if (this.debug) console.log(`[veo] guides: filtered locally guideId=${guide.guideId}`);
833
848
  return;
834
849
  }
835
- if (this.isHeldByRepeat(guide)) {
850
+ if (!opts.byUser && isSnoozed(guide.guideId)) {
851
+ if (this.debug) console.log(`[veo] guides: snoozed guideId=${guide.guideId}`);
852
+ return;
853
+ }
854
+ if (!opts.byUser && this.isHeldByRepeat(guide)) {
836
855
  if (this.debug) console.log(`[veo] guides: held by repeat guideId=${guide.guideId}`);
837
856
  return;
838
857
  }
@@ -850,7 +869,7 @@ var GuidesController = class {
850
869
  return;
851
870
  }
852
871
  if (guide.guideType === "composite") {
853
- this.startComposite(guide, sessionId, pageUrl);
872
+ this.startComposite(guide, sessionId, pageUrl, badgeOpen);
854
873
  return;
855
874
  }
856
875
  this.runSingleStepRender(guide, sessionId, pageUrl);
@@ -879,7 +898,7 @@ var GuidesController = class {
879
898
  return;
880
899
  }
881
900
  disarm();
882
- void this.showGuide(guide, endUserId, sessionId, pageUrl);
901
+ void this.showGuide(guide, endUserId, sessionId, pageUrl, { byUser: true });
883
902
  };
884
903
  const disarm = () => {
885
904
  document.removeEventListener(eventName, onFire, true);
@@ -959,7 +978,7 @@ ${pageUrl}`;
959
978
  const renderer = this.createSingleStepRenderer(guide.guideType);
960
979
  if (!renderer) return;
961
980
  this.activeByGuideId.set(guide.guideId, renderer);
962
- const delayMs = guide.activationRules.delayMs ?? 0;
981
+ const delayMs = activationDelayMs(guide.activationRules);
963
982
  window.setTimeout(
964
983
  () => this.mountSingleStepRenderer(guide, renderer, sessionId, pageUrl),
965
984
  delayMs
@@ -970,23 +989,24 @@ ${pageUrl}`;
970
989
  * `pageLocation` matchea la URL actual (targeting por-paso) y lo pinta con el
971
990
  * `CompositeRenderer`. Si ningún paso aplica a esta página, no muestra nada.
972
991
  */
973
- startComposite(guide, sessionId, pageUrl) {
992
+ startComposite(guide, sessionId, pageUrl, badgeOpen = true) {
974
993
  if (this.pendingComposite?.guide.guideId === guide.guideId) return;
975
994
  const steps = guide.guideSteps;
976
995
  const remembered = guide.activationRules.repeat?.onLoad === "resume" ? this.recallStep(guide.guideId) : null;
977
996
  const start = remembered !== null && stepMatchesPage(steps[remembered]?.pageLocation, pageUrl) ? remembered : steps.findIndex((s) => stepMatchesPage(s.pageLocation, pageUrl));
978
997
  if (start === -1) return;
998
+ if (guide.activationRules.trigger === "manual" && !steps[start]?.badge?.enabled) return;
979
999
  if (this.compositeDelays.has(guide.guideId)) return;
980
1000
  this.resetDispatched(guide.guideId);
981
- const delayMs = guide.activationRules.delayMs ?? 0;
1001
+ const delayMs = activationDelayMs(guide.activationRules);
982
1002
  if (delayMs <= 0) {
983
- this.renderCompositeStep(guide, start, sessionId, pageUrl);
1003
+ this.renderCompositeStep(guide, start, sessionId, pageUrl, badgeOpen);
984
1004
  return;
985
1005
  }
986
1006
  const timer = setTimeout(() => {
987
1007
  this.compositeDelays.delete(guide.guideId);
988
1008
  if (this.activeByGuideId.has(guide.guideId)) return;
989
- this.renderCompositeStep(guide, start, sessionId, pageUrl);
1009
+ this.renderCompositeStep(guide, start, sessionId, pageUrl, badgeOpen);
990
1010
  }, delayMs);
991
1011
  this.compositeDelays.set(guide.guideId, { guide, stepIndex: start, timer });
992
1012
  }
@@ -1088,7 +1108,7 @@ ${pageUrl}`;
1088
1108
  * que los botones next/prev avancen (misma página); en single-step los
1089
1109
  * botones cierran. Cada avance destruye el paso anterior y monta el siguiente.
1090
1110
  */
1091
- renderCompositeStep(guide, idx, sessionId, pageUrl) {
1111
+ renderCompositeStep(guide, idx, sessionId, pageUrl, badgeOpen = true) {
1092
1112
  const steps = guide.guideSteps;
1093
1113
  this.rememberStep(guide.guideId, idx);
1094
1114
  const renderer = new CompositeRenderer();
@@ -1106,9 +1126,20 @@ ${pageUrl}`;
1106
1126
  const goToStep = (target) => {
1107
1127
  cleanup();
1108
1128
  if (target < 0 || target >= steps.length) return;
1129
+ const endUserId = this.client.getEndUserId();
1130
+ if (endUserId) {
1131
+ this.enqueueInteraction(
1132
+ guide.guideId,
1133
+ endUserId,
1134
+ sessionId,
1135
+ pageUrl,
1136
+ target > idx ? "step_advanced" : "step_back",
1137
+ target
1138
+ );
1139
+ }
1109
1140
  const step = steps[target];
1110
1141
  if (step && stepMatchesPage(step.pageLocation, pageUrl)) {
1111
- this.renderCompositeStep(guide, target, sessionId, pageUrl);
1142
+ this.renderCompositeStep(guide, target, sessionId, pageUrl, badgeOpen);
1112
1143
  } else {
1113
1144
  this.pendingComposite = { guide, stepIndex: target };
1114
1145
  }
@@ -1130,10 +1161,19 @@ ${pageUrl}`;
1130
1161
  onInteraction: (e) => this.handleInteraction(guide, sessionId, pageUrl, e),
1131
1162
  onClose: cleanup,
1132
1163
  onTrack: (name, props) => this.client.track(name, props),
1133
- onFormSubmit: (answers) => this.submitFormResponse(guide.guideId, answers),
1164
+ onFormSubmit: (answers, meta) => this.submitFormResponse(guide.guideId, answers, meta),
1134
1165
  onOpenGuide: (targetGuideId) => {
1135
1166
  void this.openGuideById(targetGuideId, sessionId, pageUrl);
1136
1167
  },
1168
+ // Lista de tareas: progreso del usuario y abrir una guía en su paso.
1169
+ fetchProgress: (ids) => this.fetchProgress(ids),
1170
+ onOpenGuideAt: (targetGuideId, stepIndex) => {
1171
+ void this.openGuideById(targetGuideId, sessionId, pageUrl, stepIndex);
1172
+ },
1173
+ // Badge: cuando el MOTOR muestra la guía (automático, elemento objetivo
1174
+ // o encadenada), el contenido se abre solo; el marcador queda para
1175
+ // volver a verla. Solo `manual` deja el tooltip cerrado.
1176
+ badgeOpen,
1137
1177
  ...nav ? { nav } : {}
1138
1178
  });
1139
1179
  } catch (err) {
@@ -1157,7 +1197,7 @@ ${pageUrl}`;
1157
1197
  const onTrack = (eventName, properties) => {
1158
1198
  this.client.track(eventName, properties);
1159
1199
  };
1160
- const onFormSubmit = (answers) => this.submitFormResponse(guide.guideId, answers);
1200
+ const onFormSubmit = (answers, meta) => this.submitFormResponse(guide.guideId, answers, meta);
1161
1201
  const onOpenGuide = (targetGuideId) => {
1162
1202
  void this.openGuideById(targetGuideId, sessionId, pageUrl);
1163
1203
  };
@@ -1177,7 +1217,7 @@ ${pageUrl}`;
1177
1217
  * un botón); respeta el dedupe de guías activas y el flujo normal de
1178
1218
  * tracking. 404/red → no-op (la guía destino pudo borrarse).
1179
1219
  */
1180
- async openGuideById(guideId, sessionId, pageUrl) {
1220
+ async openGuideById(guideId, sessionId, pageUrl, startStep) {
1181
1221
  if (this.activeByGuideId.has(guideId)) return;
1182
1222
  const guide = await this.resolver.fetchById(guideId);
1183
1223
  if (!guide) {
@@ -1185,6 +1225,15 @@ ${pageUrl}`;
1185
1225
  return;
1186
1226
  }
1187
1227
  const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
1228
+ if (guide.guideType === "composite" && typeof startStep === "number") {
1229
+ const total = guide.guideSteps.length;
1230
+ const idx = Math.max(0, Math.min(total - 1, Math.floor(startStep)));
1231
+ this.enqueueInteractionOnce(guide.guideId, endUserId, sessionId, pageUrl, "shown", 0);
1232
+ if (idx > 0)
1233
+ this.enqueueInteraction(guide.guideId, endUserId, sessionId, pageUrl, "step_advanced", idx);
1234
+ this.renderCompositeStep(guide, idx, sessionId, pageUrl);
1235
+ return;
1236
+ }
1188
1237
  if (guide.guideType === "walkthrough" && guide.guideSteps.length > 1) {
1189
1238
  if (!this.walkthroughState.hasActive()) {
1190
1239
  await this.startWalkthrough(guide, endUserId, sessionId, pageUrl);
@@ -1395,6 +1444,7 @@ ${pageUrl}`;
1395
1444
  console.warn("[veo] guides: interaction without endUserId \u2014 skipping enqueue");
1396
1445
  return;
1397
1446
  }
1447
+ this.noteProgress(guide.guideId, event.action, event.stepIndex);
1398
1448
  this.trackerQueue.enqueue({
1399
1449
  guideId: guide.guideId,
1400
1450
  payload: {
@@ -1414,6 +1464,7 @@ ${pageUrl}`;
1414
1464
  * una navegación distinta) y deduplicarlas perdería datos.
1415
1465
  */
1416
1466
  enqueueInteraction(guideId, endUserId, sessionId, pageUrl, action, stepIndex) {
1467
+ this.noteProgress(guideId, action, stepIndex);
1417
1468
  this.trackerQueue.enqueue({
1418
1469
  guideId,
1419
1470
  payload: {
@@ -1426,6 +1477,45 @@ ${pageUrl}`;
1426
1477
  }
1427
1478
  });
1428
1479
  }
1480
+ noteProgress(guideId, action, stepIndex) {
1481
+ if (action !== "shown" && action !== "step_advanced" && action !== "step_back" && action !== "completed") {
1482
+ return;
1483
+ }
1484
+ const cur = this.sessionProgress.get(guideId) ?? { maxStep: -1, completed: false };
1485
+ this.sessionProgress.set(guideId, {
1486
+ maxStep: Math.max(cur.maxStep, stepIndex),
1487
+ completed: cur.completed || action === "completed"
1488
+ });
1489
+ }
1490
+ /**
1491
+ * Progreso del usuario en varias guías (bloque «Lista de tareas»): lo pide
1492
+ * al backend y lo eleva con lo visto en esta sesión (nunca lo baja).
1493
+ */
1494
+ async fetchProgress(guideIds) {
1495
+ const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
1496
+ let rows = [];
1497
+ try {
1498
+ const params = new URLSearchParams({ endUserId, guideIds: guideIds.join(",") });
1499
+ const res = await fetch(`${this.apiUrl}/v1/guides/progress?${params}`, {
1500
+ headers: { "X-Api-Key": this.apiKey }
1501
+ });
1502
+ if (res.ok) rows = (await res.json()).items ?? [];
1503
+ else if (this.debug) console.warn("[veo] guides/progress respondi\xF3", res.status);
1504
+ } catch (err) {
1505
+ if (this.debug) console.warn("[veo] guides/progress fall\xF3:", err);
1506
+ }
1507
+ return rows.map((r) => {
1508
+ const local = this.sessionProgress.get(r.guideId);
1509
+ if (!local) return r;
1510
+ const seen = Math.min(r.totalSteps, local.maxStep + 1);
1511
+ const completed = r.completed || local.completed || seen >= r.totalSteps;
1512
+ return {
1513
+ ...r,
1514
+ currentStep: completed ? r.totalSteps : Math.max(r.currentStep, seen),
1515
+ completed
1516
+ };
1517
+ });
1518
+ }
1429
1519
  /**
1430
1520
  * Encola con dedup por sesión del SDK. Para `shown` del primer step:
1431
1521
  * no queremos re-emitirlo si el usuario navega entre dos páginas que
@@ -1470,13 +1560,19 @@ ${pageUrl}`;
1470
1560
  * propiedades del usuario. Usa el endUserId actual (o el anónimo si aún no
1471
1561
  * hubo identify — el backend pre-crea la fila igual que identify).
1472
1562
  */
1473
- async submitFormResponse(guideId, answers) {
1563
+ async submitFormResponse(guideId, answers, meta) {
1474
1564
  const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
1475
1565
  try {
1476
1566
  const res = await fetch(`${this.apiUrl}/v1/guides/${guideId}/form-response`, {
1477
1567
  method: "POST",
1478
1568
  headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey },
1479
- body: JSON.stringify({ endUserId, answers })
1569
+ body: JSON.stringify({
1570
+ endUserId,
1571
+ answers,
1572
+ sessionId: this.client.getSessionId(),
1573
+ ...meta ? { stepPosition: meta.stepIndex } : {},
1574
+ ...meta?.pollId ? { pollId: meta.pollId } : {}
1575
+ })
1480
1576
  });
1481
1577
  if (!res.ok && this.debug) {
1482
1578
  console.warn("[veo] form-response respondi\xF3", res.status);
@@ -1520,5 +1616,5 @@ function guidesPlugin(config) {
1520
1616
  }
1521
1617
 
1522
1618
  export { definePlugin, guidesPlugin, runPreviewMode };
1523
- //# sourceMappingURL=chunk-FZ72JUAR.mjs.map
1524
- //# sourceMappingURL=chunk-FZ72JUAR.mjs.map
1619
+ //# sourceMappingURL=chunk-GMXQMZ4X.mjs.map
1620
+ //# sourceMappingURL=chunk-GMXQMZ4X.mjs.map