veo-sdk 0.5.32 → 0.5.34

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-XUnTmzUO.cjs';
2
- export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-XUnTmzUO.cjs';
1
+ import { P as PreviewGuideInput } from './guide-preview-DbEZdGBV.cjs';
2
+ export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-DbEZdGBV.cjs';
3
3
 
4
4
  /** Dónde soltó el autor un bloque nuevo. */
5
5
  interface BlockDropPayload {
@@ -81,8 +81,10 @@ interface ElementPickerOptions {
81
81
  onCancel?: () => void;
82
82
  /** Niveles de ancestros del selector. Default 5 (igual que autocapture). */
83
83
  maxAncestors?: number;
84
- /** Texto del banner superior. Default en español. */
84
+ /** Título del banner superior. Default en español. */
85
85
  hint?: string;
86
+ /** Subtítulo del banner (default: cómo abrir menús con Alt + clic). */
87
+ subhint?: string;
86
88
  }
87
89
  interface ElementPickerHandle {
88
90
  /** Cierra el picker sin elegir nada. Idempotente. */
@@ -119,9 +121,87 @@ declare function stopElementPicker(): void;
119
121
  * 4. La app responde con eventos (`picked`, `pick-cancelled`, …).
120
122
  */
121
123
 
124
+ /**
125
+ * Estado que el panel (editor) publica para que el SDK pinte el "chrome" del
126
+ * editor (topbar + fila de pasos) ENCIMA de la app. El panel es la única fuente
127
+ * de verdad; el chrome solo lo refleja y devuelve `chrome-action`s.
128
+ */
129
+ interface EditorChromeState {
130
+ guideName: string;
131
+ /** ISO de creación (null = guía nueva sin guardar). */
132
+ createdAt: string | null;
133
+ steps: Array<{
134
+ id: string;
135
+ label: string;
136
+ presentation: 'floating' | 'inline';
137
+ }>;
138
+ activeStep: number;
139
+ /** Qué muestra el panel: ajustes de activación o el paso activo. */
140
+ section: 'activation' | 'step' | 'badge' | 'target';
141
+ saving: boolean;
142
+ dirty: boolean;
143
+ /**
144
+ * Estado de publicación de la guía, para que el autor vea en la barra si lo
145
+ * que edita está Activa/Borrador/etc. (sin esto, guardar una guía en borrador
146
+ * y no verla en la app era un misterio). `tone` elige el color del chip.
147
+ */
148
+ status?: {
149
+ label: string;
150
+ tone: 'live' | 'testing' | 'review' | 'draft' | 'paused';
151
+ } | null;
152
+ }
153
+ /**
154
+ * Qué está haciendo la preview en la app. El SDK lo publica cada vez que
155
+ * evalúa un comando `preview` con `honorTrigger`, para que el panel explique
156
+ * por qué la guía no se ve (retraso, elemento objetivo, otra página, otro
157
+ * dispositivo) en vez de parecer rota.
158
+ */
159
+ type PreviewStatus = {
160
+ state: 'shown';
161
+ } | {
162
+ state: 'waiting-delay';
163
+ delayMs: number;
164
+ } | {
165
+ state: 'waiting-trigger';
166
+ trigger: 'on_click' | 'on_hover';
167
+ selector: string;
168
+ } | {
169
+ state: 'page-mismatch';
170
+ pattern: string;
171
+ } | {
172
+ state: 'device-mismatch';
173
+ devices: 'desktop' | 'mobile';
174
+ };
175
+ /** Acciones que el usuario dispara en el chrome (barras) y el panel ejecuta. */
176
+ type ChromeAction = {
177
+ action: 'select-activation';
178
+ } | {
179
+ action: 'select-step';
180
+ index: number;
181
+ } | {
182
+ action: 'add-step';
183
+ } | {
184
+ action: 'save';
185
+ exit: boolean;
186
+ } | {
187
+ action: 'exit';
188
+ } | {
189
+ action: 'dock';
190
+ position: 'top' | 'bottom';
191
+ } | {
192
+ action: 'show-panel';
193
+ } | {
194
+ action: 'preview';
195
+ } | {
196
+ action: 'exit-preview';
197
+ };
122
198
  /** Eventos que la app del cliente envía al dashboard. */
123
199
  type BuilderEvent = {
124
200
  type: 'ready';
201
+ chrome?: boolean;
202
+ } | {
203
+ type: 'chrome-action';
204
+ payload: ChromeAction;
125
205
  } | {
126
206
  type: 'picked';
127
207
  payload: PickedElement;
@@ -133,6 +213,9 @@ type BuilderEvent = {
133
213
  } | {
134
214
  type: 'block-dropped';
135
215
  payload: BlockDropPayload;
216
+ } | {
217
+ type: 'preview-status';
218
+ payload: PreviewStatus;
136
219
  } | {
137
220
  type: 'closed';
138
221
  };
@@ -147,6 +230,7 @@ type BuilderCommand = {
147
230
  type: 'preview';
148
231
  guide: PreviewGuideInput;
149
232
  editable?: boolean;
233
+ honorTrigger?: boolean;
150
234
  } | {
151
235
  type: 'block-drag';
152
236
  phase: 'move' | 'drop' | 'cancel';
@@ -158,6 +242,12 @@ type BuilderCommand = {
158
242
  type: 'close-preview';
159
243
  } | {
160
244
  type: 'teardown';
245
+ } | {
246
+ type: 'editor-state';
247
+ state: EditorChromeState;
248
+ } | {
249
+ type: 'panel-visibility';
250
+ visible: boolean;
161
251
  } | {
162
252
  type: 'panel-ready';
163
253
  };
package/dist/builder.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as PreviewGuideInput } from './guide-preview-XUnTmzUO.js';
2
- export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-XUnTmzUO.js';
1
+ import { P as PreviewGuideInput } from './guide-preview-DbEZdGBV.js';
2
+ export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-DbEZdGBV.js';
3
3
 
4
4
  /** Dónde soltó el autor un bloque nuevo. */
5
5
  interface BlockDropPayload {
@@ -81,8 +81,10 @@ interface ElementPickerOptions {
81
81
  onCancel?: () => void;
82
82
  /** Niveles de ancestros del selector. Default 5 (igual que autocapture). */
83
83
  maxAncestors?: number;
84
- /** Texto del banner superior. Default en español. */
84
+ /** Título del banner superior. Default en español. */
85
85
  hint?: string;
86
+ /** Subtítulo del banner (default: cómo abrir menús con Alt + clic). */
87
+ subhint?: string;
86
88
  }
87
89
  interface ElementPickerHandle {
88
90
  /** Cierra el picker sin elegir nada. Idempotente. */
@@ -119,9 +121,87 @@ declare function stopElementPicker(): void;
119
121
  * 4. La app responde con eventos (`picked`, `pick-cancelled`, …).
120
122
  */
121
123
 
124
+ /**
125
+ * Estado que el panel (editor) publica para que el SDK pinte el "chrome" del
126
+ * editor (topbar + fila de pasos) ENCIMA de la app. El panel es la única fuente
127
+ * de verdad; el chrome solo lo refleja y devuelve `chrome-action`s.
128
+ */
129
+ interface EditorChromeState {
130
+ guideName: string;
131
+ /** ISO de creación (null = guía nueva sin guardar). */
132
+ createdAt: string | null;
133
+ steps: Array<{
134
+ id: string;
135
+ label: string;
136
+ presentation: 'floating' | 'inline';
137
+ }>;
138
+ activeStep: number;
139
+ /** Qué muestra el panel: ajustes de activación o el paso activo. */
140
+ section: 'activation' | 'step' | 'badge' | 'target';
141
+ saving: boolean;
142
+ dirty: boolean;
143
+ /**
144
+ * Estado de publicación de la guía, para que el autor vea en la barra si lo
145
+ * que edita está Activa/Borrador/etc. (sin esto, guardar una guía en borrador
146
+ * y no verla en la app era un misterio). `tone` elige el color del chip.
147
+ */
148
+ status?: {
149
+ label: string;
150
+ tone: 'live' | 'testing' | 'review' | 'draft' | 'paused';
151
+ } | null;
152
+ }
153
+ /**
154
+ * Qué está haciendo la preview en la app. El SDK lo publica cada vez que
155
+ * evalúa un comando `preview` con `honorTrigger`, para que el panel explique
156
+ * por qué la guía no se ve (retraso, elemento objetivo, otra página, otro
157
+ * dispositivo) en vez de parecer rota.
158
+ */
159
+ type PreviewStatus = {
160
+ state: 'shown';
161
+ } | {
162
+ state: 'waiting-delay';
163
+ delayMs: number;
164
+ } | {
165
+ state: 'waiting-trigger';
166
+ trigger: 'on_click' | 'on_hover';
167
+ selector: string;
168
+ } | {
169
+ state: 'page-mismatch';
170
+ pattern: string;
171
+ } | {
172
+ state: 'device-mismatch';
173
+ devices: 'desktop' | 'mobile';
174
+ };
175
+ /** Acciones que el usuario dispara en el chrome (barras) y el panel ejecuta. */
176
+ type ChromeAction = {
177
+ action: 'select-activation';
178
+ } | {
179
+ action: 'select-step';
180
+ index: number;
181
+ } | {
182
+ action: 'add-step';
183
+ } | {
184
+ action: 'save';
185
+ exit: boolean;
186
+ } | {
187
+ action: 'exit';
188
+ } | {
189
+ action: 'dock';
190
+ position: 'top' | 'bottom';
191
+ } | {
192
+ action: 'show-panel';
193
+ } | {
194
+ action: 'preview';
195
+ } | {
196
+ action: 'exit-preview';
197
+ };
122
198
  /** Eventos que la app del cliente envía al dashboard. */
123
199
  type BuilderEvent = {
124
200
  type: 'ready';
201
+ chrome?: boolean;
202
+ } | {
203
+ type: 'chrome-action';
204
+ payload: ChromeAction;
125
205
  } | {
126
206
  type: 'picked';
127
207
  payload: PickedElement;
@@ -133,6 +213,9 @@ type BuilderEvent = {
133
213
  } | {
134
214
  type: 'block-dropped';
135
215
  payload: BlockDropPayload;
216
+ } | {
217
+ type: 'preview-status';
218
+ payload: PreviewStatus;
136
219
  } | {
137
220
  type: 'closed';
138
221
  };
@@ -147,6 +230,7 @@ type BuilderCommand = {
147
230
  type: 'preview';
148
231
  guide: PreviewGuideInput;
149
232
  editable?: boolean;
233
+ honorTrigger?: boolean;
150
234
  } | {
151
235
  type: 'block-drag';
152
236
  phase: 'move' | 'drop' | 'cancel';
@@ -158,6 +242,12 @@ type BuilderCommand = {
158
242
  type: 'close-preview';
159
243
  } | {
160
244
  type: 'teardown';
245
+ } | {
246
+ type: 'editor-state';
247
+ state: EditorChromeState;
248
+ } | {
249
+ type: 'panel-visibility';
250
+ visible: boolean;
161
251
  } | {
162
252
  type: 'panel-ready';
163
253
  };
package/dist/builder.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import './chunk-JPANNXIK.mjs';
2
- import { initBuilderMode } from './chunk-AFU5ZG6Z.mjs';
3
- export { createBuilderSession, initBuilderMode, startElementPicker, stopElementPicker } from './chunk-AFU5ZG6Z.mjs';
4
- export { closeGuidePreview, previewGuide } from './chunk-X27GD7IT.mjs';
1
+ import './chunk-HAAT6XVN.mjs';
2
+ import { initBuilderMode } from './chunk-IZXJA55N.mjs';
3
+ export { createBuilderSession, initBuilderMode, startElementPicker, stopElementPicker } from './chunk-IZXJA55N.mjs';
4
+ export { closeGuidePreview, previewGuide } from './chunk-W4R4XCWL.mjs';
5
5
  import './chunk-PTG3BTJE.mjs';
6
6
 
7
7
  // src/builder.ts
@@ -1,4 +1,4 @@
1
- import { hasDocument, hasWindow, previewGuide, clearPreviewToken, DEFAULT_TRACKER_MAX_RETRIES, DEFAULT_TRACKER_BATCH_SIZE, DEFAULT_TRACKER_FLUSH_INTERVAL_MS, stepMatchesPage, CompositeRenderer, ModalRenderer, BannerRenderer, WalkthroughRenderer, BadgeRenderer, InlineFormRenderer, FormRenderer, InlineCustomRenderer, InlineRenderer, CustomRenderer, TooltipRenderer, FREQUENCY_CACHE_KEY_PREFIX, FREQUENCY_CACHE_TTL_MS, FREQUENCY_CACHE_MAX_ENTRIES, WALKTHROUGH_STATE_KEY_PREFIX, WALKTHROUGH_ABANDONMENT_TIMEOUT_MS } from './chunk-X27GD7IT.mjs';
1
+ import { hasDocument, hasWindow, isAutomaticActivation, activationDelayMs, 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-W4R4XCWL.mjs';
2
2
 
3
3
  // src/plugins/plugin.ts
4
4
  function definePlugin(plugin) {
@@ -43,26 +43,87 @@ async function runPreviewMode(opts) {
43
43
  }
44
44
  };
45
45
  let preview = null;
46
- const show = () => {
46
+ let delayTimer = null;
47
+ let disarmTrigger = null;
48
+ const baseLabel = guide ? `Vista previa: ${guide.guideName}` : "";
49
+ const clearWaits = () => {
50
+ if (delayTimer) clearTimeout(delayTimer);
51
+ delayTimer = null;
52
+ disarmTrigger?.();
53
+ disarmTrigger = null;
54
+ };
55
+ const paint = () => {
47
56
  if (!guide) return;
48
57
  preview?.close();
58
+ let startStepIndex;
59
+ if (guide.guideType === "composite") {
60
+ const steps = guide.guideSteps;
61
+ const idx = steps.findIndex((st) => stepMatchesPage(st.pageLocation, window.location.href));
62
+ if (idx === -1) {
63
+ chip.setLabel(`${baseLabel} \xB7 ning\xFAn paso aplica a esta p\xE1gina`);
64
+ return;
65
+ }
66
+ startStepIndex = idx;
67
+ }
68
+ chip.setLabel(baseLabel);
49
69
  preview = previewGuide({
50
70
  guideType: guide.guideType,
51
71
  guideSteps: guide.guideSteps,
52
72
  activationRules: guide.activationRules,
53
73
  guideName: guide.guideName,
74
+ ...startStepIndex !== void 0 ? { startStepIndex } : {},
54
75
  formSubmit: submitToBackend,
55
76
  formSubmitNote: "Vista previa: guardado en TU usuario para probar el flujo."
56
77
  });
57
78
  };
79
+ const show = () => {
80
+ if (!guide) return;
81
+ clearWaits();
82
+ const rules = guide.activationRules;
83
+ const selector = rules.selector?.trim();
84
+ if ((rules.trigger === "on_click" || rules.trigger === "on_hover") && selector && !isAutomaticActivation(rules)) {
85
+ const verb = rules.trigger === "on_hover" ? "pasa el mouse por" : "haz clic en";
86
+ chip.setLabel(`${baseLabel} \xB7 ${verb} el elemento objetivo`);
87
+ const onFire = (e) => {
88
+ const target = e.target;
89
+ if (!(target instanceof Element)) return;
90
+ if (target.closest("[data-veo-preview-chip],[data-veo-guide]")) return;
91
+ try {
92
+ if (!target.closest(selector)) return;
93
+ } catch {
94
+ return;
95
+ }
96
+ clearWaits();
97
+ paint();
98
+ };
99
+ document.addEventListener("click", onFire, true);
100
+ if (rules.trigger === "on_hover") document.addEventListener("mouseover", onFire, true);
101
+ disarmTrigger = () => {
102
+ document.removeEventListener("click", onFire, true);
103
+ document.removeEventListener("mouseover", onFire, true);
104
+ };
105
+ return;
106
+ }
107
+ const delayMs = activationDelayMs(rules);
108
+ if (delayMs > 0) {
109
+ chip.setLabel(`${baseLabel} \xB7 aparece en ${Math.round(delayMs / 1e3)} s`);
110
+ delayTimer = setTimeout(() => {
111
+ delayTimer = null;
112
+ paint();
113
+ }, delayMs);
114
+ return;
115
+ }
116
+ paint();
117
+ };
58
118
  const chip = mountChip({
59
- label: guide ? `Vista previa: ${guide.guideName}` : "Link de preview inv\xE1lido o vencido",
119
+ label: guide ? baseLabel : "Link de preview inv\xE1lido o vencido",
60
120
  error: !guide,
61
121
  onReshow: guide ? show : null,
62
122
  onClose: () => handle.teardown()
63
123
  });
64
124
  const handle = {
65
125
  teardown() {
126
+ clearWaits();
66
127
  preview?.close();
67
128
  preview = null;
68
129
  chip.remove();
@@ -85,7 +146,7 @@ function mountChip(opts) {
85
146
  background: #111827; color: #f9fafb; border-radius: 9999px;
86
147
  font: 500 12px/1.2 system-ui, sans-serif; box-shadow: 0 4px 12px rgba(0,0,0,.25); }
87
148
  .chip.error { background: #7f1d1d; }
88
- .label { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
149
+ .label { max-width: 360px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
89
150
  button { all: unset; cursor: pointer; padding: 2px 6px; border-radius: 9999px;
90
151
  font-size: 12px; line-height: 1; }
91
152
  button:hover { background: rgba(255,255,255,.15); }
@@ -111,7 +172,19 @@ function mountChip(opts) {
111
172
  chip.appendChild(close);
112
173
  root.appendChild(chip);
113
174
  document.body.appendChild(host);
114
- return { remove: () => host.remove() };
175
+ return {
176
+ remove: () => host.remove(),
177
+ setLabel: (text) => {
178
+ label.textContent = text;
179
+ label.title = text;
180
+ }
181
+ };
182
+ }
183
+
184
+ // src/plugins/guides/campaign-weekdays.ts
185
+ function isWeekdayAllowed(campaignWeekdays, now = /* @__PURE__ */ new Date()) {
186
+ if (!campaignWeekdays || campaignWeekdays.length === 0) return true;
187
+ return campaignWeekdays.includes(now.getDay());
115
188
  }
116
189
 
117
190
  // src/plugins/guides/guide-frequency-cache.ts
@@ -613,6 +686,15 @@ var STATUS_BY_ACTION = {
613
686
  step_back: null,
614
687
  completed: "completed"
615
688
  };
689
+ var REPEAT_UNIT_MS = {
690
+ minute: 6e4,
691
+ hour: 36e5,
692
+ day: 864e5,
693
+ week: 6048e5
694
+ };
695
+ function repeatIntervalMs(repeat) {
696
+ return Math.max(0, repeat.every) * REPEAT_UNIT_MS[repeat.unit];
697
+ }
616
698
  var GuidesController = class {
617
699
  constructor(client, config) {
618
700
  this.client = client;
@@ -647,6 +729,20 @@ var GuidesController = class {
647
729
  * a esa página (tours multi-página estilo Pendo).
648
730
  */
649
731
  this.pendingComposite = null;
732
+ /**
733
+ * Guías composite con «retrasar la aparición» esperando su `delayMs`. Se
734
+ * cancelan si el usuario navega a una página donde el paso ya no aplica y en
735
+ * `destroy()`. Evita el doble arranque por pageviews rápidos durante la espera.
736
+ */
737
+ this.compositeDelays = /* @__PURE__ */ new Map();
738
+ /**
739
+ * Estado LOCAL de «Repetir la aparición» (por sesión del SDK). El backend es
740
+ * la fuente de verdad (interacciones persistidas), pero entre el descarte y el
741
+ * siguiente resolve la cola de interacciones puede no haber salido todavía:
742
+ * sin esto la guía reaparecía al instante en la próxima navegación.
743
+ */
744
+ this.repeatHoldUntil = /* @__PURE__ */ new Map();
745
+ this.localDismissals = /* @__PURE__ */ new Map();
650
746
  /**
651
747
  * Guías composite EN PANTALLA con el paso mostrado, por `guideId`. En una SPA,
652
748
  * al navegar a una página que ya NO matchea la `pageLocation` del paso, hay que
@@ -712,15 +808,21 @@ var GuidesController = class {
712
808
  if (this.debug) console.error("[veo] guides resolver threw:", err);
713
809
  return;
714
810
  }
811
+ guides = guides.filter(
812
+ (g) => isWeekdayAllowed(g.campaignWeekdays) && isDeviceAllowed(g.activationRules.devices)
813
+ );
715
814
  if (this.debug) console.log(`[veo] guides: ${guides.length} eligible at ${pageUrl}`);
716
815
  this.disarmTriggers();
717
816
  for (const guide of guides) {
718
- const trigger = guide.activationRules.trigger;
719
- if ((trigger === "on_click" || trigger === "on_hover") && guide.activationRules.selector) {
817
+ const rules = guide.activationRules;
818
+ const trigger = rules.trigger;
819
+ if ((trigger === "on_click" || trigger === "on_hover") && rules.selector) {
720
820
  this.armInteractionTrigger(guide, endUserId, sessionId, pageUrl);
721
- continue;
821
+ if (!isAutomaticActivation(rules)) continue;
722
822
  }
723
- await this.showGuide(guide, endUserId, sessionId, pageUrl);
823
+ await this.showGuide(guide, endUserId, sessionId, pageUrl, {
824
+ badgeOpen: trigger !== "manual"
825
+ });
724
826
  }
725
827
  }
726
828
  /**
@@ -728,11 +830,16 @@ var GuidesController = class {
728
830
  * tipo). Cuerpo original del loop de `checkGuides`, extraído para que los
729
831
  * disparadores por interacción pasen por LOS MISMOS chequeos al disparar.
730
832
  */
731
- async showGuide(guide, endUserId, sessionId, pageUrl) {
833
+ async showGuide(guide, endUserId, sessionId, pageUrl, opts = {}) {
834
+ const badgeOpen = opts.badgeOpen !== false;
732
835
  if (this.frequencyCache.shouldFilter(guide.guideId, guide.displayFrequency)) {
733
836
  if (this.debug) console.log(`[veo] guides: filtered locally guideId=${guide.guideId}`);
734
837
  return;
735
838
  }
839
+ if (!opts.byUser && this.isHeldByRepeat(guide)) {
840
+ if (this.debug) console.log(`[veo] guides: held by repeat guideId=${guide.guideId}`);
841
+ return;
842
+ }
736
843
  if (this.activeByGuideId.has(guide.guideId)) {
737
844
  if (this.debug) console.log(`[veo] guides: already shown guideId=${guide.guideId}`);
738
845
  return;
@@ -747,7 +854,7 @@ var GuidesController = class {
747
854
  return;
748
855
  }
749
856
  if (guide.guideType === "composite") {
750
- this.startComposite(guide, sessionId, pageUrl);
857
+ this.startComposite(guide, sessionId, pageUrl, badgeOpen);
751
858
  return;
752
859
  }
753
860
  this.runSingleStepRender(guide, sessionId, pageUrl);
@@ -776,7 +883,7 @@ var GuidesController = class {
776
883
  return;
777
884
  }
778
885
  disarm();
779
- void this.showGuide(guide, endUserId, sessionId, pageUrl);
886
+ void this.showGuide(guide, endUserId, sessionId, pageUrl, { byUser: true });
780
887
  };
781
888
  const disarm = () => {
782
889
  document.removeEventListener(eventName, onFire, true);
@@ -843,6 +950,10 @@ ${pageUrl}`;
843
950
  this.activeWalkthroughGuide = null;
844
951
  }
845
952
  this.disarmTriggers();
953
+ for (const pending of this.compositeDelays.values()) clearTimeout(pending.timer);
954
+ this.compositeDelays.clear();
955
+ this.repeatHoldUntil.clear();
956
+ this.localDismissals.clear();
846
957
  this.dispatched.clear();
847
958
  this.resolveCache = null;
848
959
  this.inflightResolve = null;
@@ -852,7 +963,7 @@ ${pageUrl}`;
852
963
  const renderer = this.createSingleStepRenderer(guide.guideType);
853
964
  if (!renderer) return;
854
965
  this.activeByGuideId.set(guide.guideId, renderer);
855
- const delayMs = guide.activationRules.delayMs ?? 0;
966
+ const delayMs = activationDelayMs(guide.activationRules);
856
967
  window.setTimeout(
857
968
  () => this.mountSingleStepRenderer(guide, renderer, sessionId, pageUrl),
858
969
  delayMs
@@ -863,12 +974,79 @@ ${pageUrl}`;
863
974
  * `pageLocation` matchea la URL actual (targeting por-paso) y lo pinta con el
864
975
  * `CompositeRenderer`. Si ningún paso aplica a esta página, no muestra nada.
865
976
  */
866
- startComposite(guide, sessionId, pageUrl) {
977
+ startComposite(guide, sessionId, pageUrl, badgeOpen = true) {
867
978
  if (this.pendingComposite?.guide.guideId === guide.guideId) return;
868
979
  const steps = guide.guideSteps;
869
- const start = steps.findIndex((s) => stepMatchesPage(s.pageLocation, pageUrl));
980
+ const remembered = guide.activationRules.repeat?.onLoad === "resume" ? this.recallStep(guide.guideId) : null;
981
+ const start = remembered !== null && stepMatchesPage(steps[remembered]?.pageLocation, pageUrl) ? remembered : steps.findIndex((s) => stepMatchesPage(s.pageLocation, pageUrl));
870
982
  if (start === -1) return;
871
- this.renderCompositeStep(guide, start, sessionId, pageUrl);
983
+ if (guide.activationRules.trigger === "manual" && !steps[start]?.badge?.enabled) return;
984
+ if (this.compositeDelays.has(guide.guideId)) return;
985
+ this.resetDispatched(guide.guideId);
986
+ const delayMs = activationDelayMs(guide.activationRules);
987
+ if (delayMs <= 0) {
988
+ this.renderCompositeStep(guide, start, sessionId, pageUrl, badgeOpen);
989
+ return;
990
+ }
991
+ const timer = setTimeout(() => {
992
+ this.compositeDelays.delete(guide.guideId);
993
+ if (this.activeByGuideId.has(guide.guideId)) return;
994
+ this.renderCompositeStep(guide, start, sessionId, pageUrl, badgeOpen);
995
+ }, delayMs);
996
+ this.compositeDelays.set(guide.guideId, { guide, stepIndex: start, timer });
997
+ }
998
+ /** Olvida el dedupe de interacciones de una guía (nueva aparición). */
999
+ resetDispatched(guideId) {
1000
+ const prefix = `${guideId}:`;
1001
+ for (const key of this.dispatched) {
1002
+ if (key.startsWith(prefix)) this.dispatched.delete(key);
1003
+ }
1004
+ }
1005
+ /**
1006
+ * «Repetir la aparición» evaluada en local: ya se mostró hace menos del
1007
+ * intervalo, o el usuario ya la descartó N veces en esta sesión del SDK.
1008
+ */
1009
+ isHeldByRepeat(guide) {
1010
+ const repeat = guide.activationRules.repeat;
1011
+ if (!repeat) return false;
1012
+ const until = this.repeatHoldUntil.get(guide.guideId);
1013
+ if (until !== void 0 && Date.now() < until) return true;
1014
+ if (repeat.stopAfter.kind === "dismissals") {
1015
+ return (this.localDismissals.get(guide.guideId) ?? 0) >= repeat.stopAfter.count;
1016
+ }
1017
+ return false;
1018
+ }
1019
+ /** Registra en local el efecto de una interacción sobre «repetir». */
1020
+ recordRepeatLocal(guide, action) {
1021
+ const repeat = guide.activationRules.repeat;
1022
+ if (!repeat) return;
1023
+ if (action === "shown") {
1024
+ this.repeatHoldUntil.set(guide.guideId, Date.now() + repeatIntervalMs(repeat));
1025
+ } else if (action === "dismissed") {
1026
+ this.localDismissals.set(guide.guideId, (this.localDismissals.get(guide.guideId) ?? 0) + 1);
1027
+ } else if (action === "completed" && repeat.stopAfter.kind === "completed") {
1028
+ this.repeatHoldUntil.set(guide.guideId, Number.POSITIVE_INFINITY);
1029
+ }
1030
+ }
1031
+ /** Último paso visto por guía (localStorage) para `repeat.onLoad = 'resume'`. */
1032
+ stepKey(guideId) {
1033
+ return `veo:last_step:${guideId}`;
1034
+ }
1035
+ rememberStep(guideId, idx) {
1036
+ try {
1037
+ localStorage.setItem(this.stepKey(guideId), String(idx));
1038
+ } catch {
1039
+ }
1040
+ }
1041
+ recallStep(guideId) {
1042
+ try {
1043
+ const raw = localStorage.getItem(this.stepKey(guideId));
1044
+ if (raw === null) return null;
1045
+ const n = Number(raw);
1046
+ return Number.isInteger(n) && n >= 0 ? n : null;
1047
+ } catch {
1048
+ return null;
1049
+ }
872
1050
  }
873
1051
  /**
874
1052
  * Destruye las guías composite en pantalla cuyo paso mostrado ya no matchea la
@@ -876,6 +1054,14 @@ ${pageUrl}`;
876
1054
  * esto la guía queda montada en el DOM y aparece en páginas que no debía.
877
1055
  */
878
1056
  pruneComposite(pageUrl) {
1057
+ for (const [guideId, pending] of this.compositeDelays) {
1058
+ const steps = pending.guide.guideSteps;
1059
+ const step = steps[pending.stepIndex];
1060
+ if (!step || !stepMatchesPage(step.pageLocation, pageUrl)) {
1061
+ clearTimeout(pending.timer);
1062
+ this.compositeDelays.delete(guideId);
1063
+ }
1064
+ }
879
1065
  for (const entry of this.activeComposite.values()) {
880
1066
  const steps = entry.guide.guideSteps;
881
1067
  const step = steps[entry.stepIndex];
@@ -907,8 +1093,9 @@ ${pageUrl}`;
907
1093
  * que los botones next/prev avancen (misma página); en single-step los
908
1094
  * botones cierran. Cada avance destruye el paso anterior y monta el siguiente.
909
1095
  */
910
- renderCompositeStep(guide, idx, sessionId, pageUrl) {
1096
+ renderCompositeStep(guide, idx, sessionId, pageUrl, badgeOpen = true) {
911
1097
  const steps = guide.guideSteps;
1098
+ this.rememberStep(guide.guideId, idx);
912
1099
  const renderer = new CompositeRenderer();
913
1100
  this.activeRenderers.add(renderer);
914
1101
  this.activeByGuideId.set(guide.guideId, renderer);
@@ -926,7 +1113,7 @@ ${pageUrl}`;
926
1113
  if (target < 0 || target >= steps.length) return;
927
1114
  const step = steps[target];
928
1115
  if (step && stepMatchesPage(step.pageLocation, pageUrl)) {
929
- this.renderCompositeStep(guide, target, sessionId, pageUrl);
1116
+ this.renderCompositeStep(guide, target, sessionId, pageUrl, badgeOpen);
930
1117
  } else {
931
1118
  this.pendingComposite = { guide, stepIndex: target };
932
1119
  }
@@ -952,6 +1139,10 @@ ${pageUrl}`;
952
1139
  onOpenGuide: (targetGuideId) => {
953
1140
  void this.openGuideById(targetGuideId, sessionId, pageUrl);
954
1141
  },
1142
+ // Badge: cuando el MOTOR muestra la guía (automático, elemento objetivo
1143
+ // o encadenada), el contenido se abre solo; el marcador queda para
1144
+ // volver a verla. Solo `manual` deja el tooltip cerrado.
1145
+ badgeOpen,
955
1146
  ...nav ? { nav } : {}
956
1147
  });
957
1148
  } catch (err) {
@@ -1205,6 +1396,8 @@ ${pageUrl}`;
1205
1396
  this.dispatched.add(dedupKey);
1206
1397
  const cacheStatus = STATUS_BY_ACTION[event.action];
1207
1398
  if (cacheStatus) this.frequencyCache.record(guide.guideId, cacheStatus);
1399
+ this.recordRepeatLocal(guide, event.action);
1400
+ if (event.action === "dismissed" || event.action === "completed") this.resolveCache = null;
1208
1401
  const endUserId = this.client.getEndUserId();
1209
1402
  if (!endUserId) {
1210
1403
  if (this.debug)
@@ -1336,5 +1529,5 @@ function guidesPlugin(config) {
1336
1529
  }
1337
1530
 
1338
1531
  export { definePlugin, guidesPlugin, runPreviewMode };
1339
- //# sourceMappingURL=chunk-JPANNXIK.mjs.map
1340
- //# sourceMappingURL=chunk-JPANNXIK.mjs.map
1532
+ //# sourceMappingURL=chunk-HAAT6XVN.mjs.map
1533
+ //# sourceMappingURL=chunk-HAAT6XVN.mjs.map