@agroideas/event-tracker-sdk 1.0.0 → 1.0.2
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/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.global.js +135 -86
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +135 -86
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +135 -86
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -135,13 +135,13 @@ var EventBuffer = class {
|
|
|
135
135
|
async _sendEventsAsync(events) {
|
|
136
136
|
try {
|
|
137
137
|
const payload = JSON.stringify({
|
|
138
|
-
apiKey: this._apiKey,
|
|
139
138
|
events
|
|
140
139
|
});
|
|
141
140
|
const response = await fetch(this._endpoint, {
|
|
142
141
|
method: "POST",
|
|
143
142
|
headers: {
|
|
144
|
-
"Content-Type": "application/json"
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
"X-API-Key": this._apiKey
|
|
145
145
|
},
|
|
146
146
|
body: payload,
|
|
147
147
|
keepalive: true
|
|
@@ -447,13 +447,67 @@ var ClickObserver = class {
|
|
|
447
447
|
};
|
|
448
448
|
|
|
449
449
|
// src/observers/HistoryObserver.ts
|
|
450
|
+
var POLL_INTERVAL = 1e3;
|
|
451
|
+
var MIN_DURATION = 0.5;
|
|
452
|
+
var STORAGE_KEY_URL = "event_tracker_last_url";
|
|
453
|
+
var STORAGE_KEY_TIME = "event_tracker_last_time";
|
|
450
454
|
var HistoryObserver = class {
|
|
451
455
|
constructor(tracker2) {
|
|
452
456
|
this._isActive = false;
|
|
457
|
+
this._intervalId = null;
|
|
458
|
+
this._lastUrl = "";
|
|
459
|
+
this._hasFocus = true;
|
|
460
|
+
/** Manejador de focus (ventana ganha foco) */
|
|
461
|
+
this._handleFocus = () => {
|
|
462
|
+
try {
|
|
463
|
+
if (!this._hasFocus) {
|
|
464
|
+
this._hasFocus = true;
|
|
465
|
+
this._focusStartTime = performance.now();
|
|
466
|
+
}
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
/** Manejador de blur (ventana pierde foco) */
|
|
471
|
+
this._handleBlur = () => {
|
|
472
|
+
try {
|
|
473
|
+
if (this._hasFocus) {
|
|
474
|
+
this._accumulatedFocusTime += performance.now() - this._focusStartTime;
|
|
475
|
+
this._hasFocus = false;
|
|
476
|
+
}
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
/** Manejador de visibilitychange (para móvil - app en background) */
|
|
481
|
+
this._handleVisibilityChange = () => {
|
|
482
|
+
try {
|
|
483
|
+
if (typeof document === "undefined") {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (document.visibilityState === "visible") {
|
|
487
|
+
this._handleFocus();
|
|
488
|
+
} else if (document.visibilityState === "hidden") {
|
|
489
|
+
this._handleBlur();
|
|
490
|
+
}
|
|
491
|
+
} catch {
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
/** Manejador de beforeunload (cuando el usuario sale de la página) */
|
|
495
|
+
this._handleBeforeUnload = () => {
|
|
496
|
+
try {
|
|
497
|
+
const duration = this._calculateFocusedDuration();
|
|
498
|
+
if (duration >= MIN_DURATION) {
|
|
499
|
+
this._tracker.trackPageLeave(duration, this._lastUrl);
|
|
500
|
+
}
|
|
501
|
+
this._saveToStorage(this._lastUrl, duration);
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
504
|
+
};
|
|
453
505
|
this._tracker = tracker2;
|
|
454
|
-
this.
|
|
506
|
+
this._pageStartTime = performance.now();
|
|
507
|
+
this._focusStartTime = performance.now();
|
|
455
508
|
this._accumulatedFocusTime = 0;
|
|
456
|
-
this.
|
|
509
|
+
this._lastUrl = typeof window !== "undefined" ? window.location.pathname : "";
|
|
510
|
+
this._checkPreviousPage();
|
|
457
511
|
}
|
|
458
512
|
/** Inicia el observer */
|
|
459
513
|
start() {
|
|
@@ -461,11 +515,15 @@ var HistoryObserver = class {
|
|
|
461
515
|
return;
|
|
462
516
|
}
|
|
463
517
|
try {
|
|
464
|
-
this.
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
window
|
|
468
|
-
|
|
518
|
+
this._hasFocus = typeof document !== "undefined" ? document.hasFocus() : true;
|
|
519
|
+
this._focusStartTime = performance.now();
|
|
520
|
+
this._intervalId = setInterval(() => this._checkUrlChange(), POLL_INTERVAL);
|
|
521
|
+
if (typeof window !== "undefined") {
|
|
522
|
+
window.addEventListener("focus", this._handleFocus);
|
|
523
|
+
window.addEventListener("blur", this._handleBlur);
|
|
524
|
+
window.addEventListener("beforeunload", this._handleBeforeUnload);
|
|
525
|
+
document.addEventListener("visibilitychange", this._handleVisibilityChange);
|
|
526
|
+
}
|
|
469
527
|
this._isActive = true;
|
|
470
528
|
} catch {
|
|
471
529
|
}
|
|
@@ -476,104 +534,95 @@ var HistoryObserver = class {
|
|
|
476
534
|
return;
|
|
477
535
|
}
|
|
478
536
|
try {
|
|
479
|
-
this.
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
window
|
|
537
|
+
if (this._intervalId) {
|
|
538
|
+
clearInterval(this._intervalId);
|
|
539
|
+
this._intervalId = null;
|
|
540
|
+
}
|
|
541
|
+
if (typeof window !== "undefined") {
|
|
542
|
+
window.removeEventListener("focus", this._handleFocus);
|
|
543
|
+
window.removeEventListener("blur", this._handleBlur);
|
|
544
|
+
window.removeEventListener("beforeunload", this._handleBeforeUnload);
|
|
545
|
+
document.removeEventListener("visibilitychange", this._handleVisibilityChange);
|
|
546
|
+
}
|
|
484
547
|
this._isActive = false;
|
|
485
548
|
} catch {
|
|
486
549
|
}
|
|
487
550
|
}
|
|
488
|
-
/**
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (
|
|
492
|
-
|
|
493
|
-
history.pushState = this._wrappedPushState.bind(this);
|
|
494
|
-
}
|
|
495
|
-
if (!historyWithTracking._originalReplaceState) {
|
|
496
|
-
historyWithTracking._originalReplaceState = history.replaceState;
|
|
497
|
-
history.replaceState = this._wrappedReplaceState.bind(this);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
/** Restaura los métodos originales */
|
|
501
|
-
_restoreHistoryMethods() {
|
|
502
|
-
const historyWithTracking = history;
|
|
503
|
-
if (historyWithTracking._originalPushState) {
|
|
504
|
-
history.pushState = historyWithTracking._originalPushState;
|
|
505
|
-
historyWithTracking._originalPushState = void 0;
|
|
506
|
-
}
|
|
507
|
-
if (historyWithTracking._originalReplaceState) {
|
|
508
|
-
history.replaceState = historyWithTracking._originalReplaceState;
|
|
509
|
-
historyWithTracking._originalReplaceState = void 0;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
/** Wrapper para pushState */
|
|
513
|
-
_wrappedPushState(state, title, url) {
|
|
514
|
-
try {
|
|
515
|
-
const historyWithTracking = history;
|
|
516
|
-
historyWithTracking._originalPushState?.call(history, state, title, url);
|
|
517
|
-
this._handleNavigationChange();
|
|
518
|
-
} catch {
|
|
551
|
+
/** Calcula la duración total con foco */
|
|
552
|
+
_calculateFocusedDuration() {
|
|
553
|
+
let totalFocusTime = this._accumulatedFocusTime;
|
|
554
|
+
if (this._hasFocus) {
|
|
555
|
+
totalFocusTime += performance.now() - this._focusStartTime;
|
|
519
556
|
}
|
|
557
|
+
return totalFocusTime / 1e3;
|
|
520
558
|
}
|
|
521
|
-
/**
|
|
522
|
-
|
|
559
|
+
/** Verifica si la URL cambió */
|
|
560
|
+
_checkUrlChange() {
|
|
523
561
|
try {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
562
|
+
if (typeof window === "undefined") {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const currentUrl = window.location.pathname;
|
|
566
|
+
if (currentUrl !== this._lastUrl) {
|
|
567
|
+
const duration = this._calculateFocusedDuration();
|
|
568
|
+
if (duration >= MIN_DURATION) {
|
|
569
|
+
this._tracker.trackPageLeave(duration, this._lastUrl);
|
|
570
|
+
}
|
|
571
|
+
this._tracker.trackPageView();
|
|
572
|
+
this._pageStartTime = performance.now();
|
|
573
|
+
this._resetFocusTracking();
|
|
574
|
+
this._lastUrl = currentUrl;
|
|
575
|
+
}
|
|
527
576
|
} catch {
|
|
528
577
|
}
|
|
529
578
|
}
|
|
530
|
-
/**
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
579
|
+
/** Resetea el tracking de foco para una nueva página */
|
|
580
|
+
_resetFocusTracking() {
|
|
581
|
+
this._hasFocus = typeof document !== "undefined" ? document.hasFocus() : true;
|
|
582
|
+
this._focusStartTime = performance.now();
|
|
583
|
+
this._accumulatedFocusTime = 0;
|
|
536
584
|
}
|
|
537
|
-
/**
|
|
538
|
-
|
|
585
|
+
/** Guarda la URL y tiempo en sessionStorage (para F5) */
|
|
586
|
+
_saveToStorage(url, duration) {
|
|
539
587
|
try {
|
|
540
|
-
|
|
588
|
+
if (typeof sessionStorage !== "undefined") {
|
|
589
|
+
sessionStorage.setItem(STORAGE_KEY_URL, url);
|
|
590
|
+
sessionStorage.setItem(STORAGE_KEY_TIME, String(duration));
|
|
591
|
+
}
|
|
541
592
|
} catch {
|
|
542
593
|
}
|
|
543
594
|
}
|
|
544
|
-
/**
|
|
545
|
-
|
|
595
|
+
/** Lee la URL y tiempo previos de sessionStorage (para F5) */
|
|
596
|
+
_getFromStorage() {
|
|
546
597
|
try {
|
|
547
|
-
|
|
548
|
-
|
|
598
|
+
if (typeof sessionStorage !== "undefined") {
|
|
599
|
+
const url = sessionStorage.getItem(STORAGE_KEY_URL);
|
|
600
|
+
const duration = sessionStorage.getItem(STORAGE_KEY_TIME);
|
|
601
|
+
if (url && duration) {
|
|
602
|
+
return { url, duration: parseFloat(duration) };
|
|
603
|
+
}
|
|
604
|
+
}
|
|
549
605
|
} catch {
|
|
550
606
|
}
|
|
607
|
+
return null;
|
|
551
608
|
}
|
|
552
|
-
/**
|
|
553
|
-
|
|
609
|
+
/** Limpia sessionStorage */
|
|
610
|
+
_clearStorage() {
|
|
554
611
|
try {
|
|
555
|
-
|
|
556
|
-
|
|
612
|
+
if (typeof sessionStorage !== "undefined") {
|
|
613
|
+
sessionStorage.removeItem(STORAGE_KEY_URL);
|
|
614
|
+
sessionStorage.removeItem(STORAGE_KEY_TIME);
|
|
615
|
+
}
|
|
557
616
|
} catch {
|
|
558
617
|
}
|
|
559
618
|
}
|
|
560
|
-
/**
|
|
561
|
-
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return focusedTime / 1e3;
|
|
566
|
-
}
|
|
567
|
-
/** Maneja cambios de navegación (pushState, replaceState, popstate) */
|
|
568
|
-
_handleNavigationChange() {
|
|
569
|
-
try {
|
|
570
|
-
const duration = this._calculateFocusedDuration();
|
|
571
|
-
this._tracker.trackPageLeave(duration);
|
|
572
|
-
this._tracker.trackPageView();
|
|
573
|
-
this._lastFocusTime = Date.now();
|
|
574
|
-
this._accumulatedFocusTime = 0;
|
|
575
|
-
} catch {
|
|
619
|
+
/** Verifica si hay una página previa (para F5) */
|
|
620
|
+
_checkPreviousPage() {
|
|
621
|
+
const previous = this._getFromStorage();
|
|
622
|
+
if (previous && previous.duration >= MIN_DURATION) {
|
|
623
|
+
this._tracker.trackPageLeave(previous.duration, previous.url);
|
|
576
624
|
}
|
|
625
|
+
this._clearStorage();
|
|
577
626
|
}
|
|
578
627
|
};
|
|
579
628
|
|
|
@@ -617,7 +666,7 @@ var _Tracker = class _Tracker {
|
|
|
617
666
|
/** Inicializa el tracker con la configuración */
|
|
618
667
|
init(config) {
|
|
619
668
|
try {
|
|
620
|
-
if (config.userId
|
|
669
|
+
if (config.userId) {
|
|
621
670
|
this._userIdentity = {
|
|
622
671
|
userId: config.userId,
|
|
623
672
|
userName: config.userName ?? null
|
|
@@ -680,7 +729,7 @@ var _Tracker = class _Tracker {
|
|
|
680
729
|
}
|
|
681
730
|
}
|
|
682
731
|
/** Crea un evento de page leave */
|
|
683
|
-
trackPageLeave(duration) {
|
|
732
|
+
trackPageLeave(duration, path) {
|
|
684
733
|
try {
|
|
685
734
|
const event = {
|
|
686
735
|
eventType: "page_leave",
|
|
@@ -688,7 +737,7 @@ var _Tracker = class _Tracker {
|
|
|
688
737
|
context: this._buildContext(),
|
|
689
738
|
data: {
|
|
690
739
|
duration,
|
|
691
|
-
path: getCurrentPath()
|
|
740
|
+
path: path ?? getCurrentPath()
|
|
692
741
|
}
|
|
693
742
|
};
|
|
694
743
|
this.trackEvent(event);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/EventBuffer.ts","../src/utils/uuid.ts","../src/utils/truncate.ts","../src/utils/metadata.ts","../src/observers/ClickObserver.ts","../src/observers/HistoryObserver.ts","../src/core/Tracker.ts"],"sourcesContent":["/**\r\n * Event Tracker SDK\r\n *\r\n * SDK ligero y agnóstico al framework para tracking de comportamiento frontend.\r\n *\r\n * @example\r\n * import { tracker } from '@agroideas/event-tracker-sdk';\r\n *\r\n * tracker.init({\r\n * apiKey: 'tu-api-key',\r\n * endpoint: 'https://tu-api.com/events'\r\n * });\r\n *\r\n * // Identificar usuario\r\n * tracker.identify('user-123', 'Juan Pérez');\r\n */\r\n\r\nimport { Tracker } from './core/Tracker';\r\nimport type { TrackerConfig } from './types';\r\n\r\n/** Instancia única exportada del Tracker */\r\nexport const tracker = Tracker.getInstance();\r\n\r\n/**\r\n * Inicializa el tracker con configuración requerida (apiKey y endpoint).\r\n */\r\nexport function init(config: TrackerConfig): void {\r\n\ttracker.init(config);\r\n}\r\n\r\n/** Identifica al usuario */\r\nexport function identify(userId: string, userName?: string): void {\r\n\ttracker.identify(userId, userName);\r\n}\r\n\r\n/** Fuerza el envío de eventos pendientes */\r\nexport function flush(): Promise<void> {\r\n\treturn tracker.flush();\r\n}\r\n\r\n/** Destruye el tracker */\r\nexport function destroy(): void {\r\n\ttracker.destroy();\r\n}\r\n\r\nexport type { TrackerConfig };\r\n","/**\r\n * EventBuffer: Maneja la acumulación, batching y envío de eventos\r\n */\r\n\r\nimport type { TrackerEvent, SendResult, TrackerConfig, TrackingResponse } from '../types';\r\n\r\nconst BATCH_INTERVAL = 10000;\r\nconst MAX_BUFFER_SIZE = 15;\r\n\r\nexport class EventBuffer {\r\n\tprivate _buffer: TrackerEvent[];\r\n\tprivate _config: Required<Omit<TrackerConfig, 'apiKey' | 'endpoint' | 'userId' | 'userName' | 'debug'>>;\r\n\tprivate _flushTimer: ReturnType<typeof setInterval> | null = null;\r\n\tprivate _endpoint: string;\r\n\tprivate _apiKey: string;\r\n\tprivate _isDebug: boolean;\r\n\r\n\tconstructor(config: TrackerConfig) {\r\n\t\tthis._buffer = [];\r\n\r\n\t\t// apiKey y endpoint son requeridos\r\n\t\tthis._endpoint = config.endpoint;\r\n\t\tthis._apiKey = config.apiKey;\r\n\r\n\t\t// DEBUG es opcional, default false\r\n\t\tthis._isDebug = config.debug == \"true\";\r\n\t\tconsole.log(config);\r\n\r\n\t\tthis._config = {\r\n\t\t\tbatchInterval: config.batchInterval ?? BATCH_INTERVAL,\r\n\t\t\tmaxBufferSize: config.maxBufferSize ?? MAX_BUFFER_SIZE,\r\n\t\t};\r\n\r\n\t\tif (this._isDebug) {\r\n\t\t\tconsole.log('[EventTracker] Modo DEBUG activado');\r\n\t\t\tconsole.log('[EventTracker] Endpoint:', this._endpoint);\r\n\t\t\tconsole.log('[EventTracker] API Key:', this._apiKey);\r\n\t\t}\r\n\r\n\t\t// En modo debug, no iniciar timer de flush\r\n\t\tif (!this._isDebug) {\r\n\t\t\tthis._startFlushTimer();\r\n\t\t}\r\n\r\n\t\tthis._setupVisibilityListener();\r\n\t}\r\n\r\n\t/** Agrega un evento al buffer */\r\n\tadd(event: TrackerEvent): void {\r\n\t\ttry {\r\n\t\t\tthis._buffer.push(event);\r\n\r\n\t\t\tif (this._isDebug) {\r\n\t\t\t\tconsole.log('[EventTracker] Evento agregado:', event.eventType);\r\n\t\t\t\tconsole.log('[EventTracker] Buffer size:', this._buffer.length);\r\n\t\t\t\tconsole.log('[EventTracker] Event content:', JSON.stringify(event, null, 2));\r\n\t\t\t}\r\n\r\n\t\t\t// En modo debug, no hacer flush automático\r\n\t\t\tif (!this._isDebug && this._buffer.length >= this._config.maxBufferSize) {\r\n\t\t\t\tthis.flush();\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Silently ignore errors to never block the main app\r\n\t\t}\r\n\t}\r\n\r\n\t/** Envía todos los eventos acumulados al servidor */\r\n\tasync flush(): Promise<SendResult> {\r\n\t\ttry {\r\n\t\t\tif (this._buffer.length === 0) {\r\n\t\t\t\treturn { success: true };\r\n\t\t\t}\r\n\r\n\t\t\tconst eventsToSend = [...this._buffer];\r\n\t\t\tthis._buffer = [];\r\n\r\n\t\t\t// Modo debug: solo console.log, no fetch\r\n\t\t\tif (this._isDebug) {\r\n\t\t\t\tconsole.log('[EventTracker] DEBUG - Events:', JSON.stringify(eventsToSend, null, 2));\r\n\t\t\t\treturn { success: true };\r\n\t\t\t}\r\n\r\n\t\t\tconst result = await this._sendEventsAsync(eventsToSend);\r\n\t\t\treturn result;\r\n\t\t} catch (error) {\r\n\t\t\treturn {\r\n\t\t\t\tsuccess: false,\r\n\t\t\t\terror: error instanceof Error ? error.message : 'Unknown error',\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\t/** Obtiene la cantidad de eventos en el buffer */\r\n\tgetSize(): number {\r\n\t\treturn this._buffer.length;\r\n\t}\r\n\r\n\t/** Limpia el buffer sin enviar */\r\n\tclear(): void {\r\n\t\tthis._buffer = [];\r\n\t}\r\n\r\n\t/** Destruye el buffer y limpia recursos */\r\n\tdestroy(): void {\r\n\t\tif (this._flushTimer) {\r\n\t\t\tclearInterval(this._flushTimer);\r\n\t\t\tthis._flushTimer = null;\r\n\t\t}\r\n\r\n\t\tthis._buffer = [];\r\n\t}\r\n\r\n\t/** Inicia el temporizador de flush periódico */\r\n\tprivate _startFlushTimer(): void {\r\n\t\tthis._flushTimer = setInterval(() => {\r\n\t\t\tif (this._buffer.length > 0) {\r\n\t\t\t\tthis.flush();\r\n\t\t\t}\r\n\t\t}, this._config.batchInterval);\r\n\t}\r\n\r\n\t/** Configura el listener de visibilitychange para envío de emergencia */\r\n\tprivate _setupVisibilityListener(): void {\r\n\t\tif (typeof document === 'undefined') {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst handleVisibilityChange = (): void => {\r\n\t\t\t// En modo debug, no enviar eventos\r\n\t\t\tif (this._isDebug) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (document.visibilityState === 'hidden' && this._buffer.length > 0) {\r\n\t\t\t\t// Usar setTimeout para asegurar que el fetch se complete antes del unload\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tthis.flush();\r\n\t\t\t\t}, 0);\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tdocument.addEventListener('visibilitychange', handleVisibilityChange);\r\n\t}\r\n\r\n\t/** Envía eventos usando fetch con keepalive (funciona incluso en page unload) */\r\n\tprivate async _sendEventsAsync(events: TrackerEvent[]): Promise<SendResult> {\r\n\t\ttry {\r\n\t\t\tconst payload = JSON.stringify({\r\n\t\t\t\tapiKey: this._apiKey,\r\n\t\t\t\tevents: events,\r\n\t\t\t});\r\n\r\n\t\t\tconst response = await fetch(this._endpoint, {\r\n\t\t\t\tmethod: 'POST',\r\n\t\t\t\theaders: {\r\n\t\t\t\t\t'Content-Type': 'application/json',\r\n\t\t\t\t},\r\n\t\t\t\tbody: payload,\r\n\t\t\t\tkeepalive: true,\r\n\t\t\t});\r\n\r\n\t\t\t// Intentar parsear el mensaje de respuesta del backend\r\n\t\t\tlet message: string | undefined;\r\n\t\t\ttry {\r\n\t\t\t\tconst responseData: TrackingResponse = await response.json();\r\n\t\t\t\tmessage = responseData.message;\r\n\t\t\t} catch {\r\n\t\t\t\t// Si no se puede parsear, usar el status text\r\n\t\t\t\tmessage = response.statusText;\r\n\t\t\t}\r\n\r\n\t\t\treturn {\r\n\t\t\t\tsuccess: response.ok,\r\n\t\t\t\tstatusCode: response.status,\r\n\t\t\t\tmessage,\r\n\t\t\t};\r\n\t\t} catch (error) {\r\n\t\t\treturn {\r\n\t\t\t\tsuccess: false,\r\n\t\t\t\terror: error instanceof Error ? error.message : 'Network error',\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n}\r\n","/**\r\n * Generador de UUID v4\r\n */\r\n\r\n/** Genera un UUID v4 aleatorio */\r\nexport function generateUUID(): string {\r\n\treturn 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {\r\n\t\tconst random = Math.random() * 16 | 0;\r\n\t\tconst value = char === 'x' ? random : (random & 0x3) | 0x8;\r\n\t\treturn value.toString(16);\r\n\t});\r\n}\r\n","/**\r\n * Utilidad para truncar texto\r\n */\r\n\r\n/**\r\n * Trunca un texto a la longitud máxima especificada\r\n * @param text - Texto a truncar\r\n * @param maxLength - Longitud máxima\r\n * @returns Texto truncado con '...' al final si excede la longitud\r\n */\r\nexport function truncate(text: string, maxLength: number): string {\r\n\tif (maxLength < 3) {\r\n\t\treturn '...';\r\n\t}\r\n\r\n\tif (text.length <= maxLength) {\r\n\t\treturn text;\r\n\t}\r\n\r\n\treturn text.substring(0, maxLength - 3) + '...';\r\n}\r\n","/**\r\n * Utilidades para recolectar metadatos del navegador\r\n */\r\n\r\nimport { generateUUID } from './uuid';\r\n\r\n/** Obtiene o crea un sessionId en sessionStorage */\r\nexport function getOrCreateSessionId(): string {\r\n\ttry {\r\n\t\tconst storageKey = 'event_tracker_session_id';\r\n\t\tconst existingSessionId = sessionStorage.getItem(storageKey);\r\n\r\n\t\tif (existingSessionId) {\r\n\t\t\treturn existingSessionId;\r\n\t\t}\r\n\r\n\t\tconst newSessionId = generateUUID();\r\n\t\tsessionStorage.setItem(storageKey, newSessionId);\r\n\t\treturn newSessionId;\r\n\t} catch {\r\n\t\treturn generateUUID();\r\n\t}\r\n}\r\n\r\n/** Obtiene el tamaño de pantalla */\r\nexport function getScreenSize(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '0x0';\r\n\t}\r\n\r\n\treturn `${window.innerWidth}x${window.innerHeight}`;\r\n}\r\n\r\n/** Obtiene el tamaño del viewport */\r\nexport function getViewportSize(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '0x0';\r\n\t}\r\n\r\n\treturn `${window.innerWidth}x${window.innerHeight}`;\r\n}\r\n\r\n/** Detecta el tipo de dispositivo */\r\nexport function getDeviceType(): 'mobile' | 'desktop' | 'tablet' | 'unknown' {\r\n\tif (typeof navigator === 'undefined') {\r\n\t\treturn 'unknown';\r\n\t}\r\n\r\n\tconst ua = navigator.userAgent;\r\n\r\n\tif (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {\r\n\t\treturn 'tablet';\r\n\t}\r\n\r\n\tif (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(ua)) {\r\n\t\treturn 'mobile';\r\n\t}\r\n\r\n\treturn 'desktop';\r\n}\r\n\r\n/** Obtiene el navegador */\r\nexport function getBrowser(): string {\r\n\tif (typeof navigator === 'undefined') {\r\n\t\treturn 'unknown';\r\n\t}\r\n\r\n\tconst ua = navigator.userAgent;\r\n\r\n\tif (ua.includes('Firefox')) {\r\n\t\treturn 'Firefox';\r\n\t}\r\n\r\n\tif (ua.includes('Edg/')) {\r\n\t\treturn 'Edge';\r\n\t}\r\n\r\n\tif (ua.includes('Chrome')) {\r\n\t\treturn 'Chrome';\r\n\t}\r\n\r\n\tif (ua.includes('Safari')) {\r\n\t\treturn 'Safari';\r\n\t}\r\n\r\n\tif (ua.includes('MSIE') || ua.includes('Trident/')) {\r\n\t\treturn 'Internet Explorer';\r\n\t}\r\n\r\n\treturn 'unknown';\r\n}\r\n\r\n/** Obtiene el sistema operativo */\r\nexport function getOS(): string {\r\n\tif (typeof navigator === 'undefined') {\r\n\t\treturn 'unknown';\r\n\t}\r\n\r\n\tconst ua = navigator.userAgent;\r\n\r\n\tif (ua.includes('Windows')) {\r\n\t\treturn 'Windows';\r\n\t}\r\n\r\n\tif (ua.includes('Mac OS')) {\r\n\t\treturn 'macOS';\r\n\t}\r\n\r\n\tif (ua.includes('Linux')) {\r\n\t\treturn 'Linux';\r\n\t}\r\n\r\n\tif (ua.includes('Android')) {\r\n\t\treturn 'Android';\r\n\t}\r\n\r\n\tif (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad')) {\r\n\t\treturn 'iOS';\r\n\t}\r\n\r\n\treturn 'unknown';\r\n}\r\n\r\n/** Obtiene el idioma del navegador */\r\nexport function getLanguage(): string {\r\n\tif (typeof navigator === 'undefined') {\r\n\t\treturn 'unknown';\r\n\t}\r\n\r\n\treturn navigator.language || 'unknown';\r\n}\r\n\r\n/** Obtiene la URL actual */\r\nexport function getCurrentUrl(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '';\r\n\t}\r\n\r\n\treturn window.location.href;\r\n}\r\n\r\n/** Obtiene el referrer */\r\nexport function getReferrer(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '';\r\n\t}\r\n\r\n\treturn document.referrer;\r\n}\r\n\r\n/** Obtiene el timestamp actual en formato ISO */\r\nexport function getTimestamp(): string {\r\n\treturn new Date().toISOString();\r\n}\r\n\r\n/** Obtiene el path actual */\r\nexport function getCurrentPath(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '';\r\n\t}\r\n\r\n\treturn window.location.pathname;\r\n}\r\n","/**\r\n * ClickObserver: Captura clics globales en elementos interactivos\r\n */\r\n\r\nimport type { TrackerEvent } from '../types';\r\nimport { Tracker } from '../core/Tracker';\r\nimport { generateUUID } from '../utils/uuid';\r\nimport { truncate } from '../utils/truncate';\r\nimport {\r\n\tgetReferrer,\r\n\tgetScreenSize,\r\n\tgetTimestamp,\r\n\tgetViewportSize,\r\n\tgetDeviceType,\r\n\tgetBrowser,\r\n\tgetOS,\r\n\tgetLanguage,\r\n} from '../utils/metadata';\r\n\r\n/** Tipos de elementos que deben ser rastreados */\r\nconst TRACKABLE_TAGS = ['BUTTON', 'A', 'INPUT', 'I', 'SPAN', 'DIV'];\r\n\r\n/** Tipos de input que deben ser rastreados */\r\nconst TRACKABLE_INPUT_TYPES = ['submit'];\r\n\r\n/** Tipos de input que deben ser excluidos (sensibles) */\r\nconst SENSITIVE_INPUT_TYPES = ['password', 'email', 'tel', 'number', 'search', 'url'];\r\n\r\n/** Selectores para elementos rastreables */\r\nconst TRACKABLE_SELECTORS = [\r\n\t'button',\r\n\t'a',\r\n\t'input[type=\"submit\"]',\r\n\t'[data-track]',\r\n\t'[style*=\"cursor: pointer\"]',\r\n\t'i[class*=\"mdi-\"]',\r\n];\r\n\r\n/** Verifica si un elemento es sensible (no capturar valores) */\r\nfunction _isSensitiveElement(element: Element): boolean {\r\n\tif (element.tagName === 'INPUT') {\r\n\t\tconst inputType = (element as HTMLInputElement).type?.toLowerCase() ?? '';\r\n\t\treturn SENSITIVE_INPUT_TYPES.includes(inputType);\r\n\t}\r\n\treturn false;\r\n}\r\n\r\n/** Verifica si un elemento es rastreable */\r\nfunction _isTrackableElement(element: Element): boolean {\r\n\tconst tagName = element.tagName.toUpperCase();\r\n\r\n\tif (!TRACKABLE_TAGS.includes(tagName)) {\r\n\t\treturn element.hasAttribute('data-track') ||\r\n\t\t\t_hasCursorPointer(element) ||\r\n\t\t\t_isMdiIcon(element);\r\n\t}\r\n\r\n\tif (tagName === 'INPUT') {\r\n\t\tconst inputType = (element as HTMLInputElement).type?.toLowerCase() ?? '';\r\n\t\treturn TRACKABLE_INPUT_TYPES.includes(inputType);\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/** Verifica si el elemento tiene cursor: pointer */\r\nfunction _hasCursorPointer(element: Element): boolean {\r\n\ttry {\r\n\t\tconst style = window.getComputedStyle(element);\r\n\t\treturn style.cursor === 'pointer';\r\n\t} catch {\r\n\t\treturn false;\r\n\t}\r\n}\r\n\r\n/** Verifica si es un icono MDI */\r\nfunction _isMdiIcon(element: Element): boolean {\r\n\treturn element.tagName === 'I' && element.classList.contains('mdi');\r\n}\r\n\r\n/** Genera un selector CSS simple para el elemento */\r\nfunction _generateSelector(element: Element): string {\r\n\tconst parts: string[] = [];\r\n\tlet current: Element | null = element;\r\n\r\n\twhile (current && current !== document.documentElement) {\r\n\t\tlet selector = current.tagName.toLowerCase();\r\n\r\n\t\tif (current.id) {\r\n\t\t\tselector += `#${current.id}`;\r\n\t\t\tparts.unshift(selector);\r\n\t\t\tbreak;\r\n\t\t} else if (current.className && typeof current.className === 'string') {\r\n\t\t\tconst classes = current.className.trim().split(/\\s+/).filter(Boolean);\r\n\t\t\tif (classes.length > 0) {\r\n\t\t\t\tselector += `.${classes[0]}`;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tparts.unshift(selector);\r\n\t\tcurrent = current.parentElement;\r\n\t}\r\n\r\n\treturn parts.slice(0, 4).join(' > ');\r\n}\r\n\r\n/** Obtiene todas las clases de un elemento como string separado por espacios */\r\nfunction _getAllClasses(element: Element): string | undefined {\r\n\tif (!element.className || typeof element.className !== 'string') {\r\n\t\treturn undefined;\r\n\t}\r\n\tconst classes = element.className.trim().split(/\\s+/).filter(Boolean);\r\n\treturn classes.length > 0 ? classes.join(' ') : undefined;\r\n}\r\n\r\nexport class ClickObserver {\r\n\tprivate _tracker: Tracker;\r\n\tprivate _isActive: boolean = false;\r\n\tprivate _boundHandleClick: (event: MouseEvent) => void;\r\n\r\n\tconstructor(tracker: Tracker) {\r\n\t\tthis._tracker = tracker;\r\n\t\tthis._boundHandleClick = this._handleClick.bind(this);\r\n\t}\r\n\r\n\t/** Inicia el observer */\r\n\tstart(): void {\r\n\t\tif (this._isActive) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\twindow.addEventListener('click', this._boundHandleClick, true);\r\n\t\t\tthis._isActive = true;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Detiene el observer */\r\n\tstop(): void {\r\n\t\tif (!this._isActive) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\twindow.removeEventListener('click', this._boundHandleClick, true);\r\n\t\t\tthis._isActive = false;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Manejador de eventos de clic */\r\n\tprivate _handleClick(event: MouseEvent): void {\r\n\t\ttry {\r\n\t\t\tconst target = event.target as Element;\r\n\r\n\t\t\tif (!target) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// PRIORIDAD 1: Si el elemento clickeado es rastreable, usarlo directamente\r\n\t\t\tif (_isTrackableElement(target) && !_isSensitiveElement(target)) {\r\n\t\t\t\tthis._createClickEvent(target);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// PRIORIDAD 2: Buscar el ancestro rastreable más cercano\r\n\t\t\tconst trackableElement = target.closest(TRACKABLE_SELECTORS.join(','));\r\n\r\n\t\t\tif (!trackableElement) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (_isSensitiveElement(trackableElement)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (!_isTrackableElement(trackableElement)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis._createClickEvent(trackableElement);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Crea un evento de clic */\r\n\tprivate _createClickEvent(element: Element): void {\r\n\t\tconst event: TrackerEvent = {\r\n\t\t\teventType: 'user_click',\r\n\t\t\teventId: generateUUID(),\r\n\t\t\tcontext: {\r\n\t\t\t\tsessionId: '',\r\n\t\t\t\tuserId: null,\r\n\t\t\t\tuserName: null,\r\n\t\t\t\turl: window.location.href,\r\n\t\t\t\treferrer: getReferrer(),\r\n\t\t\t\tscreenSize: getScreenSize(),\r\n\t\t\t\tviewportSize: getViewportSize(),\r\n\t\t\t\tdeviceType: getDeviceType(),\r\n\t\t\t\tbrowser: getBrowser(),\r\n\t\t\t\tos: getOS(),\r\n\t\t\t\tlanguage: getLanguage(),\r\n\t\t\t\ttimestamp: getTimestamp(),\r\n\t\t\t},\r\n\t\t\tdata: {\r\n\t\t\t\ttagName: element.tagName,\r\n\t\t\t\tid: element.id || undefined,\r\n\t\t\t\tclassName: _getAllClasses(element),\r\n\t\t\t\ttitle: element.getAttribute('title') || undefined,\r\n\t\t\t\ttext: element.textContent ? truncate(element.textContent, 30) : undefined,\r\n\t\t\t\tselector: _generateSelector(element),\r\n\t\t\t},\r\n\t\t};\r\n\r\n\t\tthis._tracker.trackEvent(event);\r\n\t}\r\n}\r\n","/**\r\n * HistoryObserver: Monitorea cambios de navegación en SPAs\r\n */\r\n\r\nimport type { Tracker } from '../core/Tracker';\r\n\r\ninterface HistoryState {\r\n\turl?: string;\r\n\tpath?: string;\r\n}\r\n\r\ninterface HistoryWithTracking {\r\n\t_originalPushState?: typeof history.pushState;\r\n\t_originalReplaceState?: typeof history.replaceState;\r\n}\r\n\r\nexport class HistoryObserver {\r\n\tprivate _tracker: Tracker;\r\n\tprivate _isActive: boolean = false;\r\n\tprivate _lastFocusTime: number;\r\n\tprivate _accumulatedFocusTime: number;\r\n\tprivate _boundHandlePopState: (event: PopStateEvent) => void;\r\n\r\n\tconstructor(tracker: Tracker) {\r\n\t\tthis._tracker = tracker;\r\n\t\tthis._lastFocusTime = Date.now();\r\n\t\tthis._accumulatedFocusTime = 0;\r\n\t\tthis._boundHandlePopState = this._handlePopState.bind(this);\r\n\t}\r\n\r\n\t/** Inicia el observer */\r\n\tstart(): void {\r\n\t\tif (this._isActive) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tthis._patchHistoryMethods();\r\n\t\t\twindow.addEventListener('popstate', this._boundHandlePopState);\r\n\t\t\twindow.addEventListener('beforeunload', this._handleBeforeUnload);\r\n\t\t\twindow.addEventListener('focus', this._handleFocus);\r\n\t\t\twindow.addEventListener('blur', this._handleBlur);\r\n\t\t\tthis._isActive = true;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Detiene el observer */\r\n\tstop(): void {\r\n\t\tif (!this._isActive) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tthis._restoreHistoryMethods();\r\n\t\t\twindow.removeEventListener('popstate', this._boundHandlePopState);\r\n\t\t\twindow.removeEventListener('beforeunload', this._handleBeforeUnload);\r\n\t\t\twindow.removeEventListener('focus', this._handleFocus);\r\n\t\t\twindow.removeEventListener('blur', this._handleBlur);\r\n\t\t\tthis._isActive = false;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Aplica monkey-patch a los métodos history */\r\n\tprivate _patchHistoryMethods(): void {\r\n\t\tconst historyWithTracking = history as HistoryWithTracking & typeof history;\r\n\r\n\t\tif (!historyWithTracking._originalPushState) {\r\n\t\t\thistoryWithTracking._originalPushState = history.pushState;\r\n\t\t\thistory.pushState = this._wrappedPushState.bind(this);\r\n\t\t}\r\n\r\n\t\tif (!historyWithTracking._originalReplaceState) {\r\n\t\t\thistoryWithTracking._originalReplaceState = history.replaceState;\r\n\t\t\thistory.replaceState = this._wrappedReplaceState.bind(this);\r\n\t\t}\r\n\t}\r\n\r\n\t/** Restaura los métodos originales */\r\n\tprivate _restoreHistoryMethods(): void {\r\n\t\tconst historyWithTracking = history as HistoryWithTracking & typeof history;\r\n\r\n\t\tif (historyWithTracking._originalPushState) {\r\n\t\t\thistory.pushState = historyWithTracking._originalPushState;\r\n\t\t\thistoryWithTracking._originalPushState = undefined;\r\n\t\t}\r\n\r\n\t\tif (historyWithTracking._originalReplaceState) {\r\n\t\t\thistory.replaceState = historyWithTracking._originalReplaceState;\r\n\t\t\thistoryWithTracking._originalReplaceState = undefined;\r\n\t\t}\r\n\t}\r\n\r\n\t/** Wrapper para pushState */\r\n\tprivate _wrappedPushState(state: HistoryState, title: string, url?: string | URL | null): void {\r\n\t\ttry {\r\n\t\t\tconst historyWithTracking = history as HistoryWithTracking & typeof history;\r\n\t\t\thistoryWithTracking._originalPushState?.call(history, state, title, url);\r\n\t\t\tthis._handleNavigationChange();\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Wrapper para replaceState */\r\n\tprivate _wrappedReplaceState(state: HistoryState, title: string, url?: string | URL | null): void {\r\n\t\ttry {\r\n\t\t\tconst historyWithTracking = history as HistoryWithTracking & typeof history;\r\n\t\t\thistoryWithTracking._originalReplaceState?.call(history, state, title, url);\r\n\t\t\tthis._handleNavigationChange();\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Manejador de popstate */\r\n\tprivate _handlePopState(): void {\r\n\t\ttry {\r\n\t\t\tthis._handleNavigationChange();\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Manejador de focus (la ventana gagne foco) */\r\n\tprivate _handleFocus(): void {\r\n\t\ttry {\r\n\t\t\tthis._lastFocusTime = Date.now();\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Manejador de blur (la ventana pierde foco) */\r\n\tprivate _handleBlur(): void {\r\n\t\ttry {\r\n\t\t\t// Acumular el tiempo con foco hasta ahora\r\n\t\t\tthis._accumulatedFocusTime += Date.now() - this._lastFocusTime;\r\n\t\t\tthis._lastFocusTime = Date.now();\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Manejador de beforeunload (cuando el usuario sale de la página) */\r\n\tprivate _handleBeforeUnload(): void {\r\n\t\ttry {\r\n\t\t\t// Calcular tiempo total con foco\r\n\t\t\tconst duration = this._calculateFocusedDuration();\r\n\t\t\tthis._tracker.trackPageLeave(duration);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Calcula la duración total con foco */\r\n\tprivate _calculateFocusedDuration(): number {\r\n\t\tconst currentTime = Date.now();\r\n\t\tconst hasFocus = document.hasFocus();\r\n\r\n\t\t// Si tiene foco ahora, sumar el tiempo desde el último foco\r\n\t\tconst focusedTime = this._accumulatedFocusTime + (hasFocus ? (currentTime - this._lastFocusTime) : 0);\r\n\r\n\t\t// Convertir a segundos\r\n\t\treturn focusedTime / 1000;\r\n\t}\r\n\r\n\t/** Maneja cambios de navegación (pushState, replaceState, popstate) */\r\n\tprivate _handleNavigationChange(): void {\r\n\t\ttry {\r\n\t\t\tconst duration = this._calculateFocusedDuration();\r\n\t\t\tthis._tracker.trackPageLeave(duration);\r\n\t\t\tthis._tracker.trackPageView();\r\n\r\n\t\t\t// Resetear contadores para la nueva página\r\n\t\t\tthis._lastFocusTime = Date.now();\r\n\t\t\tthis._accumulatedFocusTime = 0;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n}\r\n","/**\r\n * Tracker: Singleton principal que coordina observers y EventBuffer\r\n */\r\n\r\nimport type { TrackerConfig, TrackerEvent, UserIdentity, EventContext } from '../types';\r\nimport { EventBuffer } from './EventBuffer';\r\nimport { ClickObserver } from '../observers/ClickObserver';\r\nimport { HistoryObserver } from '../observers/HistoryObserver';\r\nimport {\r\n\tgetCurrentPath,\r\n\tgetReferrer,\r\n\tgetScreenSize,\r\n\tgetTimestamp,\r\n\tgetOrCreateSessionId,\r\n\tgetViewportSize,\r\n\tgetDeviceType,\r\n\tgetBrowser,\r\n\tgetOS,\r\n\tgetLanguage,\r\n} from '../utils/metadata';\r\nimport { generateUUID } from '../utils/uuid';\r\n\r\nexport class Tracker {\r\n\tprivate static _instance: Tracker | null = null;\r\n\tprivate _eventBuffer: EventBuffer | null = null;\r\n\tprivate _clickObserver: ClickObserver | null = null;\r\n\tprivate _historyObserver: HistoryObserver | null = null;\r\n\tprivate _userIdentity: UserIdentity;\r\n\tprivate _sessionId: string;\r\n\tprivate _isInitialized: boolean = false;\r\n\r\n\tprivate constructor() {\r\n\t\tthis._userIdentity = {\r\n\t\t\tuserId: null,\r\n\t\t\tuserName: null,\r\n\t\t};\r\n\t\tthis._sessionId = getOrCreateSessionId();\r\n\t}\r\n\r\n\t/** Obtiene la instancia única del Tracker */\r\n\tstatic getInstance(): Tracker {\r\n\t\tif (Tracker._instance === null) {\r\n\t\t\tTracker._instance = new Tracker();\r\n\t\t}\r\n\t\treturn Tracker._instance;\r\n\t}\r\n\r\n\t/** Construye el contexto completo del evento */\r\n\tprivate _buildContext(): EventContext {\r\n\t\treturn {\r\n\t\t\tsessionId: this._sessionId,\r\n\t\t\tuserId: this._userIdentity.userId,\r\n\t\t\tuserName: this._userIdentity.userName,\r\n\t\t\turl: window.location.href,\r\n\t\t\treferrer: getReferrer(),\r\n\t\t\tscreenSize: getScreenSize(),\r\n\t\t\tviewportSize: getViewportSize(),\r\n\t\t\tdeviceType: getDeviceType(),\r\n\t\t\tbrowser: getBrowser(),\r\n\t\t\tos: getOS(),\r\n\t\t\tlanguage: getLanguage(),\r\n\t\t\ttimestamp: getTimestamp(),\r\n\t\t};\r\n\t}\r\n\r\n\t/** Inicializa el tracker con la configuración */\r\n\tinit(config: TrackerConfig): void {\r\n\t\ttry {\r\n\t\t\t// Actualizamos el usuario sólo si se proporciona y si no es el mismo\r\n\t\t\tif (config.userId && config.userId != this._userIdentity.userId) {\r\n\t\t\t\tthis._userIdentity = {\r\n\t\t\t\t\tuserId: config.userId,\r\n\t\t\t\t\tuserName: config.userName ?? null,\r\n\t\t\t\t};\r\n\t\t\t\tif (this._isInitialized && config.debug) {\r\n\t\t\t\t\tconsole.log('[EventTracker] Usuario actualizado:', this._userIdentity);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Solo inicializar EventBuffer y observers una vez\r\n\t\t\tif (this._isInitialized) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis._eventBuffer = new EventBuffer(config);\r\n\t\t\tthis._isInitialized = true;\r\n\r\n\t\t\t// Iniciar observers\r\n\t\t\tthis._clickObserver = new ClickObserver(this);\r\n\t\t\tthis._historyObserver = new HistoryObserver(this);\r\n\t\t\tthis._clickObserver.start();\r\n\t\t\tthis._historyObserver.start();\r\n\r\n\t\t\t// Enviar page_view inicial\r\n\t\t\tthis.trackPageView();\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Identifica al usuario */\r\n\tidentify(userId: string, userName?: string): void {\r\n\t\ttry {\r\n\t\t\tthis._userIdentity = {\r\n\t\t\t\tuserId,\r\n\t\t\t\tuserName: userName ?? null,\r\n\t\t\t};\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Registra un evento */\r\n\ttrackEvent(event: TrackerEvent): void {\r\n\t\ttry {\r\n\t\t\tif (!this._eventBuffer) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tconst eventWithContext: TrackerEvent = {\r\n\t\t\t\t...event,\r\n\t\t\t\teventId: generateUUID(),\r\n\t\t\t\tcontext: this._buildContext(),\r\n\t\t\t};\r\n\r\n\t\t\tthis._eventBuffer.add(eventWithContext);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Crea un evento de page view */\r\n\ttrackPageView(): void {\r\n\t\ttry {\r\n\t\t\tconst event: TrackerEvent = {\r\n\t\t\t\teventType: 'page_view',\r\n\t\t\t\teventId: generateUUID(),\r\n\t\t\t\tcontext: this._buildContext(),\r\n\t\t\t\tdata: {\r\n\t\t\t\t\tpath: getCurrentPath(),\r\n\t\t\t\t},\r\n\t\t\t};\r\n\r\n\t\t\tthis.trackEvent(event);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Crea un evento de page leave */\r\n\ttrackPageLeave(duration: number): void {\r\n\t\ttry {\r\n\t\t\tconst event: TrackerEvent = {\r\n\t\t\t\teventType: 'page_leave',\r\n\t\t\t\teventId: generateUUID(),\r\n\t\t\t\tcontext: this._buildContext(),\r\n\t\t\t\tdata: {\r\n\t\t\t\t\tduration,\r\n\t\t\t\t\tpath: getCurrentPath(),\r\n\t\t\t\t},\r\n\t\t\t};\r\n\r\n\t\t\tthis.trackEvent(event);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Fuerza el envío de todos los eventos pendientes */\r\n\tasync flush(): Promise<void> {\r\n\t\ttry {\r\n\t\t\tif (this._eventBuffer) {\r\n\t\t\t\tawait this._eventBuffer.flush();\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Verifica si el tracker está inicializado */\r\n\tisInitialized(): boolean {\r\n\t\treturn this._isInitialized;\r\n\t}\r\n\r\n\t/** Destruye el tracker */\r\n\tdestroy(): void {\r\n\t\ttry {\r\n\t\t\tif (this._clickObserver) {\r\n\t\t\t\tthis._clickObserver.stop();\r\n\t\t\t\tthis._clickObserver = null;\r\n\t\t\t}\r\n\t\t\tif (this._historyObserver) {\r\n\t\t\t\tthis._historyObserver.stop();\r\n\t\t\t\tthis._historyObserver = null;\r\n\t\t\t}\r\n\t\t\tif (this._eventBuffer) {\r\n\t\t\t\tthis._eventBuffer.destroy();\r\n\t\t\t\tthis._eventBuffer = null;\r\n\t\t\t}\r\n\t\t\tthis._isInitialized = false;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACMA,IAAM,iBAAiB;AACvB,IAAM,kBAAkB;AAEjB,IAAM,cAAN,MAAkB;AAAA,EAQxB,YAAY,QAAuB;AALnC,SAAQ,cAAqD;AAM5D,SAAK,UAAU,CAAC;AAGhB,SAAK,YAAY,OAAO;AACxB,SAAK,UAAU,OAAO;AAGtB,SAAK,WAAW,OAAO,SAAS;AAChC,YAAQ,IAAI,MAAM;AAElB,SAAK,UAAU;AAAA,MACd,eAAe,OAAO,iBAAiB;AAAA,MACvC,eAAe,OAAO,iBAAiB;AAAA,IACxC;AAEA,QAAI,KAAK,UAAU;AAClB,cAAQ,IAAI,oCAAoC;AAChD,cAAQ,IAAI,4BAA4B,KAAK,SAAS;AACtD,cAAQ,IAAI,2BAA2B,KAAK,OAAO;AAAA,IACpD;AAGA,QAAI,CAAC,KAAK,UAAU;AACnB,WAAK,iBAAiB;AAAA,IACvB;AAEA,SAAK,yBAAyB;AAAA,EAC/B;AAAA;AAAA,EAGA,IAAI,OAA2B;AAC9B,QAAI;AACH,WAAK,QAAQ,KAAK,KAAK;AAEvB,UAAI,KAAK,UAAU;AAClB,gBAAQ,IAAI,mCAAmC,MAAM,SAAS;AAC9D,gBAAQ,IAAI,+BAA+B,KAAK,QAAQ,MAAM;AAC9D,gBAAQ,IAAI,iCAAiC,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,MAC5E;AAGA,UAAI,CAAC,KAAK,YAAY,KAAK,QAAQ,UAAU,KAAK,QAAQ,eAAe;AACxE,aAAK,MAAM;AAAA,MACZ;AAAA,IACD,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,QAA6B;AAClC,QAAI;AACH,UAAI,KAAK,QAAQ,WAAW,GAAG;AAC9B,eAAO,EAAE,SAAS,KAAK;AAAA,MACxB;AAEA,YAAM,eAAe,CAAC,GAAG,KAAK,OAAO;AACrC,WAAK,UAAU,CAAC;AAGhB,UAAI,KAAK,UAAU;AAClB,gBAAQ,IAAI,kCAAkC,KAAK,UAAU,cAAc,MAAM,CAAC,CAAC;AACnF,eAAO,EAAE,SAAS,KAAK;AAAA,MACxB;AAEA,YAAM,SAAS,MAAM,KAAK,iBAAiB,YAAY;AACvD,aAAO;AAAA,IACR,SAAS,OAAO;AACf,aAAO;AAAA,QACN,SAAS;AAAA,QACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACjD;AAAA,IACD;AAAA,EACD;AAAA;AAAA,EAGA,UAAkB;AACjB,WAAO,KAAK,QAAQ;AAAA,EACrB;AAAA;AAAA,EAGA,QAAc;AACb,SAAK,UAAU,CAAC;AAAA,EACjB;AAAA;AAAA,EAGA,UAAgB;AACf,QAAI,KAAK,aAAa;AACrB,oBAAc,KAAK,WAAW;AAC9B,WAAK,cAAc;AAAA,IACpB;AAEA,SAAK,UAAU,CAAC;AAAA,EACjB;AAAA;AAAA,EAGQ,mBAAyB;AAChC,SAAK,cAAc,YAAY,MAAM;AACpC,UAAI,KAAK,QAAQ,SAAS,GAAG;AAC5B,aAAK,MAAM;AAAA,MACZ;AAAA,IACD,GAAG,KAAK,QAAQ,aAAa;AAAA,EAC9B;AAAA;AAAA,EAGQ,2BAAiC;AACxC,QAAI,OAAO,aAAa,aAAa;AACpC;AAAA,IACD;AAEA,UAAM,yBAAyB,MAAY;AAE1C,UAAI,KAAK,UAAU;AAClB;AAAA,MACD;AAEA,UAAI,SAAS,oBAAoB,YAAY,KAAK,QAAQ,SAAS,GAAG;AAErE,mBAAW,MAAM;AAChB,eAAK,MAAM;AAAA,QACZ,GAAG,CAAC;AAAA,MACL;AAAA,IACD;AAEA,aAAS,iBAAiB,oBAAoB,sBAAsB;AAAA,EACrE;AAAA;AAAA,EAGA,MAAc,iBAAiB,QAA6C;AAC3E,QAAI;AACH,YAAM,UAAU,KAAK,UAAU;AAAA,QAC9B,QAAQ,KAAK;AAAA,QACb;AAAA,MACD,CAAC;AAED,YAAM,WAAW,MAAM,MAAM,KAAK,WAAW;AAAA,QAC5C,QAAQ;AAAA,QACR,SAAS;AAAA,UACR,gBAAgB;AAAA,QACjB;AAAA,QACA,MAAM;AAAA,QACN,WAAW;AAAA,MACZ,CAAC;AAGD,UAAI;AACJ,UAAI;AACH,cAAM,eAAiC,MAAM,SAAS,KAAK;AAC3D,kBAAU,aAAa;AAAA,MACxB,QAAQ;AAEP,kBAAU,SAAS;AAAA,MACpB;AAEA,aAAO;AAAA,QACN,SAAS,SAAS;AAAA,QAClB,YAAY,SAAS;AAAA,QACrB;AAAA,MACD;AAAA,IACD,SAAS,OAAO;AACf,aAAO;AAAA,QACN,SAAS;AAAA,QACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACjD;AAAA,IACD;AAAA,EACD;AACD;;;ACnLO,SAAS,eAAuB;AACtC,SAAO,uCAAuC,QAAQ,SAAS,CAAC,SAAS;AACxE,UAAM,SAAS,KAAK,OAAO,IAAI,KAAK;AACpC,UAAM,QAAQ,SAAS,MAAM,SAAU,SAAS,IAAO;AACvD,WAAO,MAAM,SAAS,EAAE;AAAA,EACzB,CAAC;AACF;;;ACDO,SAAS,SAAS,MAAc,WAA2B;AACjE,MAAI,YAAY,GAAG;AAClB,WAAO;AAAA,EACR;AAEA,MAAI,KAAK,UAAU,WAAW;AAC7B,WAAO;AAAA,EACR;AAEA,SAAO,KAAK,UAAU,GAAG,YAAY,CAAC,IAAI;AAC3C;;;ACbO,SAAS,uBAA+B;AAC9C,MAAI;AACH,UAAM,aAAa;AACnB,UAAM,oBAAoB,eAAe,QAAQ,UAAU;AAE3D,QAAI,mBAAmB;AACtB,aAAO;AAAA,IACR;AAEA,UAAM,eAAe,aAAa;AAClC,mBAAe,QAAQ,YAAY,YAAY;AAC/C,WAAO;AAAA,EACR,QAAQ;AACP,WAAO,aAAa;AAAA,EACrB;AACD;AAGO,SAAS,gBAAwB;AACvC,MAAI,OAAO,WAAW,aAAa;AAClC,WAAO;AAAA,EACR;AAEA,SAAO,GAAG,OAAO,UAAU,IAAI,OAAO,WAAW;AAClD;AAGO,SAAS,kBAA0B;AACzC,MAAI,OAAO,WAAW,aAAa;AAClC,WAAO;AAAA,EACR;AAEA,SAAO,GAAG,OAAO,UAAU,IAAI,OAAO,WAAW;AAClD;AAGO,SAAS,gBAA6D;AAC5E,MAAI,OAAO,cAAc,aAAa;AACrC,WAAO;AAAA,EACR;AAEA,QAAM,KAAK,UAAU;AAErB,MAAI,mDAAmD,KAAK,EAAE,GAAG;AAChE,WAAO;AAAA,EACR;AAEA,MAAI,sGAAsG,KAAK,EAAE,GAAG;AACnH,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AAGO,SAAS,aAAqB;AACpC,MAAI,OAAO,cAAc,aAAa;AACrC,WAAO;AAAA,EACR;AAEA,QAAM,KAAK,UAAU;AAErB,MAAI,GAAG,SAAS,SAAS,GAAG;AAC3B,WAAO;AAAA,EACR;AAEA,MAAI,GAAG,SAAS,MAAM,GAAG;AACxB,WAAO;AAAA,EACR;AAEA,MAAI,GAAG,SAAS,QAAQ,GAAG;AAC1B,WAAO;AAAA,EACR;AAEA,MAAI,GAAG,SAAS,QAAQ,GAAG;AAC1B,WAAO;AAAA,EACR;AAEA,MAAI,GAAG,SAAS,MAAM,KAAK,GAAG,SAAS,UAAU,GAAG;AACnD,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AAGO,SAAS,QAAgB;AAC/B,MAAI,OAAO,cAAc,aAAa;AACrC,WAAO;AAAA,EACR;AAEA,QAAM,KAAK,UAAU;AAErB,MAAI,GAAG,SAAS,SAAS,GAAG;AAC3B,WAAO;AAAA,EACR;AAEA,MAAI,GAAG,SAAS,QAAQ,GAAG;AAC1B,WAAO;AAAA,EACR;AAEA,MAAI,GAAG,SAAS,OAAO,GAAG;AACzB,WAAO;AAAA,EACR;AAEA,MAAI,GAAG,SAAS,SAAS,GAAG;AAC3B,WAAO;AAAA,EACR;AAEA,MAAI,GAAG,SAAS,KAAK,KAAK,GAAG,SAAS,QAAQ,KAAK,GAAG,SAAS,MAAM,GAAG;AACvE,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AAGO,SAAS,cAAsB;AACrC,MAAI,OAAO,cAAc,aAAa;AACrC,WAAO;AAAA,EACR;AAEA,SAAO,UAAU,YAAY;AAC9B;AAYO,SAAS,cAAsB;AACrC,MAAI,OAAO,WAAW,aAAa;AAClC,WAAO;AAAA,EACR;AAEA,SAAO,SAAS;AACjB;AAGO,SAAS,eAAuB;AACtC,UAAO,oBAAI,KAAK,GAAE,YAAY;AAC/B;AAGO,SAAS,iBAAyB;AACxC,MAAI,OAAO,WAAW,aAAa;AAClC,WAAO;AAAA,EACR;AAEA,SAAO,OAAO,SAAS;AACxB;;;AC9IA,IAAM,iBAAiB,CAAC,UAAU,KAAK,SAAS,KAAK,QAAQ,KAAK;AAGlE,IAAM,wBAAwB,CAAC,QAAQ;AAGvC,IAAM,wBAAwB,CAAC,YAAY,SAAS,OAAO,UAAU,UAAU,KAAK;AAGpF,IAAM,sBAAsB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAGA,SAAS,oBAAoB,SAA2B;AACvD,MAAI,QAAQ,YAAY,SAAS;AAChC,UAAM,YAAa,QAA6B,MAAM,YAAY,KAAK;AACvE,WAAO,sBAAsB,SAAS,SAAS;AAAA,EAChD;AACA,SAAO;AACR;AAGA,SAAS,oBAAoB,SAA2B;AACvD,QAAM,UAAU,QAAQ,QAAQ,YAAY;AAE5C,MAAI,CAAC,eAAe,SAAS,OAAO,GAAG;AACtC,WAAO,QAAQ,aAAa,YAAY,KACvC,kBAAkB,OAAO,KACzB,WAAW,OAAO;AAAA,EACpB;AAEA,MAAI,YAAY,SAAS;AACxB,UAAM,YAAa,QAA6B,MAAM,YAAY,KAAK;AACvE,WAAO,sBAAsB,SAAS,SAAS;AAAA,EAChD;AAEA,SAAO;AACR;AAGA,SAAS,kBAAkB,SAA2B;AACrD,MAAI;AACH,UAAM,QAAQ,OAAO,iBAAiB,OAAO;AAC7C,WAAO,MAAM,WAAW;AAAA,EACzB,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAGA,SAAS,WAAW,SAA2B;AAC9C,SAAO,QAAQ,YAAY,OAAO,QAAQ,UAAU,SAAS,KAAK;AACnE;AAGA,SAAS,kBAAkB,SAA0B;AACpD,QAAM,QAAkB,CAAC;AACzB,MAAI,UAA0B;AAE9B,SAAO,WAAW,YAAY,SAAS,iBAAiB;AACvD,QAAI,WAAW,QAAQ,QAAQ,YAAY;AAE3C,QAAI,QAAQ,IAAI;AACf,kBAAY,IAAI,QAAQ,EAAE;AAC1B,YAAM,QAAQ,QAAQ;AACtB;AAAA,IACD,WAAW,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AACtE,YAAM,UAAU,QAAQ,UAAU,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AACpE,UAAI,QAAQ,SAAS,GAAG;AACvB,oBAAY,IAAI,QAAQ,CAAC,CAAC;AAAA,MAC3B;AAAA,IACD;AAEA,UAAM,QAAQ,QAAQ;AACtB,cAAU,QAAQ;AAAA,EACnB;AAEA,SAAO,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,KAAK;AACpC;AAGA,SAAS,eAAe,SAAsC;AAC7D,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAChE,WAAO;AAAA,EACR;AACA,QAAM,UAAU,QAAQ,UAAU,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AACpE,SAAO,QAAQ,SAAS,IAAI,QAAQ,KAAK,GAAG,IAAI;AACjD;AAEO,IAAM,gBAAN,MAAoB;AAAA,EAK1B,YAAYA,UAAkB;AAH9B,SAAQ,YAAqB;AAI5B,SAAK,WAAWA;AAChB,SAAK,oBAAoB,KAAK,aAAa,KAAK,IAAI;AAAA,EACrD;AAAA;AAAA,EAGA,QAAc;AACb,QAAI,KAAK,WAAW;AACnB;AAAA,IACD;AAEA,QAAI;AACH,aAAO,iBAAiB,SAAS,KAAK,mBAAmB,IAAI;AAC7D,WAAK,YAAY;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,OAAa;AACZ,QAAI,CAAC,KAAK,WAAW;AACpB;AAAA,IACD;AAEA,QAAI;AACH,aAAO,oBAAoB,SAAS,KAAK,mBAAmB,IAAI;AAChE,WAAK,YAAY;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,aAAa,OAAyB;AAC7C,QAAI;AACH,YAAM,SAAS,MAAM;AAErB,UAAI,CAAC,QAAQ;AACZ;AAAA,MACD;AAGA,UAAI,oBAAoB,MAAM,KAAK,CAAC,oBAAoB,MAAM,GAAG;AAChE,aAAK,kBAAkB,MAAM;AAC7B;AAAA,MACD;AAGA,YAAM,mBAAmB,OAAO,QAAQ,oBAAoB,KAAK,GAAG,CAAC;AAErE,UAAI,CAAC,kBAAkB;AACtB;AAAA,MACD;AAEA,UAAI,oBAAoB,gBAAgB,GAAG;AAC1C;AAAA,MACD;AAEA,UAAI,CAAC,oBAAoB,gBAAgB,GAAG;AAC3C;AAAA,MACD;AAEA,WAAK,kBAAkB,gBAAgB;AAAA,IACxC,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,kBAAkB,SAAwB;AACjD,UAAM,QAAsB;AAAA,MAC3B,WAAW;AAAA,MACX,SAAS,aAAa;AAAA,MACtB,SAAS;AAAA,QACR,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,KAAK,OAAO,SAAS;AAAA,QACrB,UAAU,YAAY;AAAA,QACtB,YAAY,cAAc;AAAA,QAC1B,cAAc,gBAAgB;AAAA,QAC9B,YAAY,cAAc;AAAA,QAC1B,SAAS,WAAW;AAAA,QACpB,IAAI,MAAM;AAAA,QACV,UAAU,YAAY;AAAA,QACtB,WAAW,aAAa;AAAA,MACzB;AAAA,MACA,MAAM;AAAA,QACL,SAAS,QAAQ;AAAA,QACjB,IAAI,QAAQ,MAAM;AAAA,QAClB,WAAW,eAAe,OAAO;AAAA,QACjC,OAAO,QAAQ,aAAa,OAAO,KAAK;AAAA,QACxC,MAAM,QAAQ,cAAc,SAAS,QAAQ,aAAa,EAAE,IAAI;AAAA,QAChE,UAAU,kBAAkB,OAAO;AAAA,MACpC;AAAA,IACD;AAEA,SAAK,SAAS,WAAW,KAAK;AAAA,EAC/B;AACD;;;AC5MO,IAAM,kBAAN,MAAsB;AAAA,EAO5B,YAAYC,UAAkB;AAL9B,SAAQ,YAAqB;AAM5B,SAAK,WAAWA;AAChB,SAAK,iBAAiB,KAAK,IAAI;AAC/B,SAAK,wBAAwB;AAC7B,SAAK,uBAAuB,KAAK,gBAAgB,KAAK,IAAI;AAAA,EAC3D;AAAA;AAAA,EAGA,QAAc;AACb,QAAI,KAAK,WAAW;AACnB;AAAA,IACD;AAEA,QAAI;AACH,WAAK,qBAAqB;AAC1B,aAAO,iBAAiB,YAAY,KAAK,oBAAoB;AAC7D,aAAO,iBAAiB,gBAAgB,KAAK,mBAAmB;AAChE,aAAO,iBAAiB,SAAS,KAAK,YAAY;AAClD,aAAO,iBAAiB,QAAQ,KAAK,WAAW;AAChD,WAAK,YAAY;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,OAAa;AACZ,QAAI,CAAC,KAAK,WAAW;AACpB;AAAA,IACD;AAEA,QAAI;AACH,WAAK,uBAAuB;AAC5B,aAAO,oBAAoB,YAAY,KAAK,oBAAoB;AAChE,aAAO,oBAAoB,gBAAgB,KAAK,mBAAmB;AACnE,aAAO,oBAAoB,SAAS,KAAK,YAAY;AACrD,aAAO,oBAAoB,QAAQ,KAAK,WAAW;AACnD,WAAK,YAAY;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,uBAA6B;AACpC,UAAM,sBAAsB;AAE5B,QAAI,CAAC,oBAAoB,oBAAoB;AAC5C,0BAAoB,qBAAqB,QAAQ;AACjD,cAAQ,YAAY,KAAK,kBAAkB,KAAK,IAAI;AAAA,IACrD;AAEA,QAAI,CAAC,oBAAoB,uBAAuB;AAC/C,0BAAoB,wBAAwB,QAAQ;AACpD,cAAQ,eAAe,KAAK,qBAAqB,KAAK,IAAI;AAAA,IAC3D;AAAA,EACD;AAAA;AAAA,EAGQ,yBAA+B;AACtC,UAAM,sBAAsB;AAE5B,QAAI,oBAAoB,oBAAoB;AAC3C,cAAQ,YAAY,oBAAoB;AACxC,0BAAoB,qBAAqB;AAAA,IAC1C;AAEA,QAAI,oBAAoB,uBAAuB;AAC9C,cAAQ,eAAe,oBAAoB;AAC3C,0BAAoB,wBAAwB;AAAA,IAC7C;AAAA,EACD;AAAA;AAAA,EAGQ,kBAAkB,OAAqB,OAAe,KAAiC;AAC9F,QAAI;AACH,YAAM,sBAAsB;AAC5B,0BAAoB,oBAAoB,KAAK,SAAS,OAAO,OAAO,GAAG;AACvE,WAAK,wBAAwB;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,qBAAqB,OAAqB,OAAe,KAAiC;AACjG,QAAI;AACH,YAAM,sBAAsB;AAC5B,0BAAoB,uBAAuB,KAAK,SAAS,OAAO,OAAO,GAAG;AAC1E,WAAK,wBAAwB;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,kBAAwB;AAC/B,QAAI;AACH,WAAK,wBAAwB;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,eAAqB;AAC5B,QAAI;AACH,WAAK,iBAAiB,KAAK,IAAI;AAAA,IAChC,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,cAAoB;AAC3B,QAAI;AAEH,WAAK,yBAAyB,KAAK,IAAI,IAAI,KAAK;AAChD,WAAK,iBAAiB,KAAK,IAAI;AAAA,IAChC,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,sBAA4B;AACnC,QAAI;AAEH,YAAM,WAAW,KAAK,0BAA0B;AAChD,WAAK,SAAS,eAAe,QAAQ;AAAA,IACtC,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGQ,4BAAoC;AAC3C,UAAM,cAAc,KAAK,IAAI;AAC7B,UAAM,WAAW,SAAS,SAAS;AAGnC,UAAM,cAAc,KAAK,yBAAyB,WAAY,cAAc,KAAK,iBAAkB;AAGnG,WAAO,cAAc;AAAA,EACtB;AAAA;AAAA,EAGQ,0BAAgC;AACvC,QAAI;AACH,YAAM,WAAW,KAAK,0BAA0B;AAChD,WAAK,SAAS,eAAe,QAAQ;AACrC,WAAK,SAAS,cAAc;AAG5B,WAAK,iBAAiB,KAAK,IAAI;AAC/B,WAAK,wBAAwB;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACD;AACD;;;AClKO,IAAM,WAAN,MAAM,SAAQ;AAAA,EASZ,cAAc;AAPtB,SAAQ,eAAmC;AAC3C,SAAQ,iBAAuC;AAC/C,SAAQ,mBAA2C;AAGnD,SAAQ,iBAA0B;AAGjC,SAAK,gBAAgB;AAAA,MACpB,QAAQ;AAAA,MACR,UAAU;AAAA,IACX;AACA,SAAK,aAAa,qBAAqB;AAAA,EACxC;AAAA;AAAA,EAGA,OAAO,cAAuB;AAC7B,QAAI,SAAQ,cAAc,MAAM;AAC/B,eAAQ,YAAY,IAAI,SAAQ;AAAA,IACjC;AACA,WAAO,SAAQ;AAAA,EAChB;AAAA;AAAA,EAGQ,gBAA8B;AACrC,WAAO;AAAA,MACN,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,cAAc;AAAA,MAC3B,UAAU,KAAK,cAAc;AAAA,MAC7B,KAAK,OAAO,SAAS;AAAA,MACrB,UAAU,YAAY;AAAA,MACtB,YAAY,cAAc;AAAA,MAC1B,cAAc,gBAAgB;AAAA,MAC9B,YAAY,cAAc;AAAA,MAC1B,SAAS,WAAW;AAAA,MACpB,IAAI,MAAM;AAAA,MACV,UAAU,YAAY;AAAA,MACtB,WAAW,aAAa;AAAA,IACzB;AAAA,EACD;AAAA;AAAA,EAGA,KAAK,QAA6B;AACjC,QAAI;AAEH,UAAI,OAAO,UAAU,OAAO,UAAU,KAAK,cAAc,QAAQ;AAChE,aAAK,gBAAgB;AAAA,UACpB,QAAQ,OAAO;AAAA,UACf,UAAU,OAAO,YAAY;AAAA,QAC9B;AACA,YAAI,KAAK,kBAAkB,OAAO,OAAO;AACxC,kBAAQ,IAAI,uCAAuC,KAAK,aAAa;AAAA,QACtE;AAAA,MACD;AAGA,UAAI,KAAK,gBAAgB;AACxB;AAAA,MACD;AAEA,WAAK,eAAe,IAAI,YAAY,MAAM;AAC1C,WAAK,iBAAiB;AAGtB,WAAK,iBAAiB,IAAI,cAAc,IAAI;AAC5C,WAAK,mBAAmB,IAAI,gBAAgB,IAAI;AAChD,WAAK,eAAe,MAAM;AAC1B,WAAK,iBAAiB,MAAM;AAG5B,WAAK,cAAc;AAAA,IACpB,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,SAAS,QAAgB,UAAyB;AACjD,QAAI;AACH,WAAK,gBAAgB;AAAA,QACpB;AAAA,QACA,UAAU,YAAY;AAAA,MACvB;AAAA,IACD,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,WAAW,OAA2B;AACrC,QAAI;AACH,UAAI,CAAC,KAAK,cAAc;AACvB;AAAA,MACD;AAEA,YAAM,mBAAiC;AAAA,QACtC,GAAG;AAAA,QACH,SAAS,aAAa;AAAA,QACtB,SAAS,KAAK,cAAc;AAAA,MAC7B;AAEA,WAAK,aAAa,IAAI,gBAAgB;AAAA,IACvC,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,gBAAsB;AACrB,QAAI;AACH,YAAM,QAAsB;AAAA,QAC3B,WAAW;AAAA,QACX,SAAS,aAAa;AAAA,QACtB,SAAS,KAAK,cAAc;AAAA,QAC5B,MAAM;AAAA,UACL,MAAM,eAAe;AAAA,QACtB;AAAA,MACD;AAEA,WAAK,WAAW,KAAK;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,eAAe,UAAwB;AACtC,QAAI;AACH,YAAM,QAAsB;AAAA,QAC3B,WAAW;AAAA,QACX,SAAS,aAAa;AAAA,QACtB,SAAS,KAAK,cAAc;AAAA,QAC5B,MAAM;AAAA,UACL;AAAA,UACA,MAAM,eAAe;AAAA,QACtB;AAAA,MACD;AAEA,WAAK,WAAW,KAAK;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC5B,QAAI;AACH,UAAI,KAAK,cAAc;AACtB,cAAM,KAAK,aAAa,MAAM;AAAA,MAC/B;AAAA,IACD,QAAQ;AAAA,IAER;AAAA,EACD;AAAA;AAAA,EAGA,gBAAyB;AACxB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,UAAgB;AACf,QAAI;AACH,UAAI,KAAK,gBAAgB;AACxB,aAAK,eAAe,KAAK;AACzB,aAAK,iBAAiB;AAAA,MACvB;AACA,UAAI,KAAK,kBAAkB;AAC1B,aAAK,iBAAiB,KAAK;AAC3B,aAAK,mBAAmB;AAAA,MACzB;AACA,UAAI,KAAK,cAAc;AACtB,aAAK,aAAa,QAAQ;AAC1B,aAAK,eAAe;AAAA,MACrB;AACA,WAAK,iBAAiB;AAAA,IACvB,QAAQ;AAAA,IAER;AAAA,EACD;AACD;AAtLa,SACG,YAA4B;AADrC,IAAMC,WAAN;;;APDA,IAAM,UAAUC,SAAQ,YAAY;AAKpC,SAAS,KAAK,QAA6B;AACjD,UAAQ,KAAK,MAAM;AACpB;AAGO,SAAS,SAAS,QAAgB,UAAyB;AACjE,UAAQ,SAAS,QAAQ,QAAQ;AAClC;AAGO,SAAS,QAAuB;AACtC,SAAO,QAAQ,MAAM;AACtB;AAGO,SAAS,UAAgB;AAC/B,UAAQ,QAAQ;AACjB;","names":["tracker","tracker","Tracker","Tracker"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/EventBuffer.ts","../src/utils/uuid.ts","../src/utils/truncate.ts","../src/utils/metadata.ts","../src/observers/ClickObserver.ts","../src/observers/HistoryObserver.ts","../src/core/Tracker.ts"],"sourcesContent":["/**\r\n * Event Tracker SDK\r\n *\r\n * SDK ligero y agnóstico al framework para tracking de comportamiento frontend.\r\n *\r\n * @example\r\n * import { tracker } from '@agroideas/event-tracker-sdk';\r\n *\r\n * tracker.init({\r\n * apiKey: 'tu-api-key',\r\n * endpoint: 'https://tu-api.com/events'\r\n * });\r\n *\r\n * // Identificar usuario\r\n * tracker.identify('user-123', 'Juan Pérez');\r\n */\r\n\r\nimport { Tracker } from './core/Tracker';\r\nimport type { TrackerConfig } from './types';\r\n\r\n/** Instancia única exportada del Tracker */\r\nexport const tracker = Tracker.getInstance();\r\n\r\n/**\r\n * Inicializa el tracker con configuración requerida (apiKey y endpoint).\r\n */\r\nexport function init(config: TrackerConfig): void {\r\n\ttracker.init(config);\r\n}\r\n\r\n/** Identifica al usuario */\r\nexport function identify(userId: string, userName?: string): void {\r\n\ttracker.identify(userId, userName);\r\n}\r\n\r\n/** Fuerza el envío de eventos pendientes */\r\nexport function flush(): Promise<void> {\r\n\treturn tracker.flush();\r\n}\r\n\r\n/** Destruye el tracker */\r\nexport function destroy(): void {\r\n\ttracker.destroy();\r\n}\r\n\r\nexport type { TrackerConfig };\r\n","/**\r\n * EventBuffer: Maneja la acumulación, batching y envío de eventos\r\n */\r\n\r\nimport type { TrackerEvent, SendResult, TrackerConfig, TrackingResponse } from '../types';\r\n\r\nconst BATCH_INTERVAL = 10000;\r\nconst MAX_BUFFER_SIZE = 15;\r\n\r\nexport class EventBuffer {\r\n\tprivate _buffer: TrackerEvent[];\r\n\tprivate _config: Required<Omit<TrackerConfig, 'apiKey' | 'endpoint' | 'userId' | 'userName' | 'debug'>>;\r\n\tprivate _flushTimer: ReturnType<typeof setInterval> | null = null;\r\n\tprivate _endpoint: string;\r\n\tprivate _apiKey: string;\r\n\tprivate _isDebug: boolean;\r\n\r\n\tconstructor(config: TrackerConfig) {\r\n\t\tthis._buffer = [];\r\n\r\n\t\t// apiKey y endpoint son requeridos\r\n\t\tthis._endpoint = config.endpoint;\r\n\t\tthis._apiKey = config.apiKey;\r\n\r\n\t\t// DEBUG es opcional, default false\r\n\t\tthis._isDebug = config.debug == \"true\";\r\n\t\tconsole.log(config);\r\n\r\n\t\tthis._config = {\r\n\t\t\tbatchInterval: config.batchInterval ?? BATCH_INTERVAL,\r\n\t\t\tmaxBufferSize: config.maxBufferSize ?? MAX_BUFFER_SIZE,\r\n\t\t};\r\n\r\n\t\tif (this._isDebug) {\r\n\t\t\tconsole.log('[EventTracker] Modo DEBUG activado');\r\n\t\t\tconsole.log('[EventTracker] Endpoint:', this._endpoint);\r\n\t\t\tconsole.log('[EventTracker] API Key:', this._apiKey);\r\n\t\t}\r\n\r\n\t\t// En modo debug, no iniciar timer de flush\r\n\t\tif (!this._isDebug) {\r\n\t\t\tthis._startFlushTimer();\r\n\t\t}\r\n\r\n\t\tthis._setupVisibilityListener();\r\n\t}\r\n\r\n\t/** Agrega un evento al buffer */\r\n\tadd(event: TrackerEvent): void {\r\n\t\ttry {\r\n\t\t\tthis._buffer.push(event);\r\n\r\n\t\t\tif (this._isDebug) {\r\n\t\t\t\tconsole.log('[EventTracker] Evento agregado:', event.eventType);\r\n\t\t\t\tconsole.log('[EventTracker] Buffer size:', this._buffer.length);\r\n\t\t\t\tconsole.log('[EventTracker] Event content:', JSON.stringify(event, null, 2));\r\n\t\t\t}\r\n\r\n\t\t\t// En modo debug, no hacer flush automático\r\n\t\t\tif (!this._isDebug && this._buffer.length >= this._config.maxBufferSize) {\r\n\t\t\t\tthis.flush();\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Silently ignore errors to never block the main app\r\n\t\t}\r\n\t}\r\n\r\n\t/** Envía todos los eventos acumulados al servidor */\r\n\tasync flush(): Promise<SendResult> {\r\n\t\ttry {\r\n\t\t\tif (this._buffer.length === 0) {\r\n\t\t\t\treturn { success: true };\r\n\t\t\t}\r\n\r\n\t\t\tconst eventsToSend = [...this._buffer];\r\n\t\t\tthis._buffer = [];\r\n\r\n\t\t\t// Modo debug: solo console.log, no fetch\r\n\t\t\tif (this._isDebug) {\r\n\t\t\t\tconsole.log('[EventTracker] DEBUG - Events:', JSON.stringify(eventsToSend, null, 2));\r\n\t\t\t\treturn { success: true };\r\n\t\t\t}\r\n\r\n\t\t\tconst result = await this._sendEventsAsync(eventsToSend);\r\n\t\t\treturn result;\r\n\t\t} catch (error) {\r\n\t\t\treturn {\r\n\t\t\t\tsuccess: false,\r\n\t\t\t\terror: error instanceof Error ? error.message : 'Unknown error',\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\t/** Obtiene la cantidad de eventos en el buffer */\r\n\tgetSize(): number {\r\n\t\treturn this._buffer.length;\r\n\t}\r\n\r\n\t/** Limpia el buffer sin enviar */\r\n\tclear(): void {\r\n\t\tthis._buffer = [];\r\n\t}\r\n\r\n\t/** Destruye el buffer y limpia recursos */\r\n\tdestroy(): void {\r\n\t\tif (this._flushTimer) {\r\n\t\t\tclearInterval(this._flushTimer);\r\n\t\t\tthis._flushTimer = null;\r\n\t\t}\r\n\r\n\t\tthis._buffer = [];\r\n\t}\r\n\r\n\t/** Inicia el temporizador de flush periódico */\r\n\tprivate _startFlushTimer(): void {\r\n\t\tthis._flushTimer = setInterval(() => {\r\n\t\t\tif (this._buffer.length > 0) {\r\n\t\t\t\tthis.flush();\r\n\t\t\t}\r\n\t\t}, this._config.batchInterval);\r\n\t}\r\n\r\n\t/** Configura el listener de visibilitychange para envío de emergencia */\r\n\tprivate _setupVisibilityListener(): void {\r\n\t\tif (typeof document === 'undefined') {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst handleVisibilityChange = (): void => {\r\n\t\t\t// En modo debug, no enviar eventos\r\n\t\t\tif (this._isDebug) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (document.visibilityState === 'hidden' && this._buffer.length > 0) {\r\n\t\t\t\t// Usar setTimeout para asegurar que el fetch se complete antes del unload\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tthis.flush();\r\n\t\t\t\t}, 0);\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tdocument.addEventListener('visibilitychange', handleVisibilityChange);\r\n\t}\r\n\r\n\t/** Envía eventos usando fetch con keepalive (funciona incluso en page unload) */\r\n\tprivate async _sendEventsAsync(events: TrackerEvent[]): Promise<SendResult> {\r\n\t\ttry {\r\n\t\t\tconst payload = JSON.stringify({\r\n\t\t\t\tevents: events,\r\n\t\t\t});\r\n\r\n\t\t\tconst response = await fetch(this._endpoint, {\r\n\t\t\t\tmethod: 'POST',\r\n\t\t\t\theaders: {\r\n\t\t\t\t\t'Content-Type': 'application/json',\r\n\t\t\t\t\t'X-API-Key': this._apiKey,\r\n\t\t\t\t},\r\n\t\t\t\tbody: payload,\r\n\t\t\t\tkeepalive: true,\r\n\t\t\t});\r\n\r\n\t\t\t// Intentar parsear el mensaje de respuesta del backend\r\n\t\t\tlet message: string | undefined;\r\n\t\t\ttry {\r\n\t\t\t\tconst responseData: TrackingResponse = await response.json();\r\n\t\t\t\tmessage = responseData.message;\r\n\t\t\t} catch {\r\n\t\t\t\t// Si no se puede parsear, usar el status text\r\n\t\t\t\tmessage = response.statusText;\r\n\t\t\t}\r\n\r\n\t\t\treturn {\r\n\t\t\t\tsuccess: response.ok,\r\n\t\t\t\tstatusCode: response.status,\r\n\t\t\t\tmessage,\r\n\t\t\t};\r\n\t\t} catch (error) {\r\n\t\t\treturn {\r\n\t\t\t\tsuccess: false,\r\n\t\t\t\terror: error instanceof Error ? error.message : 'Network error',\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n}\r\n","/**\r\n * Generador de UUID v4\r\n */\r\n\r\n/** Genera un UUID v4 aleatorio */\r\nexport function generateUUID(): string {\r\n\treturn 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {\r\n\t\tconst random = Math.random() * 16 | 0;\r\n\t\tconst value = char === 'x' ? random : (random & 0x3) | 0x8;\r\n\t\treturn value.toString(16);\r\n\t});\r\n}\r\n","/**\r\n * Utilidad para truncar texto\r\n */\r\n\r\n/**\r\n * Trunca un texto a la longitud máxima especificada\r\n * @param text - Texto a truncar\r\n * @param maxLength - Longitud máxima\r\n * @returns Texto truncado con '...' al final si excede la longitud\r\n */\r\nexport function truncate(text: string, maxLength: number): string {\r\n\tif (maxLength < 3) {\r\n\t\treturn '...';\r\n\t}\r\n\r\n\tif (text.length <= maxLength) {\r\n\t\treturn text;\r\n\t}\r\n\r\n\treturn text.substring(0, maxLength - 3) + '...';\r\n}\r\n","/**\r\n * Utilidades para recolectar metadatos del navegador\r\n */\r\n\r\nimport { generateUUID } from './uuid';\r\n\r\n/** Obtiene o crea un sessionId en sessionStorage */\r\nexport function getOrCreateSessionId(): string {\r\n\ttry {\r\n\t\tconst storageKey = 'event_tracker_session_id';\r\n\t\tconst existingSessionId = sessionStorage.getItem(storageKey);\r\n\r\n\t\tif (existingSessionId) {\r\n\t\t\treturn existingSessionId;\r\n\t\t}\r\n\r\n\t\tconst newSessionId = generateUUID();\r\n\t\tsessionStorage.setItem(storageKey, newSessionId);\r\n\t\treturn newSessionId;\r\n\t} catch {\r\n\t\treturn generateUUID();\r\n\t}\r\n}\r\n\r\n/** Obtiene el tamaño de pantalla */\r\nexport function getScreenSize(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '0x0';\r\n\t}\r\n\r\n\treturn `${window.innerWidth}x${window.innerHeight}`;\r\n}\r\n\r\n/** Obtiene el tamaño del viewport */\r\nexport function getViewportSize(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '0x0';\r\n\t}\r\n\r\n\treturn `${window.innerWidth}x${window.innerHeight}`;\r\n}\r\n\r\n/** Detecta el tipo de dispositivo */\r\nexport function getDeviceType(): 'mobile' | 'desktop' | 'tablet' | 'unknown' {\r\n\tif (typeof navigator === 'undefined') {\r\n\t\treturn 'unknown';\r\n\t}\r\n\r\n\tconst ua = navigator.userAgent;\r\n\r\n\tif (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {\r\n\t\treturn 'tablet';\r\n\t}\r\n\r\n\tif (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(ua)) {\r\n\t\treturn 'mobile';\r\n\t}\r\n\r\n\treturn 'desktop';\r\n}\r\n\r\n/** Obtiene el navegador */\r\nexport function getBrowser(): string {\r\n\tif (typeof navigator === 'undefined') {\r\n\t\treturn 'unknown';\r\n\t}\r\n\r\n\tconst ua = navigator.userAgent;\r\n\r\n\tif (ua.includes('Firefox')) {\r\n\t\treturn 'Firefox';\r\n\t}\r\n\r\n\tif (ua.includes('Edg/')) {\r\n\t\treturn 'Edge';\r\n\t}\r\n\r\n\tif (ua.includes('Chrome')) {\r\n\t\treturn 'Chrome';\r\n\t}\r\n\r\n\tif (ua.includes('Safari')) {\r\n\t\treturn 'Safari';\r\n\t}\r\n\r\n\tif (ua.includes('MSIE') || ua.includes('Trident/')) {\r\n\t\treturn 'Internet Explorer';\r\n\t}\r\n\r\n\treturn 'unknown';\r\n}\r\n\r\n/** Obtiene el sistema operativo */\r\nexport function getOS(): string {\r\n\tif (typeof navigator === 'undefined') {\r\n\t\treturn 'unknown';\r\n\t}\r\n\r\n\tconst ua = navigator.userAgent;\r\n\r\n\tif (ua.includes('Windows')) {\r\n\t\treturn 'Windows';\r\n\t}\r\n\r\n\tif (ua.includes('Mac OS')) {\r\n\t\treturn 'macOS';\r\n\t}\r\n\r\n\tif (ua.includes('Linux')) {\r\n\t\treturn 'Linux';\r\n\t}\r\n\r\n\tif (ua.includes('Android')) {\r\n\t\treturn 'Android';\r\n\t}\r\n\r\n\tif (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad')) {\r\n\t\treturn 'iOS';\r\n\t}\r\n\r\n\treturn 'unknown';\r\n}\r\n\r\n/** Obtiene el idioma del navegador */\r\nexport function getLanguage(): string {\r\n\tif (typeof navigator === 'undefined') {\r\n\t\treturn 'unknown';\r\n\t}\r\n\r\n\treturn navigator.language || 'unknown';\r\n}\r\n\r\n/** Obtiene la URL actual */\r\nexport function getCurrentUrl(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '';\r\n\t}\r\n\r\n\treturn window.location.href;\r\n}\r\n\r\n/** Obtiene el referrer */\r\nexport function getReferrer(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '';\r\n\t}\r\n\r\n\treturn document.referrer;\r\n}\r\n\r\n/** Obtiene el timestamp actual en formato ISO */\r\nexport function getTimestamp(): string {\r\n\treturn new Date().toISOString();\r\n}\r\n\r\n/** Obtiene el path actual */\r\nexport function getCurrentPath(): string {\r\n\tif (typeof window === 'undefined') {\r\n\t\treturn '';\r\n\t}\r\n\r\n\treturn window.location.pathname;\r\n}\r\n","/**\r\n * ClickObserver: Captura clics globales en elementos interactivos\r\n */\r\n\r\nimport type { TrackerEvent } from '../types';\r\nimport { Tracker } from '../core/Tracker';\r\nimport { generateUUID } from '../utils/uuid';\r\nimport { truncate } from '../utils/truncate';\r\nimport {\r\n\tgetReferrer,\r\n\tgetScreenSize,\r\n\tgetTimestamp,\r\n\tgetViewportSize,\r\n\tgetDeviceType,\r\n\tgetBrowser,\r\n\tgetOS,\r\n\tgetLanguage,\r\n} from '../utils/metadata';\r\n\r\n/** Tipos de elementos que deben ser rastreados */\r\nconst TRACKABLE_TAGS = ['BUTTON', 'A', 'INPUT', 'I', 'SPAN', 'DIV'];\r\n\r\n/** Tipos de input que deben ser rastreados */\r\nconst TRACKABLE_INPUT_TYPES = ['submit'];\r\n\r\n/** Tipos de input que deben ser excluidos (sensibles) */\r\nconst SENSITIVE_INPUT_TYPES = ['password', 'email', 'tel', 'number', 'search', 'url'];\r\n\r\n/** Selectores para elementos rastreables */\r\nconst TRACKABLE_SELECTORS = [\r\n\t'button',\r\n\t'a',\r\n\t'input[type=\"submit\"]',\r\n\t'[data-track]',\r\n\t'[style*=\"cursor: pointer\"]',\r\n\t'i[class*=\"mdi-\"]',\r\n];\r\n\r\n/** Verifica si un elemento es sensible (no capturar valores) */\r\nfunction _isSensitiveElement(element: Element): boolean {\r\n\tif (element.tagName === 'INPUT') {\r\n\t\tconst inputType = (element as HTMLInputElement).type?.toLowerCase() ?? '';\r\n\t\treturn SENSITIVE_INPUT_TYPES.includes(inputType);\r\n\t}\r\n\treturn false;\r\n}\r\n\r\n/** Verifica si un elemento es rastreable */\r\nfunction _isTrackableElement(element: Element): boolean {\r\n\tconst tagName = element.tagName.toUpperCase();\r\n\r\n\tif (!TRACKABLE_TAGS.includes(tagName)) {\r\n\t\treturn element.hasAttribute('data-track') ||\r\n\t\t\t_hasCursorPointer(element) ||\r\n\t\t\t_isMdiIcon(element);\r\n\t}\r\n\r\n\tif (tagName === 'INPUT') {\r\n\t\tconst inputType = (element as HTMLInputElement).type?.toLowerCase() ?? '';\r\n\t\treturn TRACKABLE_INPUT_TYPES.includes(inputType);\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/** Verifica si el elemento tiene cursor: pointer */\r\nfunction _hasCursorPointer(element: Element): boolean {\r\n\ttry {\r\n\t\tconst style = window.getComputedStyle(element);\r\n\t\treturn style.cursor === 'pointer';\r\n\t} catch {\r\n\t\treturn false;\r\n\t}\r\n}\r\n\r\n/** Verifica si es un icono MDI */\r\nfunction _isMdiIcon(element: Element): boolean {\r\n\treturn element.tagName === 'I' && element.classList.contains('mdi');\r\n}\r\n\r\n/** Genera un selector CSS simple para el elemento */\r\nfunction _generateSelector(element: Element): string {\r\n\tconst parts: string[] = [];\r\n\tlet current: Element | null = element;\r\n\r\n\twhile (current && current !== document.documentElement) {\r\n\t\tlet selector = current.tagName.toLowerCase();\r\n\r\n\t\tif (current.id) {\r\n\t\t\tselector += `#${current.id}`;\r\n\t\t\tparts.unshift(selector);\r\n\t\t\tbreak;\r\n\t\t} else if (current.className && typeof current.className === 'string') {\r\n\t\t\tconst classes = current.className.trim().split(/\\s+/).filter(Boolean);\r\n\t\t\tif (classes.length > 0) {\r\n\t\t\t\tselector += `.${classes[0]}`;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tparts.unshift(selector);\r\n\t\tcurrent = current.parentElement;\r\n\t}\r\n\r\n\treturn parts.slice(0, 4).join(' > ');\r\n}\r\n\r\n/** Obtiene todas las clases de un elemento como string separado por espacios */\r\nfunction _getAllClasses(element: Element): string | undefined {\r\n\tif (!element.className || typeof element.className !== 'string') {\r\n\t\treturn undefined;\r\n\t}\r\n\tconst classes = element.className.trim().split(/\\s+/).filter(Boolean);\r\n\treturn classes.length > 0 ? classes.join(' ') : undefined;\r\n}\r\n\r\nexport class ClickObserver {\r\n\tprivate _tracker: Tracker;\r\n\tprivate _isActive: boolean = false;\r\n\tprivate _boundHandleClick: (event: MouseEvent) => void;\r\n\r\n\tconstructor(tracker: Tracker) {\r\n\t\tthis._tracker = tracker;\r\n\t\tthis._boundHandleClick = this._handleClick.bind(this);\r\n\t}\r\n\r\n\t/** Inicia el observer */\r\n\tstart(): void {\r\n\t\tif (this._isActive) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\twindow.addEventListener('click', this._boundHandleClick, true);\r\n\t\t\tthis._isActive = true;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Detiene el observer */\r\n\tstop(): void {\r\n\t\tif (!this._isActive) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\twindow.removeEventListener('click', this._boundHandleClick, true);\r\n\t\t\tthis._isActive = false;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Manejador de eventos de clic */\r\n\tprivate _handleClick(event: MouseEvent): void {\r\n\t\ttry {\r\n\t\t\tconst target = event.target as Element;\r\n\r\n\t\t\tif (!target) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// PRIORIDAD 1: Si el elemento clickeado es rastreable, usarlo directamente\r\n\t\t\tif (_isTrackableElement(target) && !_isSensitiveElement(target)) {\r\n\t\t\t\tthis._createClickEvent(target);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// PRIORIDAD 2: Buscar el ancestro rastreable más cercano\r\n\t\t\tconst trackableElement = target.closest(TRACKABLE_SELECTORS.join(','));\r\n\r\n\t\t\tif (!trackableElement) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (_isSensitiveElement(trackableElement)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (!_isTrackableElement(trackableElement)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis._createClickEvent(trackableElement);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Crea un evento de clic */\r\n\tprivate _createClickEvent(element: Element): void {\r\n\t\tconst event: TrackerEvent = {\r\n\t\t\teventType: 'user_click',\r\n\t\t\teventId: generateUUID(),\r\n\t\t\tcontext: {\r\n\t\t\t\tsessionId: '',\r\n\t\t\t\tuserId: null,\r\n\t\t\t\tuserName: null,\r\n\t\t\t\turl: window.location.href,\r\n\t\t\t\treferrer: getReferrer(),\r\n\t\t\t\tscreenSize: getScreenSize(),\r\n\t\t\t\tviewportSize: getViewportSize(),\r\n\t\t\t\tdeviceType: getDeviceType(),\r\n\t\t\t\tbrowser: getBrowser(),\r\n\t\t\t\tos: getOS(),\r\n\t\t\t\tlanguage: getLanguage(),\r\n\t\t\t\ttimestamp: getTimestamp(),\r\n\t\t\t},\r\n\t\t\tdata: {\r\n\t\t\t\ttagName: element.tagName,\r\n\t\t\t\tid: element.id || undefined,\r\n\t\t\t\tclassName: _getAllClasses(element),\r\n\t\t\t\ttitle: element.getAttribute('title') || undefined,\r\n\t\t\t\ttext: element.textContent ? truncate(element.textContent, 30) : undefined,\r\n\t\t\t\tselector: _generateSelector(element),\r\n\t\t\t},\r\n\t\t};\r\n\r\n\t\tthis._tracker.trackEvent(event);\r\n\t}\r\n}\r\n","/**\r\n * HistoryObserver: Monitorea cambios de navegación usando intervalo y eventos nativos\r\n * Alternativa 3: Simple y robusta - funciona con F5, SPA navigation y cierre de página\r\n */\r\n\r\nimport type { Tracker } from '../core/Tracker';\r\n\r\nconst POLL_INTERVAL = 1000; // Verificar URL cada 1 segundo\r\nconst MIN_DURATION = 0.5; // Ignorar page_leave con menos de 0.5 segundos\r\nconst STORAGE_KEY_URL = 'event_tracker_last_url';\r\nconst STORAGE_KEY_TIME = 'event_tracker_last_time';\r\n\r\nexport class HistoryObserver {\r\n\tprivate _tracker: Tracker;\r\n\tprivate _isActive: boolean = false;\r\n\tprivate _intervalId: ReturnType<typeof setInterval> | null = null;\r\n\tprivate _lastUrl: string = '';\r\n\tprivate _pageStartTime: number;\r\n\tprivate _hasFocus: boolean = true;\r\n\tprivate _focusStartTime: number;\r\n\tprivate _accumulatedFocusTime: number;\r\n\r\n\tconstructor(tracker: Tracker) {\r\n\t\tthis._tracker = tracker;\r\n\t\tthis._pageStartTime = performance.now();\r\n\t\tthis._focusStartTime = performance.now();\r\n\t\tthis._accumulatedFocusTime = 0;\r\n\t\tthis._lastUrl = typeof window !== 'undefined' ? window.location.pathname : '';\r\n\r\n\t\t// Verificar si hay una URL previa guardada (para F5)\r\n\t\tthis._checkPreviousPage();\r\n\t}\r\n\r\n\t/** Inicia el observer */\r\n\tstart(): void {\r\n\t\tif (this._isActive) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\t// Verificar foco inicial\r\n\t\t\tthis._hasFocus = typeof document !== 'undefined' ? document.hasFocus() : true;\r\n\t\t\tthis._focusStartTime = performance.now();\r\n\r\n\t\t\t// Iniciar intervalo para detectar cambios de URL\r\n\t\t\tthis._intervalId = setInterval(() => this._checkUrlChange(), POLL_INTERVAL);\r\n\r\n\t\t\t// Eventos para foco (funciona en desktop y móvil)\r\n\t\t\tif (typeof window !== 'undefined') {\r\n\t\t\t\twindow.addEventListener('focus', this._handleFocus);\r\n\t\t\t\twindow.addEventListener('blur', this._handleBlur);\r\n\t\t\t\twindow.addEventListener('beforeunload', this._handleBeforeUnload);\r\n\t\t\t\t// Para móvil: visibilitychange\r\n\t\t\t\tdocument.addEventListener('visibilitychange', this._handleVisibilityChange);\r\n\t\t\t}\r\n\r\n\t\t\tthis._isActive = true;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Detiene el observer */\r\n\tstop(): void {\r\n\t\tif (!this._isActive) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tif (this._intervalId) {\r\n\t\t\t\tclearInterval(this._intervalId);\r\n\t\t\t\tthis._intervalId = null;\r\n\t\t\t}\r\n\r\n\t\t\tif (typeof window !== 'undefined') {\r\n\t\t\t\twindow.removeEventListener('focus', this._handleFocus);\r\n\t\t\t\twindow.removeEventListener('blur', this._handleBlur);\r\n\t\t\t\twindow.removeEventListener('beforeunload', this._handleBeforeUnload);\r\n\t\t\t\tdocument.removeEventListener('visibilitychange', this._handleVisibilityChange);\r\n\t\t\t}\r\n\r\n\t\t\tthis._isActive = false;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Manejador de focus (ventana ganha foco) */\r\n\tprivate _handleFocus = (): void => {\r\n\t\ttry {\r\n\t\t\tif (!this._hasFocus) {\r\n\t\t\t\t// Reactivar foco después de haberlo perdido\r\n\t\t\t\tthis._hasFocus = true;\r\n\t\t\t\tthis._focusStartTime = performance.now();\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t};\r\n\r\n\t/** Manejador de blur (ventana pierde foco) */\r\n\tprivate _handleBlur = (): void => {\r\n\t\ttry {\r\n\t\t\tif (this._hasFocus) {\r\n\t\t\t\t// Acumular el tiempo con foco hasta ahora\r\n\t\t\t\tthis._accumulatedFocusTime += performance.now() - this._focusStartTime;\r\n\t\t\t\tthis._hasFocus = false;\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t};\r\n\r\n\t/** Manejador de visibilitychange (para móvil - app en background) */\r\n\tprivate _handleVisibilityChange = (): void => {\r\n\t\ttry {\r\n\t\t\tif (typeof document === 'undefined') {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (document.visibilityState === 'visible') {\r\n\t\t\t\tthis._handleFocus();\r\n\t\t\t} else if (document.visibilityState === 'hidden') {\r\n\t\t\t\tthis._handleBlur();\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t};\r\n\r\n\t/** Manejador de beforeunload (cuando el usuario sale de la página) */\r\n\tprivate _handleBeforeUnload = (): void => {\r\n\t\ttry {\r\n\t\t\t// Calcular duración con foco\r\n\t\t\tconst duration = this._calculateFocusedDuration();\r\n\r\n\t\t\t// Solo enviar si la duración es significativa\r\n\t\t\tif (duration >= MIN_DURATION) {\r\n\t\t\t\tthis._tracker.trackPageLeave(duration, this._lastUrl);\r\n\t\t\t}\r\n\r\n\t\t\t// Guardar URL actual en sessionStorage para F5\r\n\t\t\tthis._saveToStorage(this._lastUrl, duration);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t};\r\n\r\n\t/** Calcula la duración total con foco */\r\n\tprivate _calculateFocusedDuration(): number {\r\n\t\tlet totalFocusTime = this._accumulatedFocusTime;\r\n\r\n\t\t// Si tiene foco ahora, sumar el tiempo desde el último foco\r\n\t\tif (this._hasFocus) {\r\n\t\t\ttotalFocusTime += performance.now() - this._focusStartTime;\r\n\t\t}\r\n\r\n\t\treturn totalFocusTime / 1000;\r\n\t}\r\n\r\n\t/** Verifica si la URL cambió */\r\n\tprivate _checkUrlChange(): void {\r\n\t\ttry {\r\n\t\t\tif (typeof window === 'undefined') {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tconst currentUrl = window.location.pathname;\r\n\r\n\t\t\t// Si la URL cambió\r\n\t\t\tif (currentUrl !== this._lastUrl) {\r\n\t\t\t\t// Calcular duración de la página anterior\r\n\t\t\t\tconst duration = this._calculateFocusedDuration();\r\n\r\n\t\t\t\t// Solo enviar page_leave si la duración es significativa\r\n\t\t\t\tif (duration >= MIN_DURATION) {\r\n\t\t\t\t\t// Track page leave de la página anterior\r\n\t\t\t\t\tthis._tracker.trackPageLeave(duration, this._lastUrl);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Track page view de la nueva página\r\n\t\t\t\tthis._tracker.trackPageView();\r\n\r\n\t\t\t\t// Resetear contadores para la nueva página\r\n\t\t\t\tthis._pageStartTime = performance.now();\r\n\t\t\t\tthis._resetFocusTracking();\r\n\r\n\t\t\t\t// Actualizar la última URL\r\n\t\t\t\tthis._lastUrl = currentUrl;\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Resetea el tracking de foco para una nueva página */\r\n\tprivate _resetFocusTracking(): void {\r\n\t\tthis._hasFocus = typeof document !== 'undefined' ? document.hasFocus() : true;\r\n\t\tthis._focusStartTime = performance.now();\r\n\t\tthis._accumulatedFocusTime = 0;\r\n\t}\r\n\r\n\t/** Guarda la URL y tiempo en sessionStorage (para F5) */\r\n\tprivate _saveToStorage(url: string, duration: number): void {\r\n\t\ttry {\r\n\t\t\tif (typeof sessionStorage !== 'undefined') {\r\n\t\t\t\tsessionStorage.setItem(STORAGE_KEY_URL, url);\r\n\t\t\t\tsessionStorage.setItem(STORAGE_KEY_TIME, String(duration));\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Ignore storage errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Lee la URL y tiempo previos de sessionStorage (para F5) */\r\n\tprivate _getFromStorage(): { url: string; duration: number; } | null {\r\n\t\ttry {\r\n\t\t\tif (typeof sessionStorage !== 'undefined') {\r\n\t\t\t\tconst url = sessionStorage.getItem(STORAGE_KEY_URL);\r\n\t\t\t\tconst duration = sessionStorage.getItem(STORAGE_KEY_TIME);\r\n\t\t\t\tif (url && duration) {\r\n\t\t\t\t\treturn { url, duration: parseFloat(duration) };\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Ignore storage errors\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t/** Limpia sessionStorage */\r\n\tprivate _clearStorage(): void {\r\n\t\ttry {\r\n\t\t\tif (typeof sessionStorage !== 'undefined') {\r\n\t\t\t\tsessionStorage.removeItem(STORAGE_KEY_URL);\r\n\t\t\t\tsessionStorage.removeItem(STORAGE_KEY_TIME);\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Ignore storage errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Verifica si hay una página previa (para F5) */\r\n\tprivate _checkPreviousPage(): void {\r\n\t\tconst previous = this._getFromStorage();\r\n\t\tif (previous && previous.duration >= MIN_DURATION) {\r\n\t\t\t// La página anterior tenía una duración significativa, enviar page_leave\r\n\t\t\tthis._tracker.trackPageLeave(previous.duration, previous.url);\r\n\t\t}\r\n\t\t// Limpiar storage después de procesar\r\n\t\tthis._clearStorage();\r\n\t}\r\n}\r\n","/**\r\n * Tracker: Singleton principal que coordina observers y EventBuffer\r\n */\r\n\r\nimport type { TrackerConfig, TrackerEvent, UserIdentity, EventContext } from '../types';\r\nimport { EventBuffer } from './EventBuffer';\r\nimport { ClickObserver } from '../observers/ClickObserver';\r\nimport { HistoryObserver } from '../observers/HistoryObserver';\r\nimport {\r\n\tgetCurrentPath,\r\n\tgetReferrer,\r\n\tgetScreenSize,\r\n\tgetTimestamp,\r\n\tgetOrCreateSessionId,\r\n\tgetViewportSize,\r\n\tgetDeviceType,\r\n\tgetBrowser,\r\n\tgetOS,\r\n\tgetLanguage,\r\n} from '../utils/metadata';\r\nimport { generateUUID } from '../utils/uuid';\r\n\r\nexport class Tracker {\r\n\tprivate static _instance: Tracker | null = null;\r\n\tprivate _eventBuffer: EventBuffer | null = null;\r\n\tprivate _clickObserver: ClickObserver | null = null;\r\n\tprivate _historyObserver: HistoryObserver | null = null;\r\n\tprivate _userIdentity: UserIdentity;\r\n\tprivate _sessionId: string;\r\n\tprivate _isInitialized: boolean = false;\r\n\r\n\tprivate constructor() {\r\n\t\tthis._userIdentity = {\r\n\t\t\tuserId: null,\r\n\t\t\tuserName: null,\r\n\t\t};\r\n\t\tthis._sessionId = getOrCreateSessionId();\r\n\t}\r\n\r\n\t/** Obtiene la instancia única del Tracker */\r\n\tstatic getInstance(): Tracker {\r\n\t\tif (Tracker._instance === null) {\r\n\t\t\tTracker._instance = new Tracker();\r\n\t\t}\r\n\t\treturn Tracker._instance;\r\n\t}\r\n\r\n\t/** Construye el contexto completo del evento */\r\n\tprivate _buildContext(): EventContext {\r\n\t\treturn {\r\n\t\t\tsessionId: this._sessionId,\r\n\t\t\tuserId: this._userIdentity.userId,\r\n\t\t\tuserName: this._userIdentity.userName,\r\n\t\t\turl: window.location.href,\r\n\t\t\treferrer: getReferrer(),\r\n\t\t\tscreenSize: getScreenSize(),\r\n\t\t\tviewportSize: getViewportSize(),\r\n\t\t\tdeviceType: getDeviceType(),\r\n\t\t\tbrowser: getBrowser(),\r\n\t\t\tos: getOS(),\r\n\t\t\tlanguage: getLanguage(),\r\n\t\t\ttimestamp: getTimestamp(),\r\n\t\t};\r\n\t}\r\n\r\n\t/** Inicializa el tracker con la configuración */\r\n\tinit(config: TrackerConfig): void {\r\n\t\ttry {\r\n\t\t\t// Actualizamos el usuario cada vez que se llama init (permite actualizar userId/userName)\r\n\t\t\tif (config.userId) {\r\n\t\t\t\tthis._userIdentity = {\r\n\t\t\t\t\tuserId: config.userId,\r\n\t\t\t\t\tuserName: config.userName ?? null,\r\n\t\t\t\t};\r\n\t\t\t\tif (this._isInitialized && config.debug) {\r\n\t\t\t\t\tconsole.log('[EventTracker] Usuario actualizado:', this._userIdentity);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Solo inicializar EventBuffer y observers la primera vez\r\n\t\t\tif (this._isInitialized) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis._eventBuffer = new EventBuffer(config);\r\n\t\t\tthis._isInitialized = true;\r\n\r\n\t\t\t// Iniciar observers\r\n\t\t\tthis._clickObserver = new ClickObserver(this);\r\n\t\t\tthis._historyObserver = new HistoryObserver(this);\r\n\t\t\tthis._clickObserver.start();\r\n\t\t\tthis._historyObserver.start();\r\n\r\n\t\t\t// Enviar page_view inicial\r\n\t\t\tthis.trackPageView();\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Identifica al usuario */\r\n\tidentify(userId: string, userName?: string): void {\r\n\t\ttry {\r\n\t\t\tthis._userIdentity = {\r\n\t\t\t\tuserId,\r\n\t\t\t\tuserName: userName ?? null,\r\n\t\t\t};\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Registra un evento */\r\n\ttrackEvent(event: TrackerEvent): void {\r\n\t\ttry {\r\n\t\t\tif (!this._eventBuffer) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tconst eventWithContext: TrackerEvent = {\r\n\t\t\t\t...event,\r\n\t\t\t\teventId: generateUUID(),\r\n\t\t\t\tcontext: this._buildContext(),\r\n\t\t\t};\r\n\r\n\t\t\tthis._eventBuffer.add(eventWithContext);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Crea un evento de page view */\r\n\ttrackPageView(): void {\r\n\t\ttry {\r\n\t\t\tconst event: TrackerEvent = {\r\n\t\t\t\teventType: 'page_view',\r\n\t\t\t\teventId: generateUUID(),\r\n\t\t\t\tcontext: this._buildContext(),\r\n\t\t\t\tdata: {\r\n\t\t\t\t\tpath: getCurrentPath(),\r\n\t\t\t\t},\r\n\t\t\t};\r\n\r\n\t\t\tthis.trackEvent(event);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Crea un evento de page leave */\r\n\ttrackPageLeave(duration: number, path?: string): void {\r\n\t\ttry {\r\n\t\t\tconst event: TrackerEvent = {\r\n\t\t\t\teventType: 'page_leave',\r\n\t\t\t\teventId: generateUUID(),\r\n\t\t\t\tcontext: this._buildContext(),\r\n\t\t\t\tdata: {\r\n\t\t\t\t\tduration,\r\n\t\t\t\t\tpath: path ?? getCurrentPath(),\r\n\t\t\t\t},\r\n\t\t\t};\r\n\r\n\t\t\tthis.trackEvent(event);\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Fuerza el envío de todos los eventos pendientes */\r\n\tasync flush(): Promise<void> {\r\n\t\ttry {\r\n\t\t\tif (this._eventBuffer) {\r\n\t\t\t\tawait this._eventBuffer.flush();\r\n\t\t\t}\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n\r\n\t/** Verifica si el tracker está inicializado */\r\n\tisInitialized(): boolean {\r\n\t\treturn this._isInitialized;\r\n\t}\r\n\r\n\t/** Destruye el tracker */\r\n\tdestroy(): void {\r\n\t\ttry {\r\n\t\t\tif (this._clickObserver) {\r\n\t\t\t\tthis._clickObserver.stop();\r\n\t\t\t\tthis._clickObserver = null;\r\n\t\t\t}\r\n\t\t\tif (this._historyObserver) {\r\n\t\t\t\tthis._historyObserver.stop();\r\n\t\t\t\tthis._historyObserver = null;\r\n\t\t\t}\r\n\t\t\tif (this._eventBuffer) {\r\n\t\t\t\tthis._eventBuffer.destroy();\r\n\t\t\t\tthis._eventBuffer = null;\r\n\t\t\t}\r\n\t\t\tthis._isInitialized = false;\r\n\t\t} catch {\r\n\t\t\t// Never throw errors\r\n\t\t}\r\n\t}\r\n}\r\n"],"mappings":"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","names":["tracker","tracker","Tracker","Tracker"]}
|