@magicfeedback/popup-sdk 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react-native/index.tsx","../src/types/index.ts","../src/triggers/index.ts","../src/config/env.ts","../src/ui/surveyHtml.ts","../src/platform/react-native-renderer.ts","../src/platform/renderer.ts","../src/analytics/analytics-manager.ts","../src/analytics/feedback-payload.ts","../src/tracking/navigation-observer.ts","../src/analytics/device-info.ts","../src/analytics/geo-info.ts","../src/analytics/language.ts","../src/analytics/engagement-tracker.ts","../src/contact/contact-manager.ts","../src/analytics/crash-reporter.ts","../src/analytics/messaging.ts","../src/core/deepdots-popups.ts","../src/react-native/setup.ts"],"sourcesContent":["/**\n * Entry de React Native: `@magicfeedback/popup-sdk/react-native`.\n *\n * El cliente solo envuelve su app:\n * <DeepdotsProvider config={{ apiKey, nodeEnv }}>...</DeepdotsProvider>\n * y el SDK hace TODO (storage persistente, device info, platform, lifecycle/engagement,\n * y render de surveys en WebView). Sin pegar nada en su proyecto.\n *\n * Peer deps: react, react-native, react-native-webview. Opcionales (auto-detectados):\n * react-native-mmkv (persistencia), react-native-device-info (device info).\n */\nimport React, { createContext, useContext, useEffect, useRef, useState } from 'react';\nimport { AppState, Modal, Platform, View } from 'react-native';\nimport { WebView } from 'react-native-webview';\n\nimport { DeepdotsPopups } from '../core/deepdots-popups';\nimport { ReactNativePopupRenderer, type ReactNativeSurveyPayload } from '../platform/react-native-renderer';\nimport { setupReactNative } from './setup';\nimport type { DeepdotsInitParams } from '../types';\n\nconst sdk = new DeepdotsPopups();\nconst DeepdotsContext = createContext<DeepdotsPopups>(sdk);\n\n/** Acceso al SDK desde cualquier componente: `const dd = useDeepdots()`. */\nexport const useDeepdots = (): DeepdotsPopups => useContext(DeepdotsContext);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction optionalRequire(name: string): any {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require(name);\n } catch {\n return null;\n }\n}\n\nexport interface DeepdotsProviderProps {\n config: DeepdotsInitParams;\n children?: React.ReactNode;\n}\n\nexport function DeepdotsProvider({ config, children }: DeepdotsProviderProps) {\n const [survey, setSurvey] = useState<ReactNativeSurveyPayload | null>(null);\n const rendererRef = useRef<ReactNativePopupRenderer | null>(null);\n\n useEffect(() => {\n const renderer = new ReactNativePopupRenderer({\n onShow: (p) => setSurvey(p),\n onHide: () => setSurvey(null),\n });\n rendererRef.current = renderer;\n\n const MmkvMod = optionalRequire('react-native-mmkv');\n const mmkv = MmkvMod?.MMKV ? new MmkvMod.MMKV({ id: 'deepdots-sdk' }) : null;\n const deviceInfo = optionalRequire('react-native-device-info')?.default ?? null;\n\n const dispose = setupReactNative(sdk, config, {\n mmkv,\n deviceInfo,\n appState: AppState,\n platform: Platform.OS === 'ios' ? 'ios' : 'android',\n renderer,\n });\n return dispose;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <DeepdotsContext.Provider value={sdk}>\n {children}\n {survey ? (\n <Modal visible transparent animationType=\"slide\">\n <View style={{ flex: 1 }}>\n <WebView\n originWhitelist={['*']}\n javaScriptEnabled\n source={{ html: survey.html }}\n onMessage={(e: { nativeEvent: { data: string } }) =>\n rendererRef.current?.handleMessage(e.nativeEvent.data)\n }\n />\n </View>\n </Modal>\n ) : null}\n </DeepdotsContext.Provider>\n );\n}\n\nexport { setupReactNative } from './setup';\nexport { ReactNativePopupRenderer } from '../platform/react-native-renderer';\n","/**\n * Configuration options for initializing the DeepdotsPopups SDK\n */\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\nimport type { DeviceInfo } from '../analytics/device-info';\nimport type { AnalyticsKeys } from '../analytics/feedback-payload';\n\n/**\n * Custom logger to receive the SDK's debug output instead of the console.\n * Only `log` is required; `warn`/`error`/`info` fall back to `log` when omitted.\n * Lets the host pipe debug output to a file, Firebase, etc. `console` satisfies this shape.\n */\nexport interface DeepdotsLogger {\n log: (...args: unknown[]) => void;\n warn?: (...args: unknown[]) => void;\n error?: (...args: unknown[]) => void;\n info?: (...args: unknown[]) => void;\n}\n\nexport interface DeepdotsInitParams {\n /** API key for authentication */\n apiKey?: string;\n /** Node environment: 'development' or 'production' */\n nodeEnv?: 'development' | 'production';\n /** Enable debug logging */\n debug?: boolean;\n /** Custom logger for debug output. When set (and `debug` is true), the SDK routes its logs here instead of `console`. Default: `console`. */\n logger?: DeepdotsLogger;\n /** Optional user id to send with popup events */\n userId?: string;\n /** Starts tracking enabled (default) or disabled (e.g. until consent is granted). Equivalent to calling `setTrackingEnabled` after init. */\n trackingEnabled?: boolean;\n /** Host app version (for analytics device info / Technology #11). */\n appVersion?: string;\n /** Injectable persistent storage (RN: adapter over AsyncStorage). If missing, uses localStorage/in-memory. */\n storage?: KeyValueStorage;\n /** Platform used in the analytics envelope. Default is 'web'; in RN pass 'android'/'ios'. */\n platform?: 'web' | 'android' | 'ios';\n /**\n * BCP-47 language tag (e.g. 'es-ES') used for the analytics context AND for popup\n * language segmentation (`segments.lang`). If omitted, the SDK auto-detects:\n * `navigator.language` on web, or the `Intl` locale as a fallback (works in\n * React Native with Hermes, where `navigator.language` is absent). If no source\n * resolves a language, popups with `segments.lang` cannot be filtered and are shown.\n */\n language?: string;\n /** Injectable device info (RN: from react-native-device-info). If missing, it is derived from the browser. */\n device?: DeviceInfo;\n /**\n * Analytics channel keys (`POST /sdk/feedback`). If provided, analytics is SENT\n * to the configured integration; if missing, it stays in dry-run mode (console.log only).\n */\n analytics?: AnalyticsKeys;\n /**\n * Internal user attributes known only by the host (language, age, plan, ...), sent\n * to the backend Contact for segmentation/targeting. Requires `userId`. Equivalent to calling\n * `setContactAttributes` after init (sends only when values changed from the last sync).\n */\n contactAttributes?: Record<string, string | number | boolean>;\n\n}\nexport interface DeepdotsConfig {\n /** API key for authentication */\n apiKey?: string;\n /** Enable debug logging */\n debug?: boolean;\n /** Optional user id to send with popup events */\n userId?: string;\n}\n\n/**\n * Options for configuring survey triggers\n */\nexport interface TriggerConfig {\n /** Trigger type: 'time' (delay), 'scroll' (scroll percentage), 'exit' (route exit), 'click' (element id), 'event' (host event) */\n type: 'time' | 'scroll' | 'exit' | 'click' | 'event';\n /** Value for the trigger (milliseconds for time, percentage for scroll, seconds for exit, element id for click, event name for event) */\n value?: number | string;\n /** Survey ID to show when triggered */\n surveyId: string;\n /** Optional popup definition id (used to disambiguate repeated survey ids) */\n popupId?: string;\n}\n\n/**\n * Event types emitted by the SDK\n */\nexport type DeepdotsEventType =\n | 'popup_shown'\n | 'popup_clicked'\n | 'survey_completed';\n\n/**\n * Event payload structure\n */\nexport interface DeepdotsEvent {\n type: DeepdotsEventType;\n surveyId: string;\n timestamp: number;\n data?: Record<string, unknown>;\n}\n\n/**\n * Event listener callback\n */\nexport type EventListener = (event: DeepdotsEvent) => void;\n\n/**\n * Motivo del `deepdots_session_end` (último lote de la sesión, enviado con `completed:true`):\n * - `page_hide`: la página web se cierra o se navega fuera (`pagehide`).\n * - `background`: la app pasa a background (RN/nativo). Única señal disponible en móvil:\n * el kill de la app por el usuario o el SO NO produce ningún callback.\n * - `user_change`: `setUserId()` o un `init()` con otro `userId` (login/logout del host).\n * - `tracking_disabled`: `setTrackingEnabled(false)` (consentimiento revocado).\n * - `manual`: `endSession()` llamado por el host.\n */\nexport type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual';\n\n/** Trigger type used by remote popup definitions */\nexport type PopupTriggerType = 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';\n\nexport type PopupTriggerConditionStatus = 'SHOWED' | 'PARTIAL' | 'COMPLETED';\n\nexport const POPUP_TRIGGER_CONDITION_STATUSES: PopupTriggerConditionStatus[] = [\n 'SHOWED',\n 'PARTIAL',\n 'COMPLETED',\n];\n\n/** Extra condition used to activate the popup */\nexport interface PopupTriggerCondition {\n answered: PopupTriggerConditionStatus; // user progress status in the survey\n cooldownDays: number; // cooldown days before showing again\n}\n\n/** Trigger attached to the popup definition */\nexport interface PopupTrigger {\n type: PopupTriggerType;\n value: number | string; // seconds on page, scroll percentage, click id, or event name\n}\n\n/** Accept action (open survey) */\nexport interface PopupActionAccept {\n label: string;\n surveyId: string; // survey id to launch\n}\n\n/** Decline action */\nexport interface PopupActionDecline {\n label: string;\n cooldownDays?: number; // optional: do not show again until X days pass\n}\n\n/** Start Surveys Action */\nexport interface PopupActionStart {\n label: string;\n}\n\n/** Complete action (accept and auto-complete survey) */\n// Requires the survey to support auto-completion via parameters.\nexport interface PopupActionComplete {\n label: string;\n surveyId: string; // survey id to launch\n autoCompleteParams: Record<string, unknown>; // parameters used for auto-completion\n cooldownDays?: number;\n}\n\n/** Set of actions available in the popup */\nexport interface PopupActions {\n accept?: PopupActionAccept;\n decline?: PopupActionDecline;\n complete?: PopupActionComplete;\n start?: PopupActionStart;\n back?: PopupActionDecline;\n}\n\n/** Fuente personalizable del popup + survey (viene de la API por popup) */\nexport interface PopupFont {\n /** Nombre de la familia. Ej: \"Inter\" (nombre limpio, NO un stack). */\n family: string;\n /** Opcional: URL a un archivo de fuente (woff2/ttf/otf) para armar un @font-face. */\n url?: string;\n}\n\n/** Configurable popup styles */\nexport interface PopupStyle {\n theme: 'light' | 'dark';\n position: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center';\n /** @deprecated Not used by the SDK; kept only for API compatibility */\n imageUrl?: string | null;\n /** Fuente personalizada. Si falta, se mantiene el comportamiento actual. */\n font?: PopupFont;\n}\n\n/** Segments/targeting rules for showing the popup */\nexport interface PopupSegments {\n lang?: string[]; // allowed languages\n path?: string[]; // app routes where it can be shown\n [key: string]: unknown; // possible future extension\n}\n\n/** Full popup definition */\nexport interface PopupDefinition {\n id: string;\n title: string;\n message: string; // rendered safe HTML (recommended to sanitize upstream)\n triggers: PopupTrigger[];\n cooldown?: PopupTriggerCondition[];\n actions?: PopupActions;\n surveyId: string;\n productId: string;\n style?: PopupStyle;\n segments?: PopupSegments;\n}\n\nexport interface FormStyle\n{\n id: string;\n logo: string;\n title: string;\n companyName: string;\n backgroundColor: string;\n boxBackgroundColor: string;\n buttonOnTopColor: string;\n buttonPrimaryColor: string\n buttonSecondaryColor: string;\n loadingBarColor: string;\n favIcon: string;\n startMessage: string;\n successMessage: string;\n addLogoInMsg: boolean;\n privacyPolicyUrl: string;\n helpUrl: string;\n integrationId: string;\n redirectLink: string;\n redirectError: string;\n redirectTime: number;\n integrationThemeId: string;\n contentAlign: 'top' | 'center';\n logoSize: 'small' | 'medium' | 'large';\n logoPosition: 'left' | 'right' | 'center';\n showProgressBar: boolean;\n showProgressUnit: boolean;\n progressUnit: 'percentage' | 'fraction';\n}\n\n\nexport interface FormData {\n id: string;\n name: string;\n description: string;\n type: string;\n identity: string;\n status: string;\n createdAt: Date;\n updatedAt: Date;\n externalId?: string | null;\n companyId: string;\n productId: string;\n userId: string;\n style: FormStyle\n}\n\nexport enum POPUPSESSIONSTATUS {\n NONE = \"NONE\",\n SHOWED = \"SHOWED\",\n PARTIAL = \"PARTIAL\",\n COMPLETED = \"COMPLETED\",\n DEPRECATED = \"DEPRECATED\",\n}\n","import type { TriggerConfig } from '../types';\nimport type { DeepdotsPopups } from '../core/deepdots-popups';\n\nexport function setupTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n switch (trigger.type) {\n case 'time':\n setupTimeTrigger(popups, trigger);\n break;\n case 'scroll':\n setupScrollTrigger(popups, trigger);\n break;\n case 'exit':\n setupExitTrigger(popups, trigger);\n break;\n case 'event':\n setupEventTrigger(popups, trigger);\n break;\n case 'click':\n setupClickTrigger(popups, trigger);\n break;\n }\n}\n\nfunction setupTimeTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n const delay = trigger.value || 5000;\n setTimeout(() => {\n popups.triggerSurvey(trigger.surveyId, trigger.popupId);\n }, delay);\n popups.debug(`Time trigger set for ${delay}ms`);\n}\n\nfunction setupScrollTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n const threshold = trigger.value || 50;\n const handler = () => {\n const scrollPercentage = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;\n if (scrollPercentage >= threshold) {\n popups.triggerSurvey(trigger.surveyId, trigger.popupId);\n window.removeEventListener('scroll', handler);\n }\n };\n window.addEventListener('scroll', handler);\n popups.debug(`Scroll trigger set for ${threshold}%`);\n}\n\nfunction setupExitTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n if (typeof window === 'undefined') return;\n\n const delaySeconds = parseExitDelaySeconds(trigger.value);\n const historyRef = window.history;\n const originalPushState = historyRef.pushState.bind(historyRef);\n const originalReplaceState = historyRef.replaceState.bind(historyRef);\n\n let lastUrl = normalizeUrl(window.location.href);\n let lastQueuedKey = '';\n let lastQueuedAt = 0;\n\n const queueLeave = (fromRaw: string, toRaw: string) => {\n const from = normalizeUrl(fromRaw);\n const to = normalizeUrl(toRaw);\n if (!from || !to || from === to) return;\n\n const key = `${from}=>${to}`;\n const now = Date.now();\n if (key === lastQueuedKey && now - lastQueuedAt < 150) return;\n lastQueuedKey = key;\n lastQueuedAt = now;\n popups.queueExitPopup(trigger.surveyId, delaySeconds, from, trigger.popupId);\n };\n\n const handleNavigation = (to: string) => {\n const from = lastUrl;\n queueLeave(from, to);\n lastUrl = normalizeUrl(to);\n };\n\n const handleDocumentClick = (event: MouseEvent) => {\n if (event.defaultPrevented) return;\n if (event.button !== 0) return;\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n\n const anchor = (event.target as Element | null)?.closest?.('a');\n if (!(anchor instanceof HTMLAnchorElement)) return;\n\n const href = anchor.getAttribute('href');\n if (!href || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) {\n return;\n }\n const target = anchor.getAttribute('target');\n if (target && target !== '_self') return;\n\n let destination: URL;\n try {\n destination = new URL(href, window.location.href);\n } catch {\n return;\n }\n\n if (destination.origin !== window.location.origin) return;\n queueLeave(window.location.href, destination.href);\n };\n\n const handleHashChange = (event: HashChangeEvent) => {\n const to = event.newURL || window.location.href;\n const from = event.oldURL || lastUrl;\n queueLeave(from, to);\n lastUrl = normalizeUrl(to);\n };\n\n const handlePopState = () => {\n handleNavigation(window.location.href);\n };\n\n historyRef.pushState = function (data: any, unused: string, url?: string | URL | null) {\n const from = window.location.href;\n const result = originalPushState(data, unused, url as any);\n queueLeave(from, window.location.href);\n lastUrl = normalizeUrl(window.location.href);\n return result;\n };\n\n historyRef.replaceState = function (data: any, unused: string, url?: string | URL | null) {\n const from = window.location.href;\n const result = originalReplaceState(data, unused, url as any);\n queueLeave(from, window.location.href);\n lastUrl = normalizeUrl(window.location.href);\n return result;\n };\n\n document.addEventListener('click', handleDocumentClick, true);\n window.addEventListener('hashchange', handleHashChange);\n window.addEventListener('popstate', handlePopState);\n popups.debug('Exit trigger set (deferred to next route)', {\n surveyId: trigger.surveyId,\n delaySeconds,\n });\n}\n\nfunction setupEventTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n // Event triggers are manually fired by host application using popups.triggerEvent(name).\n popups.debug('Event trigger registered', {\n popupId: trigger.popupId,\n surveyId: trigger.surveyId,\n eventName: String(trigger.value || ''),\n });\n}\n\nfunction parseExitDelaySeconds(value?: number | string): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return Math.max(0, value);\n }\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) return Math.max(0, parsed);\n }\n return 0;\n}\n\nfunction normalizeUrl(value: string): string {\n if (!value) return '';\n const withoutIndex = value.replace(/\\/index\\.html(?=($|[?#]))/i, '');\n return withoutIndex.length > 1 && withoutIndex.endsWith('/') ? withoutIndex.slice(0, -1) : withoutIndex;\n}\n\nfunction setupClickTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n const targetId = typeof trigger.value === 'string' ? trigger.value : '';\n if (!targetId) {\n popups.debug('Click trigger missing element id', trigger);\n return;\n }\n\n const attach = () => {\n const el = document.getElementById(targetId);\n if (!el) {\n popups.debug('Click trigger target not found', targetId);\n return false;\n }\n const handler = (e: MouseEvent) => {\n if (e.defaultPrevented) return;\n popups.triggerSurvey(trigger.surveyId, trigger.popupId);\n el.removeEventListener('click', handler);\n };\n el.addEventListener('click', handler);\n popups.debug(`Click trigger set for #${targetId}`);\n return true;\n };\n\n if (typeof document === 'undefined') return;\n if (attach()) return;\n window.addEventListener('DOMContentLoaded', () => attach(), { once: true });\n}\n","import {DeepdotsConfig, DeepdotsInitParams} from '../types';\n\n/** Variables específicas por entorno derivadas de nodeEnv */\nexport interface EnvironmentVariables {\n apiBaseUrl: string;\n}\n\nconst ENVIRONMENT_MAP: Record<\"development\" | \"production\", EnvironmentVariables> = {\n development: {\n apiBaseUrl: 'https://api-dev.deepdots.com',\n },\n production: {\n apiBaseUrl: 'https://api.deepdots.com',\n },\n};\n\n/** Resuelve las variables para el entorno indicado */\nexport function resolveEnvironment(nodeEnv: DeepdotsInitParams['nodeEnv']): EnvironmentVariables {\n return ENVIRONMENT_MAP[nodeEnv || 'production'];\n}\n\n","import type { IdentityAnswer } from '../tracking/tracking-manager';\nimport { buildFontFaceCss, buildFontFamilyValue } from './font';\nimport type { PopupFont } from '../types';\n\n/**\n * Construye un HTML autocontenido que renderiza el survey de `@magicfeedback/native`\n * dentro de un WebView (React Native). Carga el bundle desde CDN, crea el form con la\n * identidad inyectada (`profile`/`metadata`) y reenvía los eventos del survey al host\n * vía el puente de react-native-webview: `window.ReactNativeWebView.postMessage`.\n *\n * Mensajes emitidos (JSON `{name, payload}`): `loaded`, `before_submit`, `after_submit`,\n * `survey_completed`, `back`, `popup_close`.\n */\nexport interface BuildSurveyHtmlOptions {\n surveyId: string;\n productId: string;\n env?: string; // 'production' | 'development'\n profile?: IdentityAnswer[];\n metadata?: IdentityAnswer[];\n version?: string; // versión de @magicfeedback/native\n font?: PopupFont; // fuente personalizada (family + url opcional)\n}\n\nexport function buildSurveyHtml(opts: BuildSurveyHtmlOptions): string {\n const env = opts.env === 'development' ? 'dev' : 'prod';\n const version = opts.version ?? '2.2.4';\n const cdn = `https://cdn.jsdelivr.net/npm/@magicfeedback/native@${version}/dist/magicfeedback-sdk.browser.js`;\n const sid = JSON.stringify(opts.surveyId);\n const pid = JSON.stringify(opts.productId);\n const profileJson = JSON.stringify(opts.profile ?? []);\n const metaJson = JSON.stringify(opts.metadata ?? []);\n const fontFaceCss = opts.font ? buildFontFaceCss(opts.font.family, opts.font.url) : '';\n const fontFamilyValue = opts.font ? buildFontFamilyValue(opts.font.family) : '-apple-system,system-ui,sans-serif';\n\n return `<!DOCTYPE html><html><head>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n<meta name=\"color-scheme\" content=\"light\"/>\n<style>${fontFaceCss}html,body{margin:0;padding:0;background:transparent;font-family:${fontFamilyValue}}#mf{width:100%;box-sizing:border-box}#mf *{max-width:100%;box-sizing:border-box}</style>\n</head><body>\n<div id=\"mf\"></div>\n<script>\n(function(){\n function emit(s){ try{ if(window.ReactNativeWebView){ window.ReactNativeWebView.postMessage(s); } }catch(e){} }\n function emitJSON(n,p){ try{ emit(JSON.stringify({name:n,payload:p||{}})); }catch(e){} }\n window.DeepdotsClose = function(){ emitJSON('popup_close'); };\n var initialized=false;\n function initMF(){\n if(initialized || !window.magicfeedback) return initialized;\n initialized=true;\n try{\n window.magicfeedback.init({ debug:false, env:'${env}' });\n var form = window.magicfeedback.form(${sid}, ${pid}, ${profileJson}, ${metaJson});\n window.DeepdotsForm = form;\n form.generate('mf', {\n addButton:false,\n getMetaData:true,\n onLoadedEvent:function(){ emitJSON('loaded'); },\n beforeSubmitEvent:function(){ emitJSON('before_submit'); },\n afterSubmitEvent:function(p){\n try{ if(p && p.completed){ emitJSON('survey_completed', p); } else { emitJSON('after_submit', p); } }\n catch(e){ emitJSON('after_submit'); }\n },\n onBackEvent:function(p){ emitJSON('back', p); }\n }).catch(function(e){ emit('error:init'); });\n }catch(e){ emit('error:exception'); }\n return true;\n }\n var s=document.createElement('script'); s.src=${JSON.stringify(cdn)}; s.async=true;\n s.onload=function(){ initMF(); };\n s.onerror=function(){ emit('error:load'); };\n document.head.appendChild(s);\n var tries=0, t=setInterval(function(){ if(initMF()||++tries>40){ clearInterval(t); } },150);\n})();\n</script>\n</body></html>`;\n}\n","import type { DeepdotsEventType, PopupActions, PopupStyle } from '../types';\nimport type { PopupRenderer } from './renderer';\nimport { buildSurveyIdentity } from '../tracking/tracking-manager';\nimport { buildSurveyHtml } from '../ui/surveyHtml';\nimport { sdkWarn } from '../util/logger';\n\ntype EmitFn = (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void;\n\n/** Payload que recibe el host para montar el WebView del survey. */\nexport interface ReactNativeSurveyPayload {\n surveyId: string;\n productId: string;\n /** HTML autocontenido para `<WebView source={{ html }}>`. */\n html: string;\n}\n\nexport interface ReactNativeRendererOptions {\n /** Se llama al mostrar un popup: monta el WebView con `payload.html`. */\n onShow?: (payload: ReactNativeSurveyPayload) => void;\n /** Se llama al cerrar el popup: desmonta el WebView. */\n onHide?: () => void;\n}\n\n/**\n * Renderer de React Native: el SDK no puede pintar componentes RN, así que entrega el\n * HTML del survey al host (que lo monta en `react-native-webview`) y traduce los\n * mensajes del WebView a eventos de popup del SDK (→ `POST /sdk/popups`, Messaging #18–22).\n *\n * Uso:\n * ```tsx\n * const renderer = new ReactNativePopupRenderer({\n * onShow: (p) => setSurvey({ ...p, visible: true }),\n * onHide: () => setSurvey((s) => ({ ...s, visible: false })),\n * });\n * sdk.setRenderer(renderer);\n * // …\n * <WebView source={{ html: survey.html }}\n * onMessage={(e) => renderer.handleMessage(e.nativeEvent.data)} />\n * ```\n */\nexport class ReactNativePopupRenderer implements PopupRenderer {\n private emitFn: EmitFn | null = null;\n private onCloseFn: (() => void) | null = null;\n private currentSurveyId: string | null = null;\n private partialEmitted = false;\n\n constructor(private options: ReactNativeRendererOptions = {}) {}\n\n init(): void {\n /* nada que preparar */\n }\n\n show(\n surveyId: string,\n productId: string,\n _actions: PopupActions | undefined,\n emit: EmitFn,\n onClose: () => void,\n env: string = 'production',\n userId?: string,\n style?: PopupStyle,\n sessionId?: string,\n miniService?: string,\n analyticsFeedbackSessionId?: string,\n ): void {\n this.emitFn = emit;\n this.onCloseFn = onClose;\n this.currentSurveyId = surveyId;\n this.partialEmitted = false;\n\n const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);\n const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata, font: style?.font });\n if (this.options.onShow) {\n this.options.onShow({ surveyId, productId, html });\n } else {\n sdkWarn(\n '[Deepdots] ReactNativePopupRenderer sin onShow: pasa { onShow } a new ReactNativePopupRenderer({...}) para montar el WebView del survey.',\n );\n }\n }\n\n hide(): void {\n this.options.onHide?.();\n this.currentSurveyId = null;\n }\n\n /**\n * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a\n * eventos del SDK: primera interacción → `popup_clicked` (PARTIAL), completado →\n * `survey_completed` (COMPLETED) y cierra el popup.\n */\n handleMessage(raw: string): void {\n const surveyId = this.currentSurveyId;\n if (!surveyId || !this.emitFn) return;\n\n let name: string | undefined;\n try {\n name = (JSON.parse(raw) as { name?: string }).name;\n } catch {\n name = raw; // mensajes simples (\"error:load\")\n }\n\n switch (name) {\n case 'loaded':\n case 'before_submit':\n case 'after_submit':\n case 'back':\n if (!this.partialEmitted) {\n this.partialEmitted = true;\n this.emitFn('popup_clicked', surveyId, { action: 'partial' });\n }\n break;\n case 'survey_completed':\n this.emitFn('survey_completed', surveyId);\n this.onCloseFn?.(); // el core enruta a hidePopup() → renderer.hide() → onHide\n break;\n case 'popup_close':\n this.onCloseFn?.();\n break;\n default:\n break;\n }\n }\n}\n\n/** Detección simple de entorno React Native. */\nexport function isReactNativeEnv(): boolean {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return typeof navigator !== 'undefined' && (navigator as any).product === 'ReactNative';\n}\n\n/** Factoría usada por `createDefaultRenderer` cuando se detecta RN (el host debería pasar onShow). */\nexport function createReactNativeRenderer(): PopupRenderer {\n return new ReactNativePopupRenderer();\n}\n","import {DeepdotsEventType, PopupActions, PopupStyle} from '../types';\nimport { isReactNativeEnv, createReactNativeRenderer } from './react-native-renderer';\n// renderPopup (DOM + @magicfeedback/native) se carga PEREZOSAMENTE solo al mostrar\n// un popup en navegador, para que importar el SDK sea seguro en React Native/SSR.\n\nexport interface PopupRenderer {\n /** Preparar recursos si aplica */\n init?(): void;\n /** Mostrar popup */\n show(\n surveyId: string,\n productId: string,\n actions: PopupActions | undefined,\n emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void,\n onClose: () => void,\n env?: string,\n userId?: string,\n style?: PopupStyle,\n sessionId?: string,\n miniService?: string,\n analyticsFeedbackSessionId?: string,\n ): void;\n /** Ocultar popup */\n hide(): void;\n}\n\n/** Renderer que no hace nada (SSR / entornos sin DOM) */\nexport class NoopPopupRenderer implements PopupRenderer {\n show(): void { /* no-op */ }\n hide(): void { /* no-op */ }\n}\n\n/** Renderer para navegadores usando la implementación actual basada en renderPopup */\nexport class BrowserPopupRenderer implements PopupRenderer {\n private container: HTMLElement | null = null;\n private visible = false;\n\n init(): void {\n if (typeof document === 'undefined') return;\n if (!this.container) {\n this.container = document.createElement('div');\n this.container.id = 'deepdots-popup-container';\n this.container.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: none;\n z-index: 999999;\n background: rgba(0, 0, 0, 0.5);\n justify-content: center;\n align-items: center;\n `;\n }\n if (!document.body.contains(this.container)) {\n document.body.appendChild(this.container);\n }\n }\n\n show(\n surveyId: string,\n productId: string,\n actions: PopupActions | undefined,\n emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void,\n onClose: () => void,\n env: string = 'production',\n userId?: string,\n style?: PopupStyle,\n sessionId?: string,\n miniService?: string,\n analyticsFeedbackSessionId?: string,\n ): void {\n if (this.visible) return;\n if (!this.container || !document.body.contains(this.container)) this.init();\n if (!this.container) return; // aún sin DOM\n this.visible = true;\n const container = this.container;\n void import('../ui/renderPopup').then(({ renderPopup }) => {\n renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);\n });\n }\n\n hide(): void {\n this.visible = false;\n if (this.container) {\n this.container.style.display = 'none';\n this.container.innerHTML = '';\n }\n }\n}\n\n/** Factoría para obtener renderer por defecto */\nexport function createDefaultRenderer(): PopupRenderer {\n if (typeof document === 'undefined') {\n // Entorno sin DOM: probar RN primero\n if (isReactNativeEnv()) return createReactNativeRenderer();\n return new NoopPopupRenderer();\n }\n // Si estamos en React Native y hay WebView con document emulado, aún queremos RN renderer\n if (isReactNativeEnv()) return createReactNativeRenderer();\n return new BrowserPopupRenderer();\n}\n","/**\n * Capa de Analytics (eventos GA-style) — canal SEPARADO del feedback de survey.\n *\n * El feedback de survey se envía con `magicfeedback.send()`. La analítica de\n * navegación/comportamiento (page views, mini-services, tasks/funnels, meaningful\n * interactions, findability…) va por un ENDPOINT PROPIO, vinculada por `user_id`.\n *\n * De momento NO se hace la llamada: el `sink` por defecto hace `console.log` del\n * payload exacto que se enviaría (dry-run), para poder verificarlo. Cuando el\n * endpoint exista, se sustituye el sink por un POST.\n */\n\nimport type { DeviceInfo } from './device-info';\n\nexport interface AnalyticsEvent {\n name: string;\n timestamp: number;\n params?: Record<string, unknown>;\n}\n\nexport interface AnalyticsEnvelope {\n /** Clave pública del cliente (auth). */\n publicKey?: string;\n /** Id que vincula la analítica al usuario (el backend cose por aquí). */\n userId: string | null;\n /** Session id del backend si ya se conoce (puede ir null). */\n sessionId: string | null;\n context: {\n platform: string; // 'web' | 'android' | 'ios' (RN inyecta la suya)\n language?: string;\n /** Info de dispositivo (Technology #11–13). */\n device?: DeviceInfo;\n /** User attributes del cliente, usados para breakdowns (registration_status, pass_type, …). */\n attributes: Record<string, string>;\n /** Métricas del host: valores medibles persistentes → van en `feedback.metrics` del body. */\n metrics?: Record<string, string>;\n };\n events: AnalyticsEvent[];\n}\n\n/** Contexto del flush, para que el sink pueda adaptar el transporte. */\nexport interface AnalyticsFlushMeta {\n /**\n * `true` cuando el flush ocurre porque la página/app se está cerrando. El sink debe usar\n * un transporte que sobreviva al unload (sendBeacon) y no puede esperar respuestas.\n */\n final?: boolean;\n /**\n * `true` cuando este es el ÚLTIMO lote de la sesión (cierre de página, app a background,\n * cambio de usuario, tracking desactivado). El sink lo envía con `completed:true` y olvida\n * el `sessionId`, para que el lote siguiente abra un registro nuevo.\n */\n sessionEnd?: boolean;\n}\n\n/**\n * Envío del lote. Si devuelve una promesa que **rechaza**, el manager re-encola los\n * eventos para reintentarlos en el siguiente flush (fallo transitorio: red o 5xx).\n * Resolver = lote entregado o descartado definitivamente (no se reintenta).\n */\nexport type AnalyticsSink = (payload: AnalyticsEnvelope, meta?: AnalyticsFlushMeta) => void | Promise<void>;\n\n/** Identidad resuelta por el tracking, inyectada al construir el payload. */\nexport interface AnalyticsIdentity {\n userId: string | null;\n sessionId: string | null;\n}\n\n/**\n * Sink de dry-run: NO envía nada, solo vuelca lo que se enviaría por el `log` dado\n * (default `console.log`, o el logger inyectado por el host en init()).\n */\nexport function createDryRunSink(log: (...args: unknown[]) => void = console.log): AnalyticsSink {\n return (payload) =>\n log(\n '[DeepdotsAnalytics] (dry-run · NO enviado · sin init.analytics) POST /sdk/feedback →',\n JSON.stringify(payload, null, 2),\n );\n}\n\n/** Sink por defecto: NO envía nada, solo pinta por consola lo que se enviaría. */\nexport const dryRunSink: AnalyticsSink = createDryRunSink();\n\nexport interface AnalyticsManagerOptions {\n sink?: AnalyticsSink;\n now?: () => number;\n publicKey?: string;\n language?: string;\n device?: DeviceInfo;\n /** Plataforma del envelope (default 'web'; RN inyecta 'android'/'ios'). */\n platform?: string;\n /** Nº máximo de eventos en buffer antes de solicitar un flush automático (default 20). */\n maxBatchSize?: number;\n /**\n * Techo de eventos retenidos en memoria contando los re-encolados por fallo de envío\n * (default 200). Al superarlo se descartan los más antiguos.\n */\n maxBufferedEvents?: number;\n /** Callback invocado cuando el buffer alcanza `maxBatchSize`. El caller hace el flush real. */\n onFlushNeeded?: () => void;\n}\n\nexport class AnalyticsManager {\n private sink: AnalyticsSink;\n private now: () => number;\n private publicKey?: string;\n private language?: string;\n private device?: DeviceInfo;\n private platform: string;\n\n private events: AnalyticsEvent[] = [];\n private attributes: Record<string, string> = {};\n /** Métricas del host (valores medibles persistentes). Sobrescriben por key; se reenvían en cada flush. */\n private metrics: Record<string, string> = {};\n /** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el \"más reciente\". */\n private activeMiniServices = new Map<string, number>();\n private maxBatchSize: number;\n private maxBufferedEvents: number;\n private onFlushNeeded?: () => void;\n\n constructor(options: AnalyticsManagerOptions = {}) {\n this.sink = options.sink ?? dryRunSink;\n this.now = options.now ?? (() => Date.now());\n this.publicKey = options.publicKey;\n this.language = options.language;\n this.device = options.device;\n this.platform = options.platform ?? 'web';\n this.maxBatchSize = options.maxBatchSize ?? 20;\n this.maxBufferedEvents = options.maxBufferedEvents ?? 200;\n this.onFlushNeeded = options.onFlushNeeded;\n }\n\n /** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */\n updateDevice(partial: Partial<DeviceInfo>): void {\n this.device = { ...this.device, ...partial } as DeviceInfo;\n }\n\n /** Mezcla user attributes (se coercionan a string). Mutable en runtime. */\n setUserAttributes(attrs: Record<string, string | number | boolean>): void {\n for (const [k, v] of Object.entries(attrs)) {\n if (!k) continue;\n this.attributes[k] = String(v);\n }\n }\n\n /**\n * Registra/actualiza una métrica (valor medible). Persistente: se reenvía en cada flush.\n * Repetir la misma key SOBRESCRIBE; el valor se coerciona a string; ignora key vacía.\n */\n setMetric(key: string, value: string | number | boolean): void {\n if (!key) return;\n this.metrics[key] = String(value);\n }\n\n /**\n * Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.\n * Admite varios activos a la vez (concurrencia); el \"actual\" para etiquetar es el más reciente.\n * Reentrar con un nombre ya activo refresca su orden y su tiempo de entrada.\n */\n enterMiniService(name: string, entryPointType?: string): void {\n this.activeMiniServices.delete(name); // reinsertar = pasa a ser el más reciente\n this.activeMiniServices.set(name, this.now());\n this.track('deepdots_mini_service_enter', { entry_point_type: entryPointType ?? null });\n }\n\n /**\n * Cierra el mini-service `name` emitiendo `mini_service_exit` con su duración (#27).\n * No-op si ese nombre no está activo. Cierre coherente con concurrencia.\n */\n exitMiniService(name: string): void {\n const enteredAt = this.activeMiniServices.get(name);\n if (enteredAt === undefined) return;\n const durationSeconds = Math.max(0, Math.round((this.now() - enteredAt) / 1000));\n this.activeMiniServices.delete(name); // dejar de etiquetar con este antes de emitir\n this.track('deepdots_mini_service_exit', { mini_service: name, duration_seconds: durationSeconds });\n }\n\n /** Cierra TODOS los mini-services activos (orden LIFO). Para el cierre por lifecycle (background/cierre). */\n exitAllMiniServices(): void {\n for (const name of Array.from(this.activeMiniServices.keys()).reverse()) {\n this.exitMiniService(name);\n }\n }\n\n /**\n * Olvida lo que pertenecía al usuario anterior (user attributes + métricas) al cambiar de\n * usuario. NO toca el buffer de eventos: el cierre de sesión ya lo vació con la identidad\n * vieja, y lo que llegue después es del usuario nuevo.\n */\n resetUserScope(): void {\n this.attributes = {};\n this.metrics = {};\n }\n\n /** Registra un evento de analítica (modelo GA: nombre + params). */\n track(name: string, params?: Record<string, unknown>): void {\n const current = this.getMiniService();\n const merged: Record<string, unknown> = {\n ...(current ? { mini_service: current } : {}),\n ...(params ?? {}),\n };\n this.events.push({\n name,\n timestamp: this.now(),\n params: Object.keys(merged).length ? merged : undefined,\n });\n this.enforceBufferCap();\n if (this.events.length >= this.maxBatchSize) {\n this.onFlushNeeded?.();\n }\n }\n\n /** Mini-service actual (el más reciente aún activo) para etiquetar eventos + metadata del survey (#33). */\n getMiniService(): string | null {\n let last: string | null = null;\n for (const name of this.activeMiniServices.keys()) last = name; // última clave = más reciente\n return last;\n }\n\n /** Nº de eventos pendientes de flush. */\n pending(): number {\n return this.events.length;\n }\n\n /** Construye el envelope que se enviaría al endpoint de analytics. */\n buildPayload(identity: AnalyticsIdentity): AnalyticsEnvelope {\n return {\n publicKey: this.publicKey,\n userId: identity.userId,\n sessionId: identity.sessionId,\n context: {\n platform: this.platform,\n language: this.language,\n device: this.device,\n attributes: { ...this.attributes },\n metrics: { ...this.metrics },\n },\n events: [...this.events],\n };\n }\n\n /**\n * Envía (vía sink) el lote acumulado y vacía el buffer. No-op si no hay eventos.\n *\n * Si el sink falla de forma transitoria (promesa rechazada o excepción síncrona) el lote\n * se **re-encola** al principio del buffer y se reintenta en el siguiente flush: un error\n * de red o un 5xx no debe hacer desaparecer los eventos.\n */\n flush(identity: AnalyticsIdentity, meta?: AnalyticsFlushMeta): AnalyticsEnvelope | null {\n if (this.events.length === 0) return null;\n const batch = this.events;\n const payload = this.buildPayload(identity);\n this.events = [];\n\n let result: void | Promise<void>;\n try {\n // sin meta se invoca con un solo argumento (compatibilidad con sinks del host)\n result = meta ? this.sink(payload, meta) : this.sink(payload);\n } catch {\n this.requeue(batch);\n return payload;\n }\n if (result && typeof (result as Promise<void>).then === 'function') {\n (result as Promise<void>).catch(() => this.requeue(batch));\n }\n return payload;\n }\n\n /**\n * Devuelve un lote fallido al buffer, delante de lo que haya llegado mientras se enviaba\n * (orden cronológico). Con el buffer lleno se descartan los eventos más antiguos.\n */\n private requeue(batch: AnalyticsEvent[]): void {\n this.events = [...batch, ...this.events];\n this.enforceBufferCap();\n }\n\n /** Techo del buffer: si el envío lleva rato fallando, se sacrifica lo más antiguo. */\n private enforceBufferCap(): void {\n const overflow = this.events.length - this.maxBufferedEvents;\n if (overflow > 0) this.events.splice(0, overflow);\n }\n}\n","/**\n * Mapeo del envelope de analytics → body de `POST /sdk/feedback`.\n *\n * La analítica se envía como un Feedback del modelo del Surveys SDK, agrupado por una\n * INTEGRACIÓN creada en la plataforma. Se manda en streaming con `completed:false`; el\n * backend cose por `sessionId` + `user_id`. El ÚLTIMO lote de una sesión (cierre de página,\n * app a background, cambio de usuario) va con `completed:true`: cierra el registro y el\n * siguiente lote omite el `sessionId` viejo para que el backend abra uno nuevo.\n * `feedback.finished` se queda siempre en `false` (no es la señal de cierre acordada).\n *\n * Encoding:\n * - todo va en `feedback.metadata`: contexto (user_id, session_id, platform…) + eventos\n * - cada entrada usa `value: string[]` (array de un elemento)\n * - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}\n * - identidad (user_id) → `profile` como `external-user-id`\n * - métricas del host (setMetric) → `feedback.metrics` (mismo shape que metadata, sin prefijo)\n * - `answers` vacío\n * - `text` vacío\n */\n\nimport type { AnalyticsEnvelope, AnalyticsSink } from './analytics-manager';\n\nexport interface AnalyticsKeys {\n publicKey: string;\n integration: string;\n}\n\nexport interface FeedbackKV {\n key: string;\n value: string[];\n}\n\nexport interface AnalyticsFeedbackBody {\n feedback: {\n text: string;\n answers: FeedbackKV[];\n metrics: FeedbackKV[];\n metadata: FeedbackKV[];\n profile: FeedbackKV[];\n finished: boolean;\n };\n publicKey: string;\n integration: string;\n completed: boolean;\n finished?: boolean;\n /** sessionId devuelto por el primer POST; permite que el backend agrupe todos los eventos en un solo registro. */\n sessionId?: string;\n}\n\n/** Inserta un par key/value en la lista solo si el valor está definido y no es vacío. */\nfunction pushKV(list: FeedbackKV[], key: string, value: unknown): void {\n if (value === undefined || value === null || value === '') return;\n list.push({ key, value: [String(value)] });\n}\n\nexport interface BuildBodyOptions {\n /**\n * `true` en el último lote de la sesión → `completed:true` (cierra el registro en backend).\n * El body SÍ lleva el `sessionId` actual (es el registro que se cierra); es el lote\n * SIGUIENTE el que lo omite.\n */\n sessionEnd?: boolean;\n}\n\nexport function buildAnalyticsFeedbackBody(\n envelope: AnalyticsEnvelope,\n keys: AnalyticsKeys,\n feedbackSessionId?: string,\n options?: BuildBodyOptions,\n): AnalyticsFeedbackBody {\n const { context } = envelope;\n\n const profile: FeedbackKV[] = [];\n pushKV(profile, 'external-user-id', envelope.userId);\n\n // Todo va en metadata: contexto del sistema (prefijo deepdots_) + atributos de usuario + eventos\n const metadata: FeedbackKV[] = [];\n pushKV(metadata, 'deepdots_user_id', envelope.userId);\n pushKV(metadata, 'deepdots_session_id', envelope.sessionId);\n pushKV(metadata, 'deepdots_platform', context.platform);\n pushKV(metadata, 'deepdots_language', context.language);\n if (context.device) {\n const d = context.device;\n pushKV(metadata, 'deepdots_device_type', d.device_type);\n pushKV(metadata, 'deepdots_os_version', d.os_version);\n pushKV(metadata, 'deepdots_device_model', d.device_model);\n pushKV(metadata, 'deepdots_app_version', d.app_version);\n pushKV(metadata, 'deepdots_user_agent', d.user_agent);\n pushKV(metadata, 'deepdots_timezone', d.timezone);\n pushKV(metadata, 'deepdots_referrer', d.referrer);\n pushKV(metadata, 'deepdots_viewport_size', d.viewport_size);\n pushKV(metadata, 'deepdots_screen_resolution', d.screen_resolution);\n pushKV(metadata, 'deepdots_pixel_ratio', d.pixel_ratio);\n pushKV(metadata, 'deepdots_entry_type', d.entry_type);\n pushKV(metadata, 'deepdots_page_load_ms', d.page_load_ms);\n pushKV(metadata, 'deepdots_connection_type', d.connection_type);\n pushKV(metadata, 'deepdots_country', d.country);\n pushKV(metadata, 'deepdots_city', d.city);\n }\n for (const [k, v] of Object.entries(context.attributes ?? {})) {\n pushKV(metadata, k, v);\n }\n for (const e of envelope.events) {\n metadata.push({\n key: e.name,\n value: [JSON.stringify({ timestamp: e.timestamp, ...(e.params ?? {}) })],\n });\n }\n\n // Métricas del host → campo dedicado `feedback.metrics` (mismo shape que metadata, sin prefijo).\n const metrics: FeedbackKV[] = [];\n for (const [k, v] of Object.entries(context.metrics ?? {})) {\n pushKV(metrics, k, v);\n }\n\n return {\n feedback: {\n text: '',\n answers: [],\n metrics,\n metadata,\n profile,\n finished: false,\n },\n publicKey: keys.publicKey,\n integration: keys.integration,\n // Único marcador de cierre acordado con backend: el último lote de la sesión.\n completed: options?.sessionEnd === true,\n ...(feedbackSessionId ? { sessionId: feedbackSessionId } : {}),\n };\n}\n\nexport interface FeedbackSinkOptions {\n /** Base URL del backend (api(.|-dev.)deepdots.com) ya resuelta por el entorno. */\n baseUrl: string;\n keys: AnalyticsKeys;\n /** Logger opcional (gated por debug). */\n log?: (...args: unknown[]) => void;\n /** fetch inyectable (tests / RN). */\n fetchImpl?: typeof fetch;\n /**\n * `navigator.sendBeacon` inyectable. Solo se usa en el flush final (cierre de página):\n * sobrevive al unload, a cambio de no poder leer la respuesta. Si no se pasa, el flush\n * final usa `fetch` con `keepalive`.\n */\n sendBeaconImpl?: (url: string, body: Blob | string) => boolean;\n /** Callback invocado cuando el backend devuelve un nuevo sessionId (primer POST). */\n onSessionId?: (id: string) => void;\n /** Callback invocado al cerrar la sesión (`completed:true`): el sessionId cacheado se olvida. */\n onSessionReset?: () => void;\n}\n\n/** Límite práctico de `fetch({keepalive:true})` y de `sendBeacon` (~64KB en Chrome). */\nconst KEEPALIVE_MAX_BYTES = 60_000;\n\n/** Un fallo transitorio merece reintento; un 4xx (payload/claves/Contact) no. */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nasync function safeBodyText(res: Response): Promise<string> {\n try {\n return (await res.text()).slice(0, 500);\n } catch {\n return '';\n }\n}\n\n/**\n * Sink real: transforma el envelope y hace `POST {baseUrl}/sdk/feedback`.\n *\n * Stateful: cachea el `sessionId` que devuelve el backend en la primera respuesta y lo\n * envía en los siguientes POSTs para que el backend agrupe todos los eventos en un único\n * registro de feedback.\n *\n * Garantías de entrega:\n * - mientras no se conozca el `sessionId`, los lotes se **serializan** (esperan la primera\n * respuesta): dos POSTs a la vez sin `sessionId` crearían dos registros y partirían los datos;\n * - `keepalive` para que el navegador no cancele el POST al navegar fuera, y `sendBeacon`\n * en el flush final (cierre de página), donde `fetch` puede morir con el documento;\n * - la promesa **rechaza** en fallo transitorio (red, 5xx, 408, 429) para que el\n * `AnalyticsManager` re-encole el lote; un 4xx se loguea y se descarta.\n */\nexport function createFeedbackSink(options: FeedbackSinkOptions): AnalyticsSink {\n let feedbackSessionId: string | undefined;\n /** Primer POST (aún sin sessionId) en vuelo: los siguientes lotes lo esperan. */\n let firstPostInFlight: Promise<void> | null = null;\n\n const url = `${options.baseUrl}/sdk/feedback`;\n\n const post = async (body: AnalyticsFeedbackBody, final: boolean, sessionEnd = false): Promise<void> => {\n const json = JSON.stringify(body);\n const small = json.length <= KEEPALIVE_MAX_BYTES;\n\n // Cierre de página: sendBeacon sobrevive al unload (a cambio, no hay respuesta que leer).\n if (final && options.sendBeaconImpl && small) {\n const payload = typeof Blob !== 'undefined' ? new Blob([json], { type: 'application/json' }) : json;\n if (options.sendBeaconImpl(url, payload)) {\n options.log?.('[DeepdotsAnalytics] flush final vía sendBeacon');\n return;\n }\n options.log?.('[DeepdotsAnalytics] sendBeacon rechazó el lote; fallback a fetch');\n }\n\n const f = options.fetchImpl ?? (typeof fetch !== 'undefined' ? fetch : undefined);\n if (!f) {\n options.log?.('[DeepdotsAnalytics] no fetch disponible; payload no enviado', body);\n return;\n }\n\n const res = await f(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: json,\n // keepalive: el POST sobrevive a la navegación mientras el body sea pequeño.\n ...(small ? { keepalive: true } : {}),\n });\n\n if (!res.ok) {\n const detail = await safeBodyText(res);\n if (isRetryableStatus(res.status)) {\n // Rechazar → el manager re-encola el lote y se reintenta en el siguiente flush.\n throw new Error(`POST /sdk/feedback ${res.status} (reintentable): ${detail}`);\n }\n options.log?.(\n `[DeepdotsAnalytics] POST /sdk/feedback rechazado con ${res.status}; lote DESCARTADO:`,\n detail,\n );\n return;\n }\n\n // El POST de cierre devuelve el sessionId del registro que acabamos de cerrar:\n // NO se re-cachea (si no, el lote siguiente volvería a apuntar al registro cerrado).\n if (sessionEnd) return;\n\n try {\n const data = (await res.json()) as { sessionId?: string };\n if (data?.sessionId && data.sessionId !== feedbackSessionId) {\n feedbackSessionId = data.sessionId;\n options.log?.('[DeepdotsAnalytics] feedbackSessionId cacheado:', feedbackSessionId);\n options.onSessionId?.(feedbackSessionId);\n }\n } catch {\n /* respuesta sin JSON — ignorar */\n }\n };\n\n return async (envelope: AnalyticsEnvelope, meta) => {\n const final = meta?.final === true;\n const sessionEnd = meta?.sessionEnd === true;\n\n // Sin sessionId todavía: esperar al primer POST para heredarlo y no partir el registro.\n // En el flush final no se espera (la página se está muriendo): mejor un registro\n // partido que perder el lote — el backend cose por user_id.\n if (!feedbackSessionId && firstPostInFlight && !final) {\n await firstPostInFlight;\n }\n\n const body = buildAnalyticsFeedbackBody(envelope, options.keys, feedbackSessionId, { sessionEnd });\n options.log?.('[DeepdotsAnalytics] POST /sdk/feedback →', body);\n\n const sent = post(body, final, sessionEnd);\n if (sessionEnd) {\n // Registro cerrado con `completed:true`: olvidar el sessionId para que el siguiente\n // lote lo OMITA y el backend abra uno nuevo (sesión nueva, o usuario nuevo).\n feedbackSessionId = undefined;\n firstPostInFlight = null;\n options.onSessionReset?.();\n } else if (!feedbackSessionId) {\n // nunca rechaza: es solo una barrera de orden, el fallo lo propaga `sent`\n firstPostInFlight = sent.then(\n () => undefined,\n () => undefined,\n );\n }\n await sent;\n };\n}\n","/**\n * Observador de navegación (Fase 2). Detecta cambios de pantalla y emite \"visitas\"\n * completadas (screen + entrada/salida/duración) cuando se abandona una pantalla.\n *\n * En Web se engancha a la History API (pushState/replaceState/popstate/hashchange).\n * La lógica de timing y normalización es pura y testeable sin DOM (vía `begin`/`visit`/`stop`\n * y un reloj inyectable).\n *\n * El consumidor (DeepdotsPopups) reenvía cada visita como un evento `page_view` por el\n * canal de analytics; el grafo se reconstruye en backend por orden + timestamp.\n */\n\nexport interface ScreenVisit {\n screen: string;\n entry: number;\n exit: number;\n durationSeconds: number;\n}\n\nexport type VisitListener = (visit: ScreenVisit) => void;\n\nconst UUID_PREFIX = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;\n\n/** Normaliza una URL a un nombre de pantalla: path (+hash route), sin query, IDs → :id. */\nexport function normalizeScreen(href: string): string {\n let pathAndHash: string;\n try {\n const u = new URL(href, 'http://deepdots.local');\n pathAndHash = u.pathname + (u.hash || '');\n } catch {\n pathAndHash = href;\n }\n // quitar query (incluida la que pueda venir dentro del hash route)\n pathAndHash = pathAndHash.replace(/\\?[^#]*/g, '');\n const collapsed = pathAndHash\n .split('/')\n .map((seg) => (/^[0-9]+$/.test(seg) || UUID_PREFIX.test(seg) ? ':id' : seg))\n .join('/');\n return collapsed || '/';\n}\n\nexport interface NavigationObserverOptions {\n now?: () => number;\n}\n\nexport class NavigationObserver {\n private now: () => number;\n private listeners = new Set<VisitListener>();\n private currentScreen: string | null = null;\n private entryAt = 0;\n private installed = false;\n private domHandlers: Array<() => void> = [];\n\n constructor(options: NavigationObserverOptions = {}) {\n this.now = options.now ?? (() => Date.now());\n }\n\n onVisit(listener: VisitListener): void {\n this.listeners.add(listener);\n }\n\n /** Fija la pantalla inicial (sin emitir visita; su duración se cierra al salir). */\n begin(href: string): void {\n this.currentScreen = normalizeScreen(href);\n this.entryAt = this.now();\n }\n\n /** Navega a una pantalla nueva: cierra la anterior (emite visita) y abre la nueva. */\n visit(href: string): void {\n const next = normalizeScreen(href);\n if (next === this.currentScreen) return; // misma pantalla → no es una visita nueva\n this.closeCurrent();\n this.currentScreen = next;\n this.entryAt = this.now();\n }\n\n /** Cierra la pantalla actual emitiendo su visita (p. ej. al cerrar la app/pestaña). */\n stop(): void {\n this.closeCurrent();\n this.currentScreen = null;\n }\n\n private closeCurrent(): void {\n if (this.currentScreen == null) return;\n const exit = this.now();\n const visit: ScreenVisit = {\n screen: this.currentScreen,\n entry: this.entryAt,\n exit,\n durationSeconds: Math.max(0, Math.round((exit - this.entryAt) / 1000)),\n };\n this.listeners.forEach((l) => {\n try {\n l(visit);\n } catch {\n /* listener no debe romper la navegación */\n }\n });\n }\n\n /** Instala los hooks de History en el navegador y registra la pantalla actual. */\n install(): void {\n if (this.installed || typeof window === 'undefined' || typeof history === 'undefined') return;\n this.installed = true;\n\n const handle = () => this.visit(window.location.href);\n\n const originalPush = history.pushState.bind(history);\n const originalReplace = history.replaceState.bind(history);\n history.pushState = (data: unknown, unused: string, url?: string | URL | null) => {\n const r = originalPush(data as never, unused, url as never);\n handle();\n return r;\n };\n history.replaceState = (data: unknown, unused: string, url?: string | URL | null) => {\n const r = originalReplace(data as never, unused, url as never);\n handle();\n return r;\n };\n window.addEventListener('popstate', handle);\n window.addEventListener('hashchange', handle);\n\n this.domHandlers.push(() => {\n history.pushState = originalPush;\n history.replaceState = originalReplace;\n window.removeEventListener('popstate', handle);\n window.removeEventListener('hashchange', handle);\n });\n\n this.begin(window.location.href);\n }\n\n uninstall(): void {\n this.domHandlers.forEach((off) => off());\n this.domHandlers = [];\n this.installed = false;\n }\n}\n","/**\n * Device info para el context de analytics (Technology #11–13).\n *\n * En Web enviamos `device_type` + `user_agent` crudo y dejamos que el backend\n * derive OS/navegador del UA (como hace GA). Los campos adicionales (timezone,\n * referrer, viewport, etc.) se recogen de las APIs del navegador sin permisos.\n * `country`/`city` se rellenan de forma asíncrona (ver geo-info.ts).\n * En KMP las APIs nativas dan os_version/device_model reales.\n */\n\nexport interface DeviceInfo {\n device_type: 'mobile' | 'tablet' | 'desktop';\n os_version?: string;\n device_model?: string;\n app_version?: string;\n user_agent?: string;\n timezone?: string;\n referrer?: string;\n viewport_size?: string;\n screen_resolution?: string;\n pixel_ratio?: number;\n entry_type?: string;\n page_load_ms?: number;\n connection_type?: string;\n country?: string;\n city?: string;\n}\n\nexport function parseDeviceType(ua: string): 'mobile' | 'tablet' | 'desktop' {\n const s = (ua || '').toLowerCase();\n if (/ipad|tablet|playbook|silk/.test(s) || (/android/.test(s) && !/mobile/.test(s))) {\n return 'tablet';\n }\n if (/mobi|iphone|ipod|windows phone/.test(s) || (/android/.test(s) && /mobile/.test(s))) {\n return 'mobile';\n }\n return 'desktop';\n}\n\nfunction getEntryType(): string | undefined {\n if (typeof performance === 'undefined') return undefined;\n const entries = performance.getEntriesByType('navigation');\n if (!entries.length) return undefined;\n return (entries[0] as PerformanceNavigationTiming).type;\n}\n\nfunction getPageLoadMs(): number | undefined {\n if (typeof performance === 'undefined') return undefined;\n const entries = performance.getEntriesByType('navigation');\n if (!entries.length) return undefined;\n const load = (entries[0] as PerformanceNavigationTiming).loadEventEnd;\n return load > 0 ? Math.round(load) : undefined;\n}\n\nfunction getConnectionType(): string | undefined {\n if (typeof navigator === 'undefined') return undefined;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (navigator as any).connection?.effectiveType ?? undefined;\n}\n\nexport function collectDeviceInfo(appVersion?: string): DeviceInfo {\n const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n return {\n device_type: parseDeviceType(ua),\n user_agent: ua || undefined,\n app_version: appVersion,\n timezone: typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions().timeZone : undefined,\n referrer: typeof document !== 'undefined' && document.referrer ? document.referrer : undefined,\n viewport_size:\n typeof window !== 'undefined' ? `${window.innerWidth}x${window.innerHeight}` : undefined,\n screen_resolution:\n typeof screen !== 'undefined' ? `${screen.width}x${screen.height}` : undefined,\n pixel_ratio: typeof window !== 'undefined' ? window.devicePixelRatio : undefined,\n entry_type: getEntryType(),\n page_load_ms: getPageLoadMs(),\n connection_type: getConnectionType(),\n };\n}\n","/**\n * Geolocalización por IP (Option B: client-side) robusta.\n *\n * Robustez:\n * - CACHE persistente en `KeyValueStorage` con TTL: en arranques posteriores el país/ciudad\n * está disponible de inmediato (sin llamada, sin gap de timing) y ahorra cuota del proveedor.\n * - CADENA de proveedores con fallback: si uno está bloqueado (ad-blocker), rate-limited (429)\n * o caído, se prueba el siguiente.\n * - TIMEOUT por proveedor vía `AbortController` para no colgarse en uno lento.\n *\n * Todo es fire-and-forget y tolerante a fallos: ante cualquier error devuelve `null`.\n */\n\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\n\nexport interface GeoInfo {\n country?: string;\n city?: string;\n}\n\n/** Clave de cache en storage. */\nexport const GEO_STORAGE_KEY = 'deepdots.geo';\n/** El país/ciudad casi no cambian: cache de 30 días. */\nexport const GEO_TTL_MS = 30 * 24 * 60 * 60 * 1000;\n/** Timeout por proveedor. */\nexport const GEO_TIMEOUT_MS = 3000;\n\ninterface CachedGeo extends GeoInfo {\n ts: number;\n}\n\n/** Lee el geo cacheado si sigue fresco (no supera el TTL). Tolerante a JSON corrupto. */\nexport function readCachedGeo(storage: KeyValueStorage, now: number): GeoInfo | null {\n try {\n const raw = storage.getItem(GEO_STORAGE_KEY);\n if (!raw) return null;\n const cached = JSON.parse(raw) as CachedGeo;\n if (typeof cached.ts !== 'number' || now - cached.ts > GEO_TTL_MS) return null;\n const geo: GeoInfo = {};\n if (cached.country) geo.country = cached.country;\n if (cached.city) geo.city = cached.city;\n return geo.country || geo.city ? geo : null;\n } catch {\n return null;\n }\n}\n\n/** Persiste el geo con la marca de tiempo actual. Tolerante a fallos de storage. */\nexport function writeCachedGeo(storage: KeyValueStorage, geo: GeoInfo, now: number): void {\n try {\n const payload: CachedGeo = { ...geo, ts: now };\n storage.setItem(GEO_STORAGE_KEY, JSON.stringify(payload));\n } catch {\n /* storage lleno / no disponible: se ignora */\n }\n}\n\nexport interface GeoProvider {\n url: string;\n parse: (data: Record<string, unknown>) => GeoInfo | null;\n}\n\n/** Proveedores en orden de preferencia. Todos devuelven JSON sin auth. */\nexport const DEFAULT_GEO_PROVIDERS: GeoProvider[] = [\n {\n // https://ipapi.co/json/\n url: 'https://ipapi.co/json/',\n parse: (d) => geoFrom(d.country_code, d.city),\n },\n {\n // https://ipwho.is/ (devuelve success:false con 200 en errores)\n url: 'https://ipwho.is/',\n parse: (d) => (d.success === false ? null : geoFrom(d.country_code, d.city)),\n },\n {\n // https://ipinfo.io/json (country = código ISO)\n url: 'https://ipinfo.io/json',\n parse: (d) => geoFrom(d.country, d.city),\n },\n];\n\nfunction geoFrom(country: unknown, city: unknown): GeoInfo | null {\n const c = typeof country === 'string' && country ? country : undefined;\n const t = typeof city === 'string' && city ? city : undefined;\n if (!c && !t) return null;\n return { country: c, city: t };\n}\n\nasync function fetchOne(provider: GeoProvider, f: typeof fetch, timeoutMs: number): Promise<GeoInfo | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await f(provider.url, { signal: controller.signal });\n if (!res.ok) return null;\n const data = (await res.json()) as Record<string, unknown>;\n return provider.parse(data);\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport interface FetchGeoOptions {\n fetchImpl?: typeof fetch;\n providers?: GeoProvider[];\n timeoutMs?: number;\n}\n\n/**\n * Intenta cada proveedor en orden (con timeout) y devuelve el primer geo válido.\n * `null` si ninguno responde. No lanza.\n */\nexport async function fetchGeo(options: FetchGeoOptions = {}): Promise<GeoInfo | null> {\n const f = options.fetchImpl ?? (typeof fetch !== 'undefined' ? fetch : undefined);\n if (!f) return null;\n const providers = options.providers ?? DEFAULT_GEO_PROVIDERS;\n const timeoutMs = options.timeoutMs ?? GEO_TIMEOUT_MS;\n for (const provider of providers) {\n const geo = await fetchOne(provider, f, timeoutMs);\n if (geo) return geo;\n }\n return null;\n}\n","/**\n * Resolución del idioma para el context de analytics.\n *\n * Prioridad: idioma explícito del host > `navigator.language` (web) > locale de `Intl`\n * (fallback automático que funciona en React Native con Hermes, donde `navigator.language`\n * no existe). Si nada resuelve, devuelve `undefined` y el campo se omite del metadata.\n */\n\n/** Forma mínima de `Intl` que necesitamos (inyectable para tests / entornos sin Intl). */\nexport interface IntlLike {\n DateTimeFormat: () => { resolvedOptions: () => { locale?: string } };\n}\n\nexport interface LanguageSources {\n /** Idioma forzado por el host (init.language). Máxima prioridad. */\n explicit?: string;\n /** `navigator` global (web). En RN suele no tener `language`. */\n navigator?: { language?: string };\n /** `Intl` global. Fallback cross-platform (web + RN/Hermes). */\n intl?: IntlLike;\n}\n\nfunction clean(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nexport function resolveLanguage(sources: LanguageSources): string | undefined {\n const explicit = clean(sources.explicit);\n if (explicit) return explicit;\n\n const navLang = clean(sources.navigator?.language);\n if (navLang) return navLang;\n\n try {\n const locale = clean(sources.intl?.DateTimeFormat().resolvedOptions().locale);\n if (locale) return locale;\n } catch {\n /* Intl no disponible o falló: se ignora */\n }\n\n return undefined;\n}\n","/**\n * Mide el tiempo ACTIVO (en primer plano), estilo GA4 (engagement time).\n * Acumula mientras está \"resumed\" (pestaña visible / app en foreground) y pausa en\n * background. `consume()` devuelve los ms activos desde la última lectura y reinicia,\n * dejando el timer corriendo. El consumidor emite un evento `user_engagement` con\n * `engagement_time_msec`; el backend lo suma por sesión → \"Average Time Spent per Session\" (#8).\n */\nexport class EngagementTracker {\n private now: () => number;\n private activeMs = 0;\n private lastResumeAt: number | null = null;\n\n constructor(now: () => number = () => Date.now()) {\n this.now = now;\n }\n\n /** Reanuda el conteo (pestaña visible / app en foreground). Idempotente. */\n resume(): void {\n if (this.lastResumeAt === null) this.lastResumeAt = this.now();\n }\n\n /** Pausa el conteo (pestaña oculta / app en background), acumulando el tramo activo. */\n pause(): void {\n if (this.lastResumeAt !== null) {\n this.activeMs += this.now() - this.lastResumeAt;\n this.lastResumeAt = null;\n }\n }\n\n /** Devuelve los ms activos acumulados desde la última lectura y reinicia (sin parar el timer). */\n consume(): number {\n if (this.lastResumeAt !== null) {\n const t = this.now();\n this.activeMs += t - this.lastResumeAt;\n this.lastResumeAt = t;\n }\n const ms = this.activeMs;\n this.activeMs = 0;\n return ms;\n }\n\n isActive(): boolean {\n return this.lastResumeAt !== null;\n }\n}\n","import type { KeyValueStorage } from '../tracking/tracking-manager';\nimport { STORAGE_KEYS } from '../tracking/tracking-manager';\n\n/** Valor de un atributo de contact (info interna del usuario que solo conoce el host). */\nexport type ContactAttributeValue = string | number | boolean;\nexport type ContactAttributes = Record<string, ContactAttributeValue>;\n\n/** Body de `POST /sdk/popups/contact`. */\nexport interface ContactBody {\n publicKey: string;\n userId: string;\n userAttributes: ContactAttributes;\n}\n\nexport interface ContactManagerOptions {\n storage: KeyValueStorage;\n publicKey: string;\n userId: string;\n /** Envío real (inyectable para tests): hace el `POST /sdk/popups/contact`. */\n post: (body: ContactBody) => Promise<void>;\n}\n\n/** Serialización estable (claves ordenadas) para el diff contra lo último enviado. */\nfunction stableSerialize(attrs: ContactAttributes): string {\n const sorted: ContactAttributes = {};\n for (const key of Object.keys(attrs).sort()) {\n sorted[key] = attrs[key];\n }\n return JSON.stringify(sorted);\n}\n\n/**\n * Gestiona los atributos de contact del usuario identificado (`userId` del init).\n * Solo envía cuando cambian respecto a lo último persistido en storage, para ahorrar\n * peticiones (el host puede llamar `setAttributes` en cada identificación sin miedo).\n */\nexport class ContactManager {\n private readonly storage: KeyValueStorage;\n private readonly publicKey: string;\n private readonly userId: string;\n private readonly post: (body: ContactBody) => Promise<void>;\n\n constructor(options: ContactManagerOptions) {\n this.storage = options.storage;\n this.publicKey = options.publicKey;\n this.userId = options.userId;\n this.post = options.post;\n }\n\n /**\n * Envía los atributos si cambiaron respecto a lo último enviado.\n * @returns `true` si se envió, `false` si no hubo cambios.\n */\n async setAttributes(attributes: ContactAttributes): Promise<boolean> {\n const serialized = stableSerialize(attributes);\n const last = this.storage.getItem(STORAGE_KEYS.contactAttributes);\n if (last === serialized) return false;\n\n await this.post({\n publicKey: this.publicKey,\n userId: this.userId,\n userAttributes: attributes,\n });\n this.storage.setItem(STORAGE_KEYS.contactAttributes, serialized);\n return true;\n }\n}\n","/**\n * Crash & error reporting (Stability #14–17) — captura de errores GESTIONADOS.\n *\n * - `reportError()`: API pública para el host (app viva) → emite `deepdots_app_crash` ya.\n * - handlers de `window` (errores no capturados): persisten a disco y se reenvían\n * en el siguiente arranque (el proceso puede morir antes del flush normal).\n *\n * El contexto de device/sesión se captura EN EL MOMENTO del crash y se guarda en el\n * record — no se lee del envelope en el replay (el replay puede ocurrir en otra\n * app_version/OS). Las dims OS/device en Web suelen ir vacías (se derivan del UA en backend).\n */\n\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\n\nexport type CrashSeverity = 'fatal' | 'error' | 'warning';\n\nexport interface CrashRecord {\n crashedAt: number;\n crashType: string;\n message: string;\n stack: string;\n fatal: boolean;\n handled: boolean;\n severity: CrashSeverity;\n sessionId: string | null;\n appVersion?: string;\n osVersion?: string;\n deviceModel?: string;\n context?: Record<string, string>;\n}\n\nexport interface ReportErrorOptions {\n severity?: CrashSeverity;\n handled?: boolean;\n context?: Record<string, unknown>;\n}\n\nexport interface DeviceSnapshot {\n appVersion?: string;\n osVersion?: string;\n deviceModel?: string;\n}\n\n/** Forma mínima de `global.ErrorUtils` de React Native. */\nexport interface ReactNativeErrorUtils {\n getGlobalHandler?(): ((error: unknown, isFatal?: boolean) => void) | undefined;\n setGlobalHandler(handler: (error: unknown, isFatal?: boolean) => void): void;\n}\n\nexport interface CrashReporterOptions {\n storage: KeyValueStorage;\n /** Emite un evento deepdots_app_crash AHORA (app viva). */\n emit: (params: Record<string, unknown>) => void;\n /** Snapshot de device en el momento del crash. */\n device: () => DeviceSnapshot;\n /** session_id en el momento del crash. */\n sessionId: () => string | null;\n now?: () => number;\n /** Kill-switch de consentimiento. */\n enabled?: () => boolean;\n}\n\nconst STACK_MAX = 8000;\nconst QUEUE_KEY = 'deepdots.crash.queue';\nconst MAX_QUEUED = 20;\n\n/** Convierte un CrashRecord en los params del evento `deepdots_app_crash` (omite undefined). */\nexport function crashRecordToParams(r: CrashRecord): Record<string, unknown> {\n const params: Record<string, unknown> = {\n crashed_at: r.crashedAt,\n crash_type: r.crashType,\n message: r.message,\n stack: r.stack,\n fatal: r.fatal,\n handled: r.handled,\n severity: r.severity,\n };\n if (r.sessionId) params.crashed_session_id = r.sessionId;\n if (r.appVersion) params.crashed_app_version = r.appVersion;\n if (r.osVersion) params.crashed_os_version = r.osVersion;\n if (r.deviceModel) params.crashed_device_model = r.deviceModel;\n if (r.context) {\n for (const [k, v] of Object.entries(r.context)) params[`ctx_${k}`] = v;\n }\n return params;\n}\n\nexport class CrashReporter {\n private options: CrashReporterOptions;\n private now: () => number;\n\n constructor(options: CrashReporterOptions) {\n this.options = options;\n this.now = options.now ?? (() => Date.now());\n }\n\n private isEnabled(): boolean {\n return this.options.enabled ? this.options.enabled() : true;\n }\n\n /** Construye un CrashRecord capturando contexto en el momento del crash. */\n private buildRecord(\n error: unknown,\n severity: CrashSeverity,\n handled: boolean,\n fatal: boolean,\n context?: Record<string, unknown>,\n ): CrashRecord {\n const err = error as { name?: string; message?: string; stack?: string };\n const isErr = error instanceof Error;\n const dev = this.options.device();\n const ctx = context\n ? Object.fromEntries(Object.entries(context).map(([k, v]) => [k, String(v)]))\n : undefined;\n return {\n crashedAt: this.now(),\n crashType: (isErr && err.name) || 'Error',\n message: isErr ? String(err.message ?? '') : String(error),\n stack: (isErr && typeof err.stack === 'string' ? err.stack : '').slice(0, STACK_MAX),\n fatal,\n handled,\n severity,\n sessionId: this.options.sessionId(),\n appVersion: dev.appVersion,\n osVersion: dev.osVersion,\n deviceModel: dev.deviceModel,\n context: ctx,\n };\n }\n\n /** API pública del host: reporta un error (app viva) → emite el evento ya. */\n reportError(error: unknown, options: ReportErrorOptions = {}): void {\n if (!this.isEnabled()) return;\n const severity = options.severity ?? 'error';\n const handled = options.handled ?? true;\n const fatal = severity === 'fatal';\n const record = this.buildRecord(error, severity, handled, fatal, options.context);\n this.options.emit(crashRecordToParams(record));\n }\n\n /** Captura un error no manejado (fatal) → persiste a disco para replay en el siguiente arranque. */\n captureUnhandled(error: unknown): void {\n this.persist(this.buildRecord(error, 'fatal', false, true));\n }\n\n /** Persiste un crash no capturado a disco para reenviarlo en el siguiente arranque. */\n private persist(record: CrashRecord): void {\n let queue: CrashRecord[] = [];\n try {\n const raw = this.options.storage.getItem(QUEUE_KEY);\n if (raw) queue = JSON.parse(raw) as CrashRecord[];\n if (!Array.isArray(queue)) queue = [];\n } catch {\n queue = [];\n }\n queue.push(record);\n if (queue.length > MAX_QUEUED) queue = queue.slice(queue.length - MAX_QUEUED);\n try {\n this.options.storage.setItem(QUEUE_KEY, JSON.stringify(queue));\n } catch {\n /* storage lleno / no disponible — se pierde el crash, no rompemos la app */\n }\n }\n\n /** Lee y vacía la cola de crashes pendientes (llamado en init para el replay). */\n drainPendingCrashes(): CrashRecord[] {\n let queue: CrashRecord[] = [];\n try {\n const raw = this.options.storage.getItem(QUEUE_KEY);\n if (raw) queue = JSON.parse(raw) as CrashRecord[];\n if (!Array.isArray(queue)) queue = [];\n } catch {\n queue = [];\n }\n if (queue.length) {\n try { this.options.storage.removeItem(QUEUE_KEY); } catch { /* noop */ }\n }\n return queue;\n }\n\n /** Instala los handlers globales de errores no capturados (no-op sin `window`). */\n install(): void {\n // En React Native `window` existe pero NO tiene addEventListener; comprobamos\n // la capacidad, no solo la existencia de `window`. En RN el host usa\n // installReactNative(ErrorUtils) para capturar los errores no manejados.\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') return;\n window.addEventListener('error', (e: ErrorEvent) => {\n if (!this.isEnabled()) return;\n this.captureUnhandled(e.error ?? e.message ?? 'error');\n });\n window.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => {\n if (!this.isEnabled()) return;\n this.captureUnhandled(e.reason ?? 'unhandledrejection');\n });\n }\n\n /** Engancha `global.ErrorUtils` de RN (no hay `window`): persiste y encadena al handler previo. */\n installReactNative(errorUtils: ReactNativeErrorUtils): void {\n const previous = errorUtils.getGlobalHandler?.();\n errorUtils.setGlobalHandler((error: unknown, isFatal?: boolean) => {\n if (this.isEnabled()) this.captureUnhandled(error);\n previous?.(error, isFatal);\n });\n }\n\n /** @internal Test seam: ejercita el camino de persistencia (window handlers no son testeables en vitest). */\n _persistForTest(error: unknown): void {\n this.captureUnhandled(error);\n }\n}\n","/**\n * Messaging (#18–22): tracking de notificaciones del host (push / in-app).\n * Un único evento reservado `deepdots_message` con un campo `stage` discriminador;\n * el funnel se correlaciona por `message_id` y se agrupa por `message_title`.\n */\n\nexport type MessageStage = 'delivered' | 'clicked' | 'converted';\n\n/** Canales válidos. Un mensaje tiene UN canal: se valida en runtime porque el host puede no ser TS. */\nexport const MESSAGE_CHANNELS = ['push', 'in_app'] as const;\nexport type MessageChannel = (typeof MESSAGE_CHANNELS)[number];\n\nexport interface TrackMessageOptions {\n /** Identificador del mensaje/notificación — correlaciona el funnel delivered→clicked→converted. */\n id: string;\n /** Título del mensaje — dimensión de agrupación de #18–22. */\n title: string;\n /** Canal de entrega. */\n channel: MessageChannel;\n /** Nombre de la campaña (opcional). */\n campaign?: string;\n /** Valor de conversión (opcional, típico en `stage: 'converted'`). */\n value?: number;\n currency?: string;\n /** Parámetros libres adicionales. */\n params?: Record<string, unknown>;\n}\n\n/** Construye los params del evento `deepdots_message` (omite opcionales ausentes). */\nexport function buildMessageParams(stage: MessageStage, o: TrackMessageOptions): Record<string, unknown> {\n const p: Record<string, unknown> = {\n stage,\n message_id: o.id,\n message_title: o.title,\n channel: o.channel,\n };\n if (o.campaign) p.campaign = o.campaign;\n if (o.value !== undefined) p.value = o.value;\n if (o.currency) p.currency = o.currency;\n return { ...p, ...(o.params ?? {}) };\n}\n\n/** Techo de `message_id` vigilados por sesión; los más antiguos se evictan (memoria acotada). */\nexport const MAX_TRACKED_MESSAGES = 500;\n\nexport type MessageRejectionReason = 'invalid_channel' | 'duplicate_stage' | 'channel_conflict';\n\nexport type MessageGuardVerdict =\n | { emit: true }\n | { emit: false; reason: MessageRejectionReason; detail: string };\n\n/**\n * Protege el funnel de Messaging de las formas imposibles que el host puede producir por error\n * (visto en producción: CTR > 100% por falta de `delivered` + el mismo `message_id` reportado en\n * dos canales). Tres reglas, todas dentro de la sesión (se reinicia en cada `init()`):\n *\n * 1. `channel` fuera de `MESSAGE_CHANNELS` → se descarta el evento.\n * 2. Un `(message_id, stage)` ya emitido → se emite una sola vez.\n * 3. Un `message_id` ya visto en un canal → se descartan los eventos de otro canal.\n *\n * Los eventos rechazados NO mutan el estado: un rechazo nunca \"consume\" el stage bueno.\n * Lo que el SDK no puede arreglar es la ausencia de `delivered`: eso lo instrumenta el host.\n */\nexport class MessageGuard {\n /** message_id → canal fijado + stages ya emitidos. El orden de inserción marca la eviction. */\n private seen = new Map<string, { channel: MessageChannel; stages: Set<MessageStage> }>();\n\n constructor(private maxTrackedMessages: number = MAX_TRACKED_MESSAGES) {}\n\n evaluate(stage: MessageStage, options: TrackMessageOptions): MessageGuardVerdict {\n const channel = options.channel;\n if (!(MESSAGE_CHANNELS as readonly string[]).includes(channel)) {\n return {\n emit: false,\n reason: 'invalid_channel',\n detail: `channel \"${channel}\" no válido (esperado ${MESSAGE_CHANNELS.join(' | ')})`,\n };\n }\n\n const entry = this.seen.get(options.id);\n if (!entry) {\n this.remember(options.id, channel, stage);\n return { emit: true };\n }\n if (entry.channel !== channel) {\n return {\n emit: false,\n reason: 'channel_conflict',\n detail: `message_id \"${options.id}\" ya se reportó en channel \"${entry.channel}\"; se descarta \"${channel}\"`,\n };\n }\n if (entry.stages.has(stage)) {\n return {\n emit: false,\n reason: 'duplicate_stage',\n detail: `stage \"${stage}\" ya emitido para message_id \"${options.id}\"`,\n };\n }\n entry.stages.add(stage);\n return { emit: true };\n }\n\n /** Olvida todo lo vigilado (nueva sesión). */\n reset(): void {\n this.seen.clear();\n }\n\n private remember(id: string, channel: MessageChannel, stage: MessageStage): void {\n this.seen.set(id, { channel, stages: new Set([stage]) });\n while (this.seen.size > this.maxTrackedMessages) {\n const oldest = this.seen.keys().next();\n if (oldest.done) break;\n this.seen.delete(oldest.value);\n }\n }\n}\n","import {\n DeepdotsConfig,\n TriggerConfig,\n DeepdotsEvent,\n DeepdotsEventType,\n EventListener,\n DeepdotsInitParams,\n DeepdotsLogger,\n PopupDefinition,\n PopupTriggerCondition,\n PopupActions,\n PopupTrigger,\n PopupTriggerConditionStatus,\n POPUP_TRIGGER_CONDITION_STATUSES, POPUPSESSIONSTATUS,\n PopupStyle,\n SessionEndReason,\n} from '../types';\n// renderPopup se carga de forma PEREZOSA (dynamic import) para no arrastrar\n// `@magicfeedback/native` (browser) ni el CSS al importar el SDK en entornos sin DOM (React Native).\nimport { setupTrigger } from '../triggers';\nimport { resolveEnvironment } from '../config/env';\nimport { PopupRenderer, createDefaultRenderer } from '../platform/renderer';\nimport { TrackingManager, createDefaultStorage, type KeyValueStorage } from '../tracking/tracking-manager';\nimport { AnalyticsManager, createDryRunSink, type AnalyticsEnvelope } from '../analytics/analytics-manager';\nimport { createFeedbackSink } from '../analytics/feedback-payload';\nimport { NavigationObserver } from '../tracking/navigation-observer';\nimport { collectDeviceInfo } from '../analytics/device-info';\nimport { readCachedGeo, writeCachedGeo, fetchGeo } from '../analytics/geo-info';\nimport { resolveLanguage } from '../analytics/language';\nimport { EngagementTracker } from '../analytics/engagement-tracker';\nimport { ContactManager, type ContactAttributes } from '../contact/contact-manager';\nimport { CrashReporter, crashRecordToParams, type ReportErrorOptions, type ReactNativeErrorUtils } from '../analytics/crash-reporter';\nimport { setLogger } from '../util/logger';\nimport { buildMessageParams, MessageGuard, type MessageStage, type TrackMessageOptions } from '../analytics/messaging';\n\nconst EXIT_QUEUE_STORAGE_KEY = '__deepdots_exit_popup_queue__';\nconst DAY_IN_MS = 24 * 60 * 60 * 1000;\nconst ANALYTICS_MAX_BATCH_SIZE = 20;\nconst ANALYTICS_FLUSH_INTERVAL_MS = 30_000;\n/** Namespace reservado para los eventos que emite el propio SDK (page_view, message, mini_service…). */\nconst RESERVED_EVENT_PREFIX = 'deepdots_';\n/** Prefijo que se antepone a los eventos custom del host para poder identificarlos frente a los reservados. */\nconst CUSTOM_EVENT_PREFIX = 'deepdots_event_';\n\ninterface DeferredExitPopup {\n id: string;\n surveyId: string;\n dueAt: number;\n sourceUrl: string;\n}\n\ninterface LegacyPopupTriggerCondition {\n answered?: boolean;\n cooldownDays?: number;\n}\n\ninterface PopupProgressState {\n status: PopupTriggerConditionStatus;\n timestamp: number;\n}\n\ntype NormalizedPopupDefinition = PopupDefinition & {\n legacyConditions?: LegacyPopupTriggerCondition[];\n};\n\n/**\n * Main class for managing survey popups\n */\nexport class DeepdotsPopups {\n private config: DeepdotsConfig | null = null;\n private listeners: Map<DeepdotsEventType, Set<EventListener>> = new Map();\n private triggers: TriggerConfig[] = [];\n private initialized = false;\n private renderer: PopupRenderer = createDefaultRenderer();\n /** Logger para el output de debug. Default `console`; el host puede inyectar el suyo en init(). */\n private logger: DeepdotsLogger = console;\n private popupContainer: HTMLElement | null = null; // deprecated: mantenido para compatibilidad interna\n private popupDefinitions: NormalizedPopupDefinition[] = [];\n private popupsLoaded = false;\n private pendingAutoLaunch = false;\n\n private answeredSurveys: Set<string> = new Set();\n private surveyProgress: Map<string, PopupProgressState> = new Map();\n private lastShown: Map<string, number> = new Map(); // popupId -> timestamp mostrado\n private surveyToPopupId: Map<string, string> = new Map(); // surveyId -> popupId\n private deferredExitTimers: number[] = [];\n\n private baseUrl: string = '';\n private env: 'production' | 'development' = 'production';\n\n /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */\n private tracking: TrackingManager | null = null;\n /** Capa de analytics (canal separado del feedback). Null hasta init(). */\n private analytics: AnalyticsManager | null = null;\n /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */\n private analyticsFeedbackSessionId: string | undefined = undefined;\n /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */\n private analyticsFlushTimer: ReturnType<typeof setInterval> | undefined = undefined;\n /** Crash & error reporting (#14–17). Null hasta init(). */\n private crashReporter: CrashReporter | null = null;\n /** Protecciones del funnel de Messaging (#18–22). Vigencia de sesión: se reinicia en init(). */\n private messageGuard = new MessageGuard();\n /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */\n private navObserver: NavigationObserver | null = null;\n /** Tiempo activo (engagement time, #8). */\n private engagement: EngagementTracker | null = null;\n /** Atributos internos del usuario identificado → POST /sdk/popups/contact. Null si no hay userId. */\n private contact: ContactManager | null = null;\n /** Storage resuelto en init() (inyectado por el host o localStorage). Reusado al cambiar de usuario. */\n private storage: KeyValueStorage | null = null;\n /** Marca si ya se inició la navegación manual (setScreen) — para RN sin History API. */\n private navStarted = false;\n /**\n * `true` entre `session_start` y `session_end`. Hace idempotente tanto la apertura como\n * el cierre (p. ej. `visibilitychange` + `pagehide` seguidos) y permite abrir una sesión\n * nueva al volver a foreground o al conceder el consentimiento más tarde.\n */\n private sessionOpen = false;\n /**\n * Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de\n * verdad para el context de analytics Y para la segmentación por idioma de los popups.\n */\n private language: string | undefined = undefined;\n\n /** Initialize the SDK with configuration */\n init(config: DeepdotsInitParams): void {\n this.logger = config.logger ?? console;\n setLogger(this.logger); // los módulos sin `this` (renderPopup, renderers) enrutan por aquí\n if (this.initialized) {\n // Un init() con OTRO userId es un CAMBIO DE USUARIO (login/logout del host): cierra\n // la sesión anterior y cambia la identidad en caliente. Reinicializar de verdad\n // duplicaría listeners, timers y la carga de popups.\n const nextUserId = config.userId || undefined;\n if (nextUserId !== this.config?.userId) {\n this.setUserId(nextUserId);\n return;\n }\n this.log('SDK already initialized');\n return;\n }\n\n const env = resolveEnvironment(config.nodeEnv);\n\n this.baseUrl = env.apiBaseUrl;\n this.env = config.nodeEnv || 'production';\n\n this.config = {\n apiKey: config.apiKey || undefined,\n debug: config.debug || false,\n userId: config.userId || undefined,\n } as DeepdotsConfig;\n\n // user_id lo gestiona el SDK (persistente); el session_id lo provee el backend\n // (respuesta de POST /sdk/popups) y se cachea — el SDK no genera ni expira sesiones.\n const storage = config.storage ?? createDefaultStorage();\n this.storage = storage;\n this.tracking = new TrackingManager({\n storage,\n clientUserId: this.config.userId,\n enabled: config.trackingEnabled ?? true,\n });\n\n // Contact: info interna del usuario que solo conoce el host (segmentación/targeting).\n // Solo trackeamos usuarios IDENTIFICADOS → se crea únicamente si hay userId del init.\n if (this.config.userId) {\n const userId = this.config.userId;\n const publicKey = this.config.apiKey ?? '';\n this.contact = new ContactManager({\n storage,\n publicKey,\n userId,\n post: (body) => this.postContact(body),\n });\n // Atributos de contact pasados en init: envío fire-and-forget (solo si cambiaron).\n if (config.contactAttributes) {\n void this.setContactAttributes(config.contactAttributes);\n }\n }\n this.log('tracking · user_id:', this.tracking.getUserId(), '· new_user:', this.tracking.isNewUser(), '· enabled:', this.tracking.isTrackingEnabled());\n\n // Analytics: se envía como Feedback a la integración (`POST /sdk/feedback`) si se\n // pasan claves en init.analytics; si no, queda en dry-run (solo console.log).\n const analyticsSink = config.analytics\n ? createFeedbackSink({\n baseUrl: this.baseUrl,\n keys: config.analytics,\n log: (...a) => this.log(...a),\n // sendBeacon: único transporte que sobrevive al cierre de la página.\n sendBeaconImpl:\n typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function'\n ? (url, body) => navigator.sendBeacon(url, body)\n : undefined,\n onSessionId: (id) => { this.analyticsFeedbackSessionId = id; },\n onSessionReset: () => { this.analyticsFeedbackSessionId = undefined; },\n })\n : createDryRunSink((...a) => this.logger.log(...a));\n const device = config.device ?? collectDeviceInfo(config.appVersion);\n this.language = resolveLanguage({\n explicit: config.language,\n navigator: typeof navigator !== 'undefined' ? navigator : undefined,\n intl: typeof Intl !== 'undefined' ? Intl : undefined,\n });\n this.messageGuard.reset();\n this.analytics = new AnalyticsManager({\n sink: analyticsSink,\n publicKey: config.analytics?.publicKey ?? this.config.apiKey,\n language: this.language,\n platform: config.platform ?? 'web',\n device,\n maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,\n onFlushNeeded: () => this.flushAnalytics(),\n });\n // Crash & error reporting (#14–17): captura errores no manejados (a disco, replay\n // en el siguiente arranque) y expone reportError() para el host (emite ya).\n this.crashReporter = new CrashReporter({\n storage,\n emit: (params) => this.track('deepdots_app_crash', params),\n device: () => ({ appVersion: device.app_version, osVersion: device.os_version, deviceModel: device.device_model }),\n sessionId: () => this.tracking?.getSessionId() ?? null,\n now: () => Date.now(),\n enabled: () => this.tracking?.isTrackingEnabled() ?? false,\n });\n if (this.tracking?.isTrackingEnabled()) {\n this.crashReporter.install();\n }\n // Geolocalización por IP: aplica el cache persistente de inmediato (sin gap de timing)\n // y refresca en background (cadena de proveedores + timeout), recacheando el resultado.\n const cachedGeo = readCachedGeo(storage, Date.now());\n if (cachedGeo) this.analytics.updateDevice(cachedGeo);\n fetchGeo()\n .then((geo) => {\n if (geo) {\n this.analytics?.updateDevice(geo);\n writeCachedGeo(storage, geo, Date.now());\n }\n })\n .catch(() => {});\n // Fase 2: navegación → eventos page_view por el canal de analytics.\n this.navObserver = new NavigationObserver();\n this.navObserver.onVisit((v) => this.track('deepdots_page_view', { screen: v.screen, duration_seconds: v.durationSeconds }));\n this.navObserver.install();\n // Engagement time (#8): cuenta tiempo activo en primer plano.\n this.engagement = new EngagementTracker();\n this.engagement.resume();\n this.setupAnalyticsFlush();\n // Marca de inicio de sesión (base para Crash-Free Users #14).\n this.openSession();\n // Drena SIEMPRE la cola (descarta los pendientes si tracking está off, spec §7);\n // solo reenvía como evento cuando el tracking está activo.\n const pendingCrashes = this.crashReporter.drainPendingCrashes();\n if (this.tracking?.isTrackingEnabled()) {\n for (const rec of pendingCrashes) {\n this.track('deepdots_app_crash', crashRecordToParams(rec));\n }\n }\n\n this.initialized = true;\n this.log('SDK initialized', this.config);\n if (this.renderer.init) this.renderer.init();\n this.setupPopupContainer();\n\n // Los popups SIEMPRE se reciben de la API (no se definen en init).\n this.fetchPopupsFromServer().then((defs) => {\n this.popupDefinitions = this.validatePopupDefinitions(defs);\n this.popupsLoaded = true;\n this.log('Popups loaded from API', this.popupDefinitions);\n this.configureTriggersFromDefinitions();\n this.processDeferredExitQueue();\n if (this.pendingAutoLaunch) {\n this.startTriggers();\n }\n });\n }\n\n /** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */\n getUserId(): string | null {\n return this.tracking?.getUserId() ?? null;\n }\n\n /** Session id de navegación actual. Null si tracking off. */\n getSessionId(): string | null {\n return this.tracking?.getSessionId() ?? null;\n }\n\n /** Activa/desactiva el tracking (identidad + sesión + analytics). Kill-switch del contrato §7bis. */\n setTrackingEnabled(enabled: boolean): void {\n // Al revocar el consentimiento se cierra la sesión ANTES de apagar el canal: si no, lo\n // acumulado se quedaría sin enviar y el registro abierto sin cerrar.\n if (!enabled) this.closeSession('tracking_disabled');\n this.tracking?.setTrackingEnabled(enabled);\n // Consentimiento concedido (ahora o más tarde que el init): abre sesión.\n if (enabled) this.openSession();\n this.log('tracking · setTrackingEnabled:', enabled, '· session_id:', this.tracking?.getSessionId() ?? null);\n }\n\n // ───────── Analytics (canal separado del feedback, vinculado por user_id) ─────────\n\n /** Registra un evento de analítica (modelo GA: nombre + params). No-op si tracking off. */\n track(name: string, params?: Record<string, unknown>): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n // Los eventos del SDK ya van en el namespace `deepdots_`; los custom del host se\n // prefijan con `deepdots_event_` para poder distinguirlos en analítica.\n const eventName = name.startsWith(RESERVED_EVENT_PREFIX) ? name : `${CUSTOM_EVENT_PREFIX}${name}`;\n this.analytics?.track(eventName, params);\n this.log('analytics · track:', eventName, params ?? {});\n }\n\n /** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */\n setUserAttributes(attributes: Record<string, string | number | boolean>): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.setUserAttributes(attributes);\n }\n\n /**\n * Registra/actualiza una métrica (valor medible) → `feedback.metrics` del body.\n * Persistente y sobrescribe por key. No-op si tracking off.\n */\n setMetric(key: string, value: string | number | boolean): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.setMetric(key, value);\n this.log('analytics · setMetric:', key, value);\n }\n\n /** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */\n reportError(error: unknown, options?: ReportErrorOptions): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.crashReporter?.reportError(error, options);\n }\n\n /** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */\n installReactNativeCrashHandler(errorUtils: ReactNativeErrorUtils): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.crashReporter?.installReactNative(errorUtils);\n }\n\n /**\n * Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el\n * Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del\n * init. Solo envía si los atributos cambiaron (diff en storage). No-op si tracking off o sin userId.\n * @returns `true` si se envió al backend, `false` si no hubo cambios o está deshabilitado.\n */\n async setContactAttributes(attributes: ContactAttributes): Promise<boolean> {\n if (!this.tracking?.isTrackingEnabled()) return false;\n if (!this.contact) return false;\n return this.contact.setAttributes(attributes);\n }\n\n /** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */\n enterMiniService(name: string, entryPointType?: string): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.enterMiniService(name, entryPointType);\n this.log('analytics · enterMiniService:', name, entryPointType ?? '');\n }\n\n /** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */\n exitMiniService(name: string): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.exitMiniService(name);\n this.log('analytics · exitMiniService:', name);\n }\n\n /** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */\n trackSearch(query: string, resultsCount: number, params?: Record<string, unknown>): void {\n this.track('deepdots_search', { query, results_count: resultsCount, has_results: resultsCount > 0, ...(params ?? {}) });\n }\n\n /** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */\n trackFindabilityFriction(frictionTopic: string, params?: Record<string, unknown>): void {\n this.track('deepdots_findability_friction', { friction_topic: frictionTopic, ...(params ?? {}) });\n }\n\n /** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */\n trackFunnelStep(funnel: string, step: string, taskId: string, params?: Record<string, unknown>): void {\n this.track('deepdots_funnel_step', { funnel, step, task_id: taskId, ...(params ?? {}) });\n }\n\n /**\n * Messaging (#18–22): registra una etapa del funnel de una notificación (push/in-app).\n * No-op si tracking off. El evento se descarta (con warning) si el `channel` no es válido,\n * si ese `(message_id, stage)` ya se emitió en la sesión, o si el `message_id` ya se reportó\n * en otro canal — ver `MessageGuard`.\n */\n trackMessage(stage: MessageStage, options: TrackMessageOptions): void {\n const verdict = this.messageGuard.evaluate(stage, options);\n if (!verdict.emit) {\n console.warn(`[DeepdotsPopups] trackMessage descartado (${verdict.reason}): ${verdict.detail}`);\n return;\n }\n this.track('deepdots_message', buildMessageParams(stage, options));\n }\n\n /**\n * Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation).\n * El host la llama en cada cambio de pantalla; emite `page_view` al salir de la anterior.\n */\n setScreen(name: string): void {\n if (!this.navObserver) return;\n if (!this.navStarted) {\n this.navObserver.begin(name);\n this.navStarted = true;\n } else {\n this.navObserver.visit(name);\n }\n }\n\n /** App a foreground (RN: AppState 'active'): reanuda el engagement time y abre sesión nueva. */\n onForeground(): void {\n this.engagement?.resume();\n this.openSession();\n }\n\n /**\n * App a background (RN: AppState 'background'): FIN DE SESIÓN. Cierra pantalla actual\n * (page_view), mini-services (mini_service_exit), emite engagement + `session_end` y\n * envía el último lote con `completed:true`.\n *\n * Es la única señal disponible en móvil: el kill de la app (swipe) NO da callback.\n */\n onBackground(): void {\n this.closeSession('background');\n this.engagement?.pause();\n }\n\n /**\n * Cierre EXPLÍCITO de la sesión por parte del host (logout, fin de flujo…): emite\n * `session_end` y envía el último lote con `completed:true`.\n */\n endSession(): void {\n this.closeSession('manual');\n }\n\n /**\n * Cambio de usuario (login / logout / cambio de cuenta). Cierra la sesión del usuario\n * anterior (`session_end` con `reason: 'user_change'` + `completed:true`), cambia la\n * identidad y abre una sesión nueva. Sin `userId` vuelve al id anónimo del SDK.\n *\n * Equivale a llamar a `init()` otra vez con otro `userId`.\n */\n setUserId(userId?: string): void {\n const next = userId || undefined;\n if (!this.initialized || !this.config || next === this.config.userId) return;\n\n this.closeSession('user_change');\n\n const enabled = this.tracking?.isTrackingEnabled() ?? true;\n const storage = this.storage ?? createDefaultStorage();\n this.config.userId = next;\n this.tracking = new TrackingManager({ storage, clientUserId: next, enabled });\n // El Contact solo existe para usuarios identificados.\n this.contact = next\n ? new ContactManager({\n storage,\n publicKey: this.config.apiKey ?? '',\n userId: next,\n post: (body) => this.postContact(body),\n })\n : null;\n // Atributos, métricas y protecciones de messaging pertenecían al usuario anterior.\n this.analytics?.resetUserScope();\n this.messageGuard.reset();\n\n this.log('tracking · user_change · user_id:', this.tracking.getUserId());\n this.openSession();\n }\n\n /**\n * Cierra la sesión actual: vuelca todo lo que quedaba abierto y envía el último lote\n * marcado con `completed:true`, para que el backend cierre el registro. El `sessionId`\n * cacheado se olvida → el lote siguiente abre un registro nuevo.\n *\n * Idempotente: dos cierres seguidos (p. ej. `visibilitychange` + `pagehide`) no duplican\n * el `session_end`.\n */\n private closeSession(reason: SessionEndReason): void {\n if (!this.sessionOpen) return;\n if (!this.tracking?.isTrackingEnabled()) return;\n this.sessionOpen = false;\n\n this.navObserver?.stop(); // cierra la pantalla actual → page_view\n this.navStarted = false;\n this.analytics?.exitAllMiniServices(); // → mini_service_exit con duración\n this.flushEngagement(); // → user_engagement con el tiempo activo\n this.track('deepdots_session_end', { reason });\n // `final` solo cuando el documento se está muriendo: ahí hace falta sendBeacon.\n this.flushAnalytics({ final: reason === 'page_hide', sessionEnd: true });\n // El session_id del canal de analytics y el de popups pertenecían a la sesión cerrada.\n this.analyticsFeedbackSessionId = undefined;\n this.tracking?.setSessionId(null);\n this.log('tracking · session_end:', reason);\n }\n\n /**\n * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente:\n * se llama en init(), al volver a foreground y al conceder el consentimiento.\n */\n private openSession(): void {\n if (this.sessionOpen) return;\n if (!this.tracking?.isTrackingEnabled()) return;\n this.sessionOpen = true;\n this.track('deepdots_session_start', {});\n }\n\n /** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */\n previewAnalytics(): AnalyticsEnvelope {\n return (\n this.analytics?.buildPayload(this.analyticsIdentity()) ?? {\n userId: null,\n sessionId: null,\n context: { platform: 'web', attributes: {} },\n events: [],\n }\n );\n }\n\n /**\n * Envía el lote acumulado de analytics y vacía el buffer.\n * `final: true` (cierre de página/app) cambia el transporte a `sendBeacon`, que sobrevive\n * al unload; un lote que falle por red o 5xx se re-encola para el siguiente flush.\n */\n flushAnalytics(options?: { final?: boolean; sessionEnd?: boolean }): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.flush(this.analyticsIdentity(), options);\n }\n\n /** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */\n private flushEngagement(): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n const ms = this.engagement?.consume() ?? 0;\n if (ms > 0) this.track('deepdots_user_engagement', { engagement_time_msec: ms });\n }\n\n private analyticsIdentity() {\n return {\n userId: this.tracking?.getUserId() ?? null,\n sessionId: this.tracking?.getSessionId() ?? null,\n };\n }\n\n /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */\n private setupAnalyticsFlush(): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\n // flush periódico mientras la app está en primer plano\n this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);\n // al cerrar la página: FIN DE SESIÓN (page_view + mini_service_exit + engagement +\n // session_end) y último lote con completed:true vía sendBeacon.\n window.addEventListener('pagehide', () => {\n clearInterval(this.analyticsFlushTimer);\n this.closeSession('page_hide');\n });\n // al ocultar/mostrar la pestaña: pausar/reanudar engagement y enviar lo acumulado\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.flushEngagement();\n this.engagement?.pause();\n this.flushAnalytics();\n } else if (document.visibilityState === 'visible') {\n this.engagement?.resume();\n }\n });\n }\n\n /** Enable auto-launch functionality with configured triggers */\n autoLaunch(): void {\n if (!this.initialized) {\n throw new Error('SDK not initialized. Call init() first.');\n }\n if (!this.popupsLoaded) {\n this.pendingAutoLaunch = true;\n this.log('Auto-launch deferred until popups are loaded');\n return;\n }\n this.startTriggers();\n }\n\n /** Inicia los triggers configurados */\n private startTriggers(): void {\n this.log('Auto-launch enabled');\n this.triggers.forEach((trigger) => setupTrigger(this, trigger));\n }\n\n /** Configure triggers for auto-launching popups (manual) */\n configureTriggers(triggers: TriggerConfig[]): void {\n if (!this.initialized) {\n throw new Error('SDK not initialized. Call init() first.');\n }\n this.triggers = triggers;\n this.log('Triggers configured', triggers);\n }\n\n /** Deriva triggers desde las definiciones de popup */\n private configureTriggersFromDefinitions(): void {\n const derived: TriggerConfig[] = [];\n this.debug('Validated popup definitions', this.popupDefinitions);\n\n this.popupDefinitions.forEach((def) => {\n def.triggers.forEach((trigger) => {\n const type = this.mapPopupTriggerType(trigger.type);\n if (!type) {\n this.debug('Unsupported trigger type', trigger.type);\n return;\n }\n const value = type === 'time' && typeof trigger.value === 'number'\n ? trigger.value * 1000\n : trigger.value;\n derived.push({ type, value, surveyId: def.surveyId, popupId: def.id });\n });\n });\n\n if (derived.length) {\n this.configureTriggers(derived);\n }\n }\n\n /** Lógica para evaluar condiciones antes de mostrar una encuesta */\n triggerSurvey(surveyId: string, popupId?: string): void {\n const def = this.findPopupDefinition(surveyId, popupId);\n if (!def) {\n this.debug('No popup definition for trigger', { surveyId, popupId });\n return;\n }\n if (!this.shouldShow(def)) {\n this.debug('Conditions prevented showing popup', def.id);\n return;\n }\n this.showDefinition(def);\n }\n\n /** Trigger popups configured with trigger.type = 'event' and matching trigger.value */\n public triggerEvent(eventName: string): void {\n if (!this.initialized) {\n throw new Error('SDK not initialized. Call init() first.');\n }\n const normalized = String(eventName || '').trim();\n if (!normalized) {\n this.debug('Ignoring empty event trigger name');\n return;\n }\n\n const candidates = this.popupDefinitions.filter((def) => {\n return def.triggers.some((trigger) => {\n const triggerValue = String(trigger.value ?? '').trim();\n return trigger.type === 'event' && triggerValue === normalized;\n });\n });\n\n if (!candidates.length) {\n this.debug('No event popup definitions found', { eventName: normalized });\n return;\n }\n\n const matched = candidates.find((def) => this.shouldShow(def));\n if (!matched) {\n this.debug('Event popup blocked by conditions/segments', { eventName: normalized });\n return;\n }\n\n this.showDefinition(matched);\n }\n\n private showDefinition(def: PopupDefinition): void {\n this.log('Showing popup (definition)', def);\n this.surveyToPopupId.set(def.surveyId, def.id);\n this.lastShown.set(def.id, Date.now());\n this.renderPopup(def.surveyId, def.productId, def.actions, def.style);\n this.emitEvent('popup_shown', def.surveyId, { popupId: def.id });\n }\n\n private shouldShow(def: NormalizedPopupDefinition, pathUrl?: string, skipPathCheck = false): boolean {\n if (!skipPathCheck && !this.matchesSegmentsPath(def, pathUrl)) {\n return false;\n }\n if (!this.matchesSegmentsLang(def)) {\n return false;\n }\n\n const cooldowns = def.cooldown || [];\n if (cooldowns.length && !cooldowns.every((cooldown) => this.evaluateCooldown(def, cooldown))) {\n return false;\n }\n\n const legacyConditions = def.legacyConditions || [];\n if (!legacyConditions.length) return true;\n return legacyConditions.every((condition) => this.evaluateLegacyCondition(def, condition));\n }\n\n private matchesSegmentsLang(def: PopupDefinition): boolean {\n const langs = def.segments?.lang;\n if (!langs || langs.length === 0) return true;\n\n // Mismo idioma que reporta el context de analytics: explícito > navigator.language\n // > Intl. ⚠️ NO leer `navigator.language` aquí: en React Native `navigator` existe\n // pero no tiene `language`, y `undefined.toLowerCase()` reventaba la evaluación\n // COMPLETA del trigger (no solo este popup).\n const currentLang = (this.language ?? '').toLowerCase();\n\n if (!currentLang) {\n this.debug('No language available for lang comparison', { popupId: def.id, langs });\n return true;\n }\n\n // Acepta si el idioma actual empieza con alguno de los configurados\n // ej: \"en-US\" coincide con segment \"en\". Ignora entradas no-string del backend.\n const matches = langs.some(\n (lang) => typeof lang === 'string' && currentLang.startsWith(lang.toLowerCase()),\n );\n if (!matches) {\n this.debug('Lang mismatch for popup', { popupId: def.id, langs, currentLang });\n }\n return matches;\n }\n\n private matchesSegmentsPath(def: PopupDefinition, pathUrl?: string): boolean {\n const paths = def.segments?.path;\n if (!paths || paths.length === 0) return true;\n if (typeof window === 'undefined' || !window.location) {\n this.debug('No window.location available for path comparison', { popupId: def.id, paths });\n return true;\n }\n\n const normalizedHref = this.normalizeUrl(pathUrl || window.location.href || '');\n const currentUrl = this.safeParseUrl(normalizedHref);\n const normalizedPath = this.normalizeUrl(currentUrl?.pathname || window.location.pathname || '');\n\n const matches = paths.some((rawCandidate) => {\n const candidate = this.normalizeUrl(rawCandidate);\n let match = false;\n if (candidate.startsWith('http://') || candidate.startsWith('https://')) {\n match = normalizedHref === candidate;\n } else if (candidate.startsWith('/')) {\n match = normalizedHref.includes(candidate);\n } else {\n match = normalizedPath === candidate;\n }\n this.debug('Path comparison', {\n popupId: def.id,\n candidate,\n currentPath: normalizedPath,\n currentHref: normalizedHref,\n match,\n });\n return match;\n });\n\n if (!matches) {\n this.debug('No path match for popup', { popupId: def.id, paths, currentPath: normalizedPath });\n }\n\n return matches;\n }\n\n private evaluateCooldown(def: PopupDefinition, condition: PopupTriggerCondition): boolean {\n switch (condition.answered) {\n case 'SHOWED':\n return this.hasCooldownElapsed(this.lastShown.get(def.id), condition.cooldownDays);\n case 'PARTIAL':\n case 'COMPLETED': {\n const progress = this.surveyProgress.get(def.surveyId);\n if (!progress || progress.status !== condition.answered) {\n return true;\n }\n return this.hasCooldownElapsed(progress.timestamp, condition.cooldownDays);\n }\n default:\n return true;\n }\n }\n\n private evaluateLegacyCondition(def: PopupDefinition, condition: LegacyPopupTriggerCondition): boolean {\n if (!condition.answered && this.answeredSurveys.has(def.surveyId)) {\n return false;\n }\n return this.hasCooldownElapsed(this.lastShown.get(def.id), condition.cooldownDays);\n }\n\n private hasCooldownElapsed(timestamp: number | undefined, cooldownDays?: number): boolean {\n if (!timestamp || !cooldownDays) {\n return true;\n }\n return Date.now() - timestamp >= cooldownDays * DAY_IN_MS;\n }\n\n /** Marcar encuesta como contestada externamente */\n public markSurveyAnswered(surveyId: string): void {\n this.answeredSurveys.add(surveyId);\n this.markSurveyProgress(surveyId, 'COMPLETED');\n }\n\n /** Queue an exit popup so it can render after navigation */\n public queueExitPopup(surveyId: string, delaySeconds: number, sourceUrl?: string, popupId?: string): void {\n const def = this.findPopupDefinition(surveyId, popupId);\n if (!def) {\n this.debug('No popup definition for exit trigger', { surveyId, popupId });\n return;\n }\n\n const originUrl = sourceUrl || (typeof window !== 'undefined' ? window.location.href : '');\n if (!originUrl) {\n this.debug('Exit popup skipped: missing source URL', { surveyId });\n return;\n }\n\n if (!this.shouldShow(def, originUrl)) {\n this.debug('Exit popup skipped by conditions/path', { popupId: def.id, sourceUrl: originUrl });\n return;\n }\n\n const safeDelayMs = Number.isFinite(delaySeconds) ? Math.max(0, delaySeconds * 1000) : 0;\n const deferred: DeferredExitPopup = {\n id: def.id,\n surveyId: def.surveyId,\n sourceUrl: this.normalizeUrl(originUrl),\n dueAt: Date.now() + safeDelayMs,\n };\n\n const queue = this.getDeferredExitQueue().filter((item) => !(item.id === deferred.id && item.sourceUrl === deferred.sourceUrl));\n queue.push(deferred);\n this.setDeferredExitQueue(queue);\n this.scheduleDeferredExitPopup(deferred);\n this.debug('Exit popup queued', deferred);\n }\n\n private processDeferredExitQueue(): void {\n const queue = this.getDeferredExitQueue();\n if (!queue.length) return;\n\n const pending: DeferredExitPopup[] = [];\n const now = Date.now();\n queue.forEach((item) => {\n const def = this.popupDefinitions.find((popup) => popup.id === item.id && popup.surveyId === item.surveyId);\n if (!def) {\n return;\n }\n if (item.dueAt <= now) {\n this.tryShowDeferredExitPopup(item);\n return;\n }\n pending.push(item);\n this.scheduleDeferredExitPopup(item);\n });\n this.setDeferredExitQueue(pending);\n }\n\n private scheduleDeferredExitPopup(item: DeferredExitPopup): void {\n if (typeof window === 'undefined') return;\n const delay = Math.max(0, item.dueAt - Date.now());\n const timer = window.setTimeout(() => this.tryShowDeferredExitPopup(item), delay);\n this.deferredExitTimers.push(timer);\n }\n\n private tryShowDeferredExitPopup(item: DeferredExitPopup): void {\n const def = this.popupDefinitions.find((popup) => popup.id === item.id && popup.surveyId === item.surveyId);\n if (!def) {\n this.removeDeferredExitPopup(item);\n return;\n }\n\n const currentUrl = typeof window !== 'undefined' ? this.normalizeUrl(window.location.href || '') : '';\n if (!currentUrl || currentUrl === this.normalizeUrl(item.sourceUrl)) {\n this.removeDeferredExitPopup(item);\n this.debug('Exit popup dropped because route did not change', item);\n return;\n }\n\n if (!this.shouldShow(def, undefined, true)) {\n this.removeDeferredExitPopup(item);\n this.debug('Exit popup skipped at render-time conditions', item);\n return;\n }\n\n this.showDefinition(def);\n this.removeDeferredExitPopup(item);\n }\n\n private findPopupDefinition(surveyId: string, popupId?: string): NormalizedPopupDefinition | undefined {\n if (popupId) {\n const byId = this.popupDefinitions.find((popup) => popup.id === popupId);\n if (byId) return byId;\n }\n return this.popupDefinitions.find((popup) => popup.surveyId === surveyId);\n }\n\n private removeDeferredExitPopup(item: DeferredExitPopup): void {\n const queue = this.getDeferredExitQueue().filter((entry) => !(entry.id === item.id && entry.sourceUrl === item.sourceUrl));\n this.setDeferredExitQueue(queue);\n }\n\n private getDeferredExitQueue(): DeferredExitPopup[] {\n if (typeof window === 'undefined') return [];\n try {\n const raw = window.sessionStorage.getItem(EXIT_QUEUE_STORAGE_KEY);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed\n .filter((entry: unknown): entry is DeferredExitPopup => {\n if (typeof entry !== 'object' || entry === null) return false;\n const item = entry as Partial<DeferredExitPopup>;\n return typeof item.id === 'string'\n && typeof item.surveyId === 'string'\n && typeof item.sourceUrl === 'string'\n && typeof item.dueAt === 'number'\n && Number.isFinite(item.dueAt);\n })\n .map((item) => ({\n ...item,\n sourceUrl: this.normalizeUrl(item.sourceUrl),\n }));\n } catch {\n return [];\n }\n }\n\n private setDeferredExitQueue(queue: DeferredExitPopup[]): void {\n if (typeof window === 'undefined') return;\n try {\n if (!queue.length) {\n window.sessionStorage.removeItem(EXIT_QUEUE_STORAGE_KEY);\n return;\n }\n window.sessionStorage.setItem(EXIT_QUEUE_STORAGE_KEY, JSON.stringify(queue));\n } catch {\n // Ignore storage errors in host environments that block storage access.\n }\n }\n\n /** Fetch al servidor para obtener popups */\n private async fetchPopupsFromServer(): Promise<unknown[]> {\n const apiKey = this.config?.apiKey;\n const baseUrl = this.baseUrl;\n const userId = this.config?.userId;\n if (!apiKey || !baseUrl) {\n this.log('Missing apiKey or baseUrl. Skipping popups fetch.');\n return [];\n }\n const filter = userId ? { where: { userId } } : null;\n const query = filter ? `?filter=${encodeURIComponent(JSON.stringify(filter))}` : '';\n const endpoint = `${baseUrl}/sdk/${encodeURIComponent(apiKey)}/popups${query}`;\n try {\n const response = await fetch(endpoint);\n if (!response.ok) {\n this.log('Failed to fetch popups', response.status, response.statusText);\n return [];\n }\n const raw = await response.text();\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n this.log('Unexpected popups payload', parsed);\n return [];\n }\n this.debug('Fetched popups payload', parsed);\n return parsed;\n } catch (error) {\n this.log('Error fetching popups', error);\n return [];\n }\n }\n\n /** Envía los atributos de contact a la API (`POST /sdk/popups/contact`). */\n private async postContact(body: { publicKey: string; userId: string; userAttributes: ContactAttributes }): Promise<void> {\n const baseUrl = this.baseUrl;\n if (!baseUrl) return;\n const endpoint = `${baseUrl}/sdk/popups/contact`;\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n this.log('Failed to post contact', response.status, response.statusText);\n return;\n }\n this.log('contact · sent attributes for user:', body.userId);\n } catch (error) {\n this.log('Error posting contact', error);\n }\n }\n\n /** Notifica eventos de popup a la API */\n private async postPopupEvent(status: POPUPSESSIONSTATUS, popupId: string, userId?: string): Promise<void> {\n const apiKey = this.config?.apiKey;\n const baseUrl = this.baseUrl;\n if (!apiKey || !baseUrl) {\n return;\n }\n const endpoint = `${baseUrl}/sdk/popups`;\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n publicKey: apiKey,\n status,\n popupId,\n userId: userId || undefined,\n }),\n });\n if (!response.ok) {\n this.log('Failed to post popup event', response.status, response.statusText);\n return;\n }\n // Contrato Fase 1 §6: el backend devuelve el sessionId; lo cacheamos para\n // inyectarlo en las respuestas de survey (lo cose por user_id).\n try {\n const data = await response.json();\n if (data && typeof data.sessionId === 'string') {\n this.tracking?.setSessionId(data.sessionId);\n this.log('tracking · session_id from backend:', data.sessionId);\n }\n } catch {\n // respuesta sin cuerpo JSON: nada que cachear\n }\n } catch (error) {\n this.log('Error posting popup event', error);\n }\n }\n\n /** Add an event listener */\n on(eventType: DeepdotsEventType, listener: EventListener): void {\n if (!this.listeners.has(eventType)) {\n this.listeners.set(eventType, new Set());\n }\n this.listeners.get(eventType)!.add(listener);\n }\n\n /** Remove an event listener */\n off(eventType: DeepdotsEventType, listener: EventListener): void {\n const listeners = this.listeners.get(eventType);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n\n /** Setup popup container element */\n private setupPopupContainer(): void {\n if (typeof document === 'undefined') {\n return;\n }\n this.popupContainer = document.createElement('div');\n this.popupContainer.id = 'deepdots-popup-container';\n this.popupContainer.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: none;\n z-index: 999999;\n background: rgba(0, 0, 0, 0.5);\n justify-content: center;\n align-items: center;\n `;\n document.body.appendChild(this.popupContainer);\n }\n\n /** Render the popup UI */\n private renderPopup(surveyId: string, productId: string, actions?: PopupActions, style?: PopupStyle): void {\n const userId = this.tracking?.getUserId() ?? this.config?.userId;\n const sessionId = this.tracking?.getSessionId() ?? undefined;\n const miniService = this.analytics?.getMiniService() ?? undefined;\n const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;\n this.log('tracking · survey identity →', { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });\n if (this.renderer) {\n this.renderer.show(\n surveyId,\n productId,\n actions,\n (type, id, payload) => this.emitEvent(type, id, payload),\n () => this.hidePopup(),\n this.env,\n userId,\n style,\n sessionId,\n miniService,\n analyticsFeedbackSessionId,\n );\n return;\n }\n\n if (!this.popupContainer) return;\n const container = this.popupContainer;\n void import('../ui/renderPopup').then(({ renderPopup }) => {\n renderPopup(\n container,\n surveyId,\n productId,\n actions,\n (type, id, payload) => this.emitEvent(type, id, payload),\n () => this.hidePopup(),\n this.env,\n userId,\n style,\n sessionId,\n miniService,\n analyticsFeedbackSessionId,\n );\n });\n }\n\n /** Hide the popup */\n private hidePopup(): void {\n if (this.renderer) {\n this.renderer.hide();\n return;\n }\n if (this.popupContainer) {\n this.popupContainer.style.display = 'none';\n this.popupContainer.innerHTML = '';\n }\n }\n\n /** Emit an event */\n private emitEvent(type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>): void {\n const event: DeepdotsEvent = { type, surveyId, timestamp: Date.now(), data };\n this.log('Event emitted', event);\n\n const isPartialClick = type === 'popup_clicked' && data?.action === 'partial';\n if (isPartialClick) {\n this.markSurveyProgress(surveyId, 'PARTIAL');\n }\n if (type === 'survey_completed') {\n this.markSurveyAnswered(surveyId);\n }\n if (type === 'popup_shown' || type === 'survey_completed' || isPartialClick) {\n const popupIdFromData = data?.popupId as string | undefined;\n const popupId = popupIdFromData || this.surveyToPopupId.get(surveyId);\n if (popupId) {\n const userIdFromData = data?.userId as string | undefined;\n const status = type === 'popup_shown'\n ? POPUPSESSIONSTATUS.SHOWED\n : type === 'survey_completed'\n ? POPUPSESSIONSTATUS.COMPLETED\n : POPUPSESSIONSTATUS.PARTIAL;\n void this.postPopupEvent(status, popupId, userIdFromData || this.tracking?.getUserId() || this.config?.userId);\n } else {\n this.debug('No popupId available to post event', { type, surveyId });\n }\n }\n\n const listeners = this.listeners.get(type);\n if (listeners) {\n listeners.forEach((listener) => {\n try {\n listener(event);\n } catch (error) {\n if (this.logger.error) this.logger.error('Error in event listener:', error);\n else this.logger.log('Error in event listener:', error);\n }\n });\n }\n }\n\n /** Log debug messages */\n private log(...args: unknown[]): void {\n if (this.config?.debug) {\n this.logger.log('[DeepdotsPopups]', ...args);\n }\n }\n\n /** External debug method for triggers */\n public debug(...args: unknown[]): void {\n this.log(...args);\n }\n\n /** Set a custom renderer */\n public setRenderer(renderer: PopupRenderer): void {\n this.renderer = renderer;\n if (this.initialized && this.renderer.init) {\n this.renderer.init();\n }\n }\n\n private normalizeUrl(value: string): string {\n if (!value) return '';\n const withoutIndex = value.replace(/\\/index\\.html(?=($|[?#]))/i, '');\n return withoutIndex.length > 1 && withoutIndex.endsWith('/') ? withoutIndex.slice(0, -1) : withoutIndex;\n }\n\n private safeParseUrl(url: string): URL | null {\n if (!url) return null;\n try {\n return new URL(url, typeof window !== 'undefined' ? window.location.href : undefined);\n } catch {\n return null;\n }\n }\n\n private mapPopupTriggerType(type: PopupTrigger['type']): TriggerConfig['type'] | null {\n switch (type) {\n case 'time_on_page':\n return 'time';\n case 'scroll':\n return 'scroll';\n case 'exit':\n return 'exit';\n case 'click':\n return 'click';\n case 'event':\n return 'event';\n default:\n return null;\n }\n }\n\n private markSurveyProgress(surveyId: string, status: PopupTriggerConditionStatus): void {\n const current = this.surveyProgress.get(surveyId);\n if (current?.status === 'COMPLETED' && status !== 'COMPLETED') {\n return;\n }\n this.surveyProgress.set(surveyId, { status, timestamp: Date.now() });\n }\n\n private isPopupDefinition(value: unknown): value is NormalizedPopupDefinition {\n if (typeof value !== 'object' || value === null) return false;\n const popup = value as Partial<NormalizedPopupDefinition>;\n\n return typeof popup.id === 'string'\n && typeof popup.title === 'string'\n && typeof popup.message === 'string'\n && typeof popup.surveyId === 'string'\n && typeof popup.productId === 'string'\n && Array.isArray(popup.triggers)\n && popup.triggers.length > 0\n && popup.triggers.every((trigger) => this.isPopupTrigger(trigger))\n && (!popup.cooldown || popup.cooldown.every((condition) => this.isPopupTriggerCondition(condition)));\n }\n\n private isPopupTrigger(value: unknown): value is PopupTrigger {\n if (typeof value !== 'object' || value === null) return false;\n const trigger = value as Partial<PopupTrigger>;\n return this.isPopupTriggerType(trigger.type)\n && (typeof trigger.value === 'number' || typeof trigger.value === 'string');\n }\n\n private isPopupTriggerType(value: unknown): value is PopupTrigger['type'] {\n return value === 'time_on_page'\n || value === 'scroll'\n || value === 'exit'\n || value === 'click'\n || value === 'event';\n }\n\n private isPopupTriggerCondition(value: unknown): value is PopupTriggerCondition {\n if (typeof value !== 'object' || value === null) return false;\n const condition = value as Partial<PopupTriggerCondition>;\n return POPUP_TRIGGER_CONDITION_STATUSES.includes(condition.answered as PopupTriggerConditionStatus)\n && typeof condition.cooldownDays === 'number'\n && Number.isFinite(condition.cooldownDays)\n && condition.cooldownDays >= 0;\n }\n\n private isLegacyPopupTriggerCondition(value: unknown): value is LegacyPopupTriggerCondition {\n if (typeof value !== 'object' || value === null) return false;\n const condition = value as Partial<LegacyPopupTriggerCondition>;\n if (condition.answered !== undefined && typeof condition.answered !== 'boolean') {\n return false;\n }\n if (condition.cooldownDays !== undefined) {\n return typeof condition.cooldownDays === 'number'\n && Number.isFinite(condition.cooldownDays)\n && condition.cooldownDays >= 0;\n }\n return true;\n }\n\n private normalizeTriggers(value: unknown): PopupTrigger[] {\n const items = Array.isArray(value) ? value : value ? [value] : [];\n return items\n .filter((trigger): trigger is PopupTrigger => this.isPopupTrigger(trigger))\n .map((trigger) => ({\n type: trigger.type,\n value: trigger.value,\n }));\n }\n\n private normalizeCooldown(value: unknown): PopupTriggerCondition[] {\n if (!Array.isArray(value)) return [];\n return value.filter((condition): condition is PopupTriggerCondition => this.isPopupTriggerCondition(condition));\n }\n\n private normalizeLegacyConditions(value: unknown): LegacyPopupTriggerCondition[] {\n if (!Array.isArray(value)) return [];\n return value.filter((condition): condition is LegacyPopupTriggerCondition => this.isLegacyPopupTriggerCondition(condition));\n }\n\n private normalizePopupDefinition(def: unknown): NormalizedPopupDefinition | null {\n if (typeof def !== 'object' || def === null) return null;\n const raw = def as Partial<PopupDefinition> & {\n trigger?: PopupTrigger | PopupTrigger[];\n triggers?: PopupTrigger | PopupTrigger[];\n conditions?: LegacyPopupTriggerCondition[];\n cooldown?: PopupTriggerCondition[];\n };\n\n const rawTriggers = raw.triggers ?? raw.trigger;\n const triggers = this.normalizeTriggers(rawTriggers);\n if (!triggers.length) return null;\n\n const rawTriggerList = Array.isArray(rawTriggers) ? rawTriggers : rawTriggers ? [rawTriggers] : [];\n const legacyConditions = [\n ...this.normalizeLegacyConditions(raw.conditions),\n ...rawTriggerList.flatMap((trigger) => this.normalizeLegacyConditions((trigger as { condition?: unknown }).condition)),\n ];\n const cooldown = this.normalizeCooldown(raw.cooldown);\n\n return {\n ...(raw as PopupDefinition),\n triggers,\n cooldown,\n legacyConditions,\n };\n }\n\n private validatePopupDefinitions(defs: unknown[]): NormalizedPopupDefinition[] {\n return defs\n .map((def) => this.normalizePopupDefinition(def))\n .filter((def): def is NormalizedPopupDefinition => !!def && this.isPopupDefinition(def));\n }\n}\n","/**\n * Auto-wiring de React Native: concentra TODA la configuración en el SDK para que el\n * host solo tenga que envolver su app en un `<DeepdotsProvider config={...}>`.\n *\n * No importa React ni librerías nativas: el host (o el Provider) le pasa los módulos\n * nativos que tenga instalados (MMKV, device-info, AppState). Todo es opcional y degrada\n * con elegancia (sin MMKV → in-memory; sin device-info → sin device info).\n */\nimport type { DeepdotsPopups } from '../core/deepdots-popups';\nimport type { DeepdotsInitParams } from '../types';\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\nimport type { DeviceInfo } from '../analytics/device-info';\nimport type { PopupRenderer } from '../platform/renderer';\nimport type { ReactNativeErrorUtils } from '../analytics/crash-reporter';\n\n/** Forma mínima de `react-native-mmkv` (instancia MMKV). */\nexport interface MmkvLike {\n getString(key: string): string | undefined | null;\n set(key: string, value: string): void;\n delete(key: string): void;\n}\n\n/** Forma mínima de `react-native-device-info`. */\nexport interface DeviceInfoLike {\n isTablet?: () => boolean;\n getSystemVersion?: () => string;\n getDeviceId?: () => string;\n getVersion?: () => string;\n}\n\n/** Forma mínima de `AppState` de react-native. */\nexport interface AppStateLike {\n addEventListener(type: 'change', listener: (state: string) => void): { remove: () => void } | void;\n}\n\nexport interface ReactNativeSetupDeps {\n mmkv?: MmkvLike | null;\n deviceInfo?: DeviceInfoLike | null;\n appState?: AppStateLike | null;\n platform?: 'web' | 'android' | 'ios';\n renderer?: PopupRenderer;\n /** `global.ErrorUtils` de RN (para capturar errores JS no manejados). Default: `globalThis.ErrorUtils`. */\n errorUtils?: ReactNativeErrorUtils | null;\n}\n\n/** Adaptador KeyValueStorage (síncrono) sobre una instancia de MMKV. */\nexport function mmkvStorage(mmkv: MmkvLike): KeyValueStorage {\n return {\n getItem: (k) => mmkv.getString(k) ?? null,\n setItem: (k, v) => mmkv.set(k, v),\n removeItem: (k) => mmkv.delete(k),\n };\n}\n\n/** Recoge device info desde react-native-device-info (Technology #11–13). */\nexport function collectRnDevice(d: DeviceInfoLike): DeviceInfo {\n return {\n device_type: d.isTablet?.() ? 'tablet' : 'mobile',\n os_version: d.getSystemVersion?.(),\n device_model: d.getDeviceId?.(),\n app_version: d.getVersion?.(),\n };\n}\n\n/**\n * Configura el SDK para React Native en una sola llamada: storage persistente (MMKV),\n * device info, platform, init y lifecycle (AppState → onForeground/onBackground).\n * Devuelve una función de limpieza (quita el listener de AppState).\n */\nexport function setupReactNative(\n sdk: DeepdotsPopups,\n config: DeepdotsInitParams,\n deps: ReactNativeSetupDeps = {},\n): () => void {\n if (deps.renderer) sdk.setRenderer(deps.renderer);\n\n const storage = deps.mmkv ? mmkvStorage(deps.mmkv) : config.storage;\n const device = deps.deviceInfo ? collectRnDevice(deps.deviceInfo) : config.device;\n const platform = deps.platform ?? config.platform;\n\n sdk.init({ ...config, storage, device, platform });\n\n // Captura de errores JS no manejados en RN (no hay `window`): usa el ErrorUtils inyectado\n // o el global del runtime RN. Degrada si no existe.\n const errorUtils =\n deps.errorUtils ?? (globalThis as { ErrorUtils?: ReactNativeErrorUtils }).ErrorUtils;\n if (errorUtils && typeof errorUtils.setGlobalHandler === 'function') {\n sdk.installReactNativeCrashHandler(errorUtils);\n }\n\n let subscription: { remove: () => void } | void;\n if (deps.appState?.addEventListener) {\n subscription = deps.appState.addEventListener('change', (state) => {\n if (state === 'active') sdk.onForeground();\n // Solo 'background' es fin de sesión. En iOS 'inactive' es transitorio (llamada\n // entrante, app switcher) y cerrar sesión ahí partiría la sesión en dos: se envía\n // lo acumulado, por si el SO mata la app, pero sin cerrar el registro.\n else if (state === 'background') sdk.onBackground();\n else sdk.flushAnalytics();\n });\n }\n\n return () => {\n if (subscription && typeof subscription.remove === 'function') subscription.remove();\n };\n}\n"],"mappings":"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","names":["sdk"]}
1
+ {"version":3,"sources":["../src/react-native/index.tsx","../src/types/index.ts","../src/triggers/index.ts","../src/config/env.ts","../src/ui/surveyHtml.ts","../src/platform/react-native-renderer.ts","../src/platform/renderer.ts","../src/analytics/analytics-manager.ts","../src/analytics/feedback-payload.ts","../src/tracking/navigation-observer.ts","../src/analytics/device-info.ts","../src/analytics/geo-info.ts","../src/analytics/language.ts","../src/analytics/engagement-tracker.ts","../src/contact/contact-manager.ts","../src/analytics/crash-reporter.ts","../src/analytics/messaging.ts","../src/core/deepdots-popups.ts","../src/react-native/setup.ts"],"sourcesContent":["/**\n * Entry de React Native: `@magicfeedback/popup-sdk/react-native`.\n *\n * El cliente solo envuelve su app:\n * <DeepdotsProvider config={{ apiKey, nodeEnv }}>...</DeepdotsProvider>\n * y el SDK hace TODO (storage persistente, device info, platform, lifecycle/engagement,\n * y render de surveys en WebView). Sin pegar nada en su proyecto.\n *\n * Peer deps: react, react-native, react-native-webview. Opcionales (auto-detectados):\n * react-native-mmkv (persistencia), react-native-device-info (device info).\n */\nimport React, { createContext, useContext, useEffect, useRef, useState } from 'react';\nimport { AppState, Modal, Platform, View } from 'react-native';\nimport { WebView } from 'react-native-webview';\n\nimport { DeepdotsPopups } from '../core/deepdots-popups';\nimport { ReactNativePopupRenderer, type ReactNativeSurveyPayload } from '../platform/react-native-renderer';\nimport { setupReactNative } from './setup';\nimport type { DeepdotsInitParams } from '../types';\n\nconst sdk = new DeepdotsPopups();\nconst DeepdotsContext = createContext<DeepdotsPopups>(sdk);\n\n/** Acceso al SDK desde cualquier componente: `const dd = useDeepdots()`. */\nexport const useDeepdots = (): DeepdotsPopups => useContext(DeepdotsContext);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction optionalRequire(name: string): any {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require(name);\n } catch {\n return null;\n }\n}\n\nexport interface DeepdotsProviderProps {\n config: DeepdotsInitParams;\n children?: React.ReactNode;\n}\n\nexport function DeepdotsProvider({ config, children }: DeepdotsProviderProps) {\n const [survey, setSurvey] = useState<ReactNativeSurveyPayload | null>(null);\n const rendererRef = useRef<ReactNativePopupRenderer | null>(null);\n\n useEffect(() => {\n const renderer = new ReactNativePopupRenderer({\n onShow: (p) => setSurvey(p),\n onHide: () => setSurvey(null),\n });\n rendererRef.current = renderer;\n\n const MmkvMod = optionalRequire('react-native-mmkv');\n const mmkv = MmkvMod?.MMKV ? new MmkvMod.MMKV({ id: 'deepdots-sdk' }) : null;\n const deviceInfo = optionalRequire('react-native-device-info')?.default ?? null;\n\n const dispose = setupReactNative(sdk, config, {\n mmkv,\n deviceInfo,\n appState: AppState,\n platform: Platform.OS === 'ios' ? 'ios' : 'android',\n renderer,\n });\n return dispose;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <DeepdotsContext.Provider value={sdk}>\n {children}\n {survey ? (\n <Modal visible transparent animationType=\"slide\">\n <View style={{ flex: 1 }}>\n <WebView\n style={{ flex: 1 }}\n originWhitelist={['*']}\n javaScriptEnabled\n source={{ html: survey.html, baseUrl: 'https://sdk.deepdots.com/' }}\n onMessage={(e: { nativeEvent: { data: string } }) =>\n rendererRef.current?.handleMessage(e.nativeEvent.data)\n }\n />\n </View>\n </Modal>\n ) : null}\n </DeepdotsContext.Provider>\n );\n}\n\nexport { setupReactNative } from './setup';\nexport { ReactNativePopupRenderer } from '../platform/react-native-renderer';\n","/**\n * Configuration options for initializing the DeepdotsPopups SDK\n */\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\nimport type { DeviceInfo } from '../analytics/device-info';\nimport type { AnalyticsKeys } from '../analytics/feedback-payload';\n\n/**\n * Custom logger to receive the SDK's debug output instead of the console.\n * Only `log` is required; `warn`/`error`/`info` fall back to `log` when omitted.\n * Lets the host pipe debug output to a file, Firebase, etc. `console` satisfies this shape.\n */\nexport interface DeepdotsLogger {\n log: (...args: unknown[]) => void;\n warn?: (...args: unknown[]) => void;\n error?: (...args: unknown[]) => void;\n info?: (...args: unknown[]) => void;\n}\n\nexport interface DeepdotsInitParams {\n /** API key for authentication */\n apiKey?: string;\n /** Node environment: 'development' or 'production' */\n nodeEnv?: 'development' | 'production';\n /** Enable debug logging */\n debug?: boolean;\n /** Custom logger for debug output. When set (and `debug` is true), the SDK routes its logs here instead of `console`. Default: `console`. */\n logger?: DeepdotsLogger;\n /** Optional user id to send with popup events */\n userId?: string;\n /** Starts tracking enabled (default) or disabled (e.g. until consent is granted). Equivalent to calling `setTrackingEnabled` after init. */\n trackingEnabled?: boolean;\n /** Host app version (for analytics device info / Technology #11). */\n appVersion?: string;\n /** Injectable persistent storage (RN: adapter over AsyncStorage). If missing, uses localStorage/in-memory. */\n storage?: KeyValueStorage;\n /** Platform used in the analytics envelope. Default is 'web'; in RN pass 'android'/'ios'. */\n platform?: 'web' | 'android' | 'ios';\n /**\n * BCP-47 language tag (e.g. 'es-ES') used for the analytics context AND for popup\n * language segmentation (`segments.lang`). If omitted, the SDK auto-detects:\n * `navigator.language` on web, or the `Intl` locale as a fallback (works in\n * React Native with Hermes, where `navigator.language` is absent). If no source\n * resolves a language, popups with `segments.lang` cannot be filtered and are shown.\n */\n language?: string;\n /** Injectable device info (RN: from react-native-device-info). If missing, it is derived from the browser. */\n device?: DeviceInfo;\n /**\n * Analytics channel keys (`POST /sdk/feedback`). If provided, analytics is SENT\n * to the configured integration; if missing, it stays in dry-run mode (console.log only).\n */\n analytics?: AnalyticsKeys;\n /**\n * Internal user attributes known only by the host (language, age, plan, ...), sent\n * to the backend Contact for segmentation/targeting. Requires `userId`. Equivalent to calling\n * `setContactAttributes` after init (sends only when values changed from the last sync).\n */\n contactAttributes?: Record<string, string | number | boolean>;\n\n}\nexport interface DeepdotsConfig {\n /** API key for authentication */\n apiKey?: string;\n /** Enable debug logging */\n debug?: boolean;\n /** Optional user id to send with popup events */\n userId?: string;\n}\n\n/**\n * Options for configuring survey triggers\n */\nexport interface TriggerConfig {\n /** Trigger type: 'time' (delay), 'scroll' (scroll percentage), 'exit' (route exit), 'click' (element id), 'event' (host event) */\n type: 'time' | 'scroll' | 'exit' | 'click' | 'event';\n /** Value for the trigger (milliseconds for time, percentage for scroll, seconds for exit, element id for click, event name for event) */\n value?: number | string;\n /** Survey ID to show when triggered */\n surveyId: string;\n /** Optional popup definition id (used to disambiguate repeated survey ids) */\n popupId?: string;\n}\n\n/**\n * Event types emitted by the SDK\n */\nexport type DeepdotsEventType =\n | 'popup_shown'\n | 'popup_clicked'\n | 'survey_completed';\n\n/**\n * Event payload structure\n */\nexport interface DeepdotsEvent {\n type: DeepdotsEventType;\n surveyId: string;\n timestamp: number;\n data?: Record<string, unknown>;\n}\n\n/**\n * Event listener callback\n */\nexport type EventListener = (event: DeepdotsEvent) => void;\n\n/**\n * Motivo del `deepdots_session_end` (último lote de la sesión, enviado con `completed:true`):\n * - `page_hide`: la página web se cierra o se navega fuera (`pagehide`).\n * - `background`: la app pasa a background (RN/nativo). Única señal disponible en móvil:\n * el kill de la app por el usuario o el SO NO produce ningún callback.\n * - `user_change`: `setUserId()` o un `init()` con otro `userId` (login/logout del host).\n * - `tracking_disabled`: `setTrackingEnabled(false)` (consentimiento revocado).\n * - `manual`: `endSession()` llamado por el host.\n */\nexport type SessionEndReason = 'page_hide' | 'background' | 'user_change' | 'tracking_disabled' | 'manual';\n\n/** Trigger type used by remote popup definitions */\nexport type PopupTriggerType = 'time_on_page' | 'scroll' | 'exit' | 'click' | 'event';\n\nexport type PopupTriggerConditionStatus = 'SHOWED' | 'PARTIAL' | 'COMPLETED';\n\nexport const POPUP_TRIGGER_CONDITION_STATUSES: PopupTriggerConditionStatus[] = [\n 'SHOWED',\n 'PARTIAL',\n 'COMPLETED',\n];\n\n/** Extra condition used to activate the popup */\nexport interface PopupTriggerCondition {\n answered: PopupTriggerConditionStatus; // user progress status in the survey\n cooldownDays: number; // cooldown days before showing again\n}\n\n/** Trigger attached to the popup definition */\nexport interface PopupTrigger {\n type: PopupTriggerType;\n value: number | string; // seconds on page, scroll percentage, click id, or event name\n}\n\n/** Accept action (open survey) */\nexport interface PopupActionAccept {\n label: string;\n surveyId: string; // survey id to launch\n}\n\n/** Decline action */\nexport interface PopupActionDecline {\n label: string;\n cooldownDays?: number; // optional: do not show again until X days pass\n}\n\n/** Start Surveys Action */\nexport interface PopupActionStart {\n label: string;\n}\n\n/** Complete action (accept and auto-complete survey) */\n// Requires the survey to support auto-completion via parameters.\nexport interface PopupActionComplete {\n label: string;\n surveyId: string; // survey id to launch\n autoCompleteParams: Record<string, unknown>; // parameters used for auto-completion\n cooldownDays?: number;\n}\n\n/** Set of actions available in the popup */\nexport interface PopupActions {\n accept?: PopupActionAccept;\n decline?: PopupActionDecline;\n complete?: PopupActionComplete;\n start?: PopupActionStart;\n back?: PopupActionDecline;\n}\n\n/** Fuente personalizable del popup + survey (viene de la API por popup) */\nexport interface PopupFont {\n /** Nombre de la familia. Ej: \"Inter\" (nombre limpio, NO un stack). */\n family: string;\n /** Opcional: URL a un archivo de fuente (woff2/ttf/otf) para armar un @font-face. */\n url?: string;\n}\n\n/** Configurable popup styles */\nexport interface PopupStyle {\n theme: 'light' | 'dark';\n position: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center';\n /** @deprecated Not used by the SDK; kept only for API compatibility */\n imageUrl?: string | null;\n /** Fuente personalizada. Si falta, se mantiene el comportamiento actual. */\n font?: PopupFont;\n}\n\n/** Segments/targeting rules for showing the popup */\nexport interface PopupSegments {\n lang?: string[]; // allowed languages\n path?: string[]; // app routes where it can be shown\n [key: string]: unknown; // possible future extension\n}\n\n/** Full popup definition */\nexport interface PopupDefinition {\n id: string;\n title: string;\n message: string; // rendered safe HTML (recommended to sanitize upstream)\n triggers: PopupTrigger[];\n cooldown?: PopupTriggerCondition[];\n actions?: PopupActions;\n surveyId: string;\n productId: string;\n style?: PopupStyle;\n segments?: PopupSegments;\n}\n\nexport interface FormStyle\n{\n id: string;\n logo: string;\n title: string;\n companyName: string;\n backgroundColor: string;\n boxBackgroundColor: string;\n buttonOnTopColor: string;\n buttonPrimaryColor: string\n buttonSecondaryColor: string;\n loadingBarColor: string;\n favIcon: string;\n startMessage: string;\n successMessage: string;\n addLogoInMsg: boolean;\n privacyPolicyUrl: string;\n helpUrl: string;\n integrationId: string;\n redirectLink: string;\n redirectError: string;\n redirectTime: number;\n integrationThemeId: string;\n contentAlign: 'top' | 'center';\n logoSize: 'small' | 'medium' | 'large';\n logoPosition: 'left' | 'right' | 'center';\n showProgressBar: boolean;\n showProgressUnit: boolean;\n progressUnit: 'percentage' | 'fraction';\n}\n\n\nexport interface FormData {\n id: string;\n name: string;\n description: string;\n type: string;\n identity: string;\n status: string;\n createdAt: Date;\n updatedAt: Date;\n externalId?: string | null;\n companyId: string;\n productId: string;\n userId: string;\n style: FormStyle\n}\n\nexport enum POPUPSESSIONSTATUS {\n NONE = \"NONE\",\n SHOWED = \"SHOWED\",\n PARTIAL = \"PARTIAL\",\n COMPLETED = \"COMPLETED\",\n DEPRECATED = \"DEPRECATED\",\n}\n","import type { TriggerConfig } from '../types';\nimport type { DeepdotsPopups } from '../core/deepdots-popups';\n\nexport function setupTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n switch (trigger.type) {\n case 'time':\n setupTimeTrigger(popups, trigger);\n break;\n case 'scroll':\n setupScrollTrigger(popups, trigger);\n break;\n case 'exit':\n setupExitTrigger(popups, trigger);\n break;\n case 'event':\n setupEventTrigger(popups, trigger);\n break;\n case 'click':\n setupClickTrigger(popups, trigger);\n break;\n }\n}\n\nfunction setupTimeTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n const delay = trigger.value || 5000;\n setTimeout(() => {\n popups.triggerSurvey(trigger.surveyId, trigger.popupId);\n }, delay);\n popups.debug(`Time trigger set for ${delay}ms`);\n}\n\nfunction setupScrollTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n const threshold = trigger.value || 50;\n const handler = () => {\n const scrollPercentage = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;\n if (scrollPercentage >= threshold) {\n popups.triggerSurvey(trigger.surveyId, trigger.popupId);\n window.removeEventListener('scroll', handler);\n }\n };\n window.addEventListener('scroll', handler);\n popups.debug(`Scroll trigger set for ${threshold}%`);\n}\n\nfunction setupExitTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n if (typeof window === 'undefined') return;\n\n const delaySeconds = parseExitDelaySeconds(trigger.value);\n const historyRef = window.history;\n const originalPushState = historyRef.pushState.bind(historyRef);\n const originalReplaceState = historyRef.replaceState.bind(historyRef);\n\n let lastUrl = normalizeUrl(window.location.href);\n let lastQueuedKey = '';\n let lastQueuedAt = 0;\n\n const queueLeave = (fromRaw: string, toRaw: string) => {\n const from = normalizeUrl(fromRaw);\n const to = normalizeUrl(toRaw);\n if (!from || !to || from === to) return;\n\n const key = `${from}=>${to}`;\n const now = Date.now();\n if (key === lastQueuedKey && now - lastQueuedAt < 150) return;\n lastQueuedKey = key;\n lastQueuedAt = now;\n popups.queueExitPopup(trigger.surveyId, delaySeconds, from, trigger.popupId);\n };\n\n const handleNavigation = (to: string) => {\n const from = lastUrl;\n queueLeave(from, to);\n lastUrl = normalizeUrl(to);\n };\n\n const handleDocumentClick = (event: MouseEvent) => {\n if (event.defaultPrevented) return;\n if (event.button !== 0) return;\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n\n const anchor = (event.target as Element | null)?.closest?.('a');\n if (!(anchor instanceof HTMLAnchorElement)) return;\n\n const href = anchor.getAttribute('href');\n if (!href || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) {\n return;\n }\n const target = anchor.getAttribute('target');\n if (target && target !== '_self') return;\n\n let destination: URL;\n try {\n destination = new URL(href, window.location.href);\n } catch {\n return;\n }\n\n if (destination.origin !== window.location.origin) return;\n queueLeave(window.location.href, destination.href);\n };\n\n const handleHashChange = (event: HashChangeEvent) => {\n const to = event.newURL || window.location.href;\n const from = event.oldURL || lastUrl;\n queueLeave(from, to);\n lastUrl = normalizeUrl(to);\n };\n\n const handlePopState = () => {\n handleNavigation(window.location.href);\n };\n\n historyRef.pushState = function (data: any, unused: string, url?: string | URL | null) {\n const from = window.location.href;\n const result = originalPushState(data, unused, url as any);\n queueLeave(from, window.location.href);\n lastUrl = normalizeUrl(window.location.href);\n return result;\n };\n\n historyRef.replaceState = function (data: any, unused: string, url?: string | URL | null) {\n const from = window.location.href;\n const result = originalReplaceState(data, unused, url as any);\n queueLeave(from, window.location.href);\n lastUrl = normalizeUrl(window.location.href);\n return result;\n };\n\n document.addEventListener('click', handleDocumentClick, true);\n window.addEventListener('hashchange', handleHashChange);\n window.addEventListener('popstate', handlePopState);\n popups.debug('Exit trigger set (deferred to next route)', {\n surveyId: trigger.surveyId,\n delaySeconds,\n });\n}\n\nfunction setupEventTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n // Event triggers are manually fired by host application using popups.triggerEvent(name).\n popups.debug('Event trigger registered', {\n popupId: trigger.popupId,\n surveyId: trigger.surveyId,\n eventName: String(trigger.value || ''),\n });\n}\n\nfunction parseExitDelaySeconds(value?: number | string): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return Math.max(0, value);\n }\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) return Math.max(0, parsed);\n }\n return 0;\n}\n\nfunction normalizeUrl(value: string): string {\n if (!value) return '';\n const withoutIndex = value.replace(/\\/index\\.html(?=($|[?#]))/i, '');\n return withoutIndex.length > 1 && withoutIndex.endsWith('/') ? withoutIndex.slice(0, -1) : withoutIndex;\n}\n\nfunction setupClickTrigger(popups: DeepdotsPopups, trigger: TriggerConfig): void {\n const targetId = typeof trigger.value === 'string' ? trigger.value : '';\n if (!targetId) {\n popups.debug('Click trigger missing element id', trigger);\n return;\n }\n\n const attach = () => {\n const el = document.getElementById(targetId);\n if (!el) {\n popups.debug('Click trigger target not found', targetId);\n return false;\n }\n const handler = (e: MouseEvent) => {\n if (e.defaultPrevented) return;\n popups.triggerSurvey(trigger.surveyId, trigger.popupId);\n el.removeEventListener('click', handler);\n };\n el.addEventListener('click', handler);\n popups.debug(`Click trigger set for #${targetId}`);\n return true;\n };\n\n if (typeof document === 'undefined') return;\n if (attach()) return;\n window.addEventListener('DOMContentLoaded', () => attach(), { once: true });\n}\n","import {DeepdotsConfig, DeepdotsInitParams} from '../types';\n\n/** Variables específicas por entorno derivadas de nodeEnv */\nexport interface EnvironmentVariables {\n apiBaseUrl: string;\n}\n\nconst ENVIRONMENT_MAP: Record<\"development\" | \"production\", EnvironmentVariables> = {\n development: {\n apiBaseUrl: 'https://api-dev.deepdots.com',\n },\n production: {\n apiBaseUrl: 'https://api.deepdots.com',\n },\n};\n\n/** Resuelve las variables para el entorno indicado */\nexport function resolveEnvironment(nodeEnv: DeepdotsInitParams['nodeEnv']): EnvironmentVariables {\n return ENVIRONMENT_MAP[nodeEnv || 'production'];\n}\n\n","import type { IdentityAnswer } from '../tracking/tracking-manager';\nimport { buildFontFaceCss, buildFontFamilyValue } from './font';\nimport type { PopupActions, PopupFont } from '../types';\nimport magicfeedbackCss from '../assets/style.css';\n\n/**\n * Construye un HTML autocontenido que renderiza el popup (chrome + survey de\n * `@magicfeedback/native`) dentro de un WebView (React Native). Es el equivalente RN de\n * `renderPopup.ts` (DOM real, solo navegador): mismo header con botón cerrar, spinner,\n * footer con back/start/send, gate \"Start survey\" y colores/logo desde `formData.style`.\n * Carga el bundle del survey desde CDN, crea el form con la identidad inyectada\n * (`profile`/`metadata`) y reenvía los eventos al host vía el puente de\n * react-native-webview: `window.ReactNativeWebView.postMessage`.\n *\n * Mensajes emitidos (JSON `{name, payload}`): `loaded`, `before_submit`, `after_submit`,\n * `survey_completed`, `back`, `popup_close`.\n */\nexport interface BuildSurveyHtmlOptions {\n surveyId: string;\n productId: string;\n env?: string; // 'production' | 'development'\n profile?: IdentityAnswer[];\n metadata?: IdentityAnswer[];\n version?: string; // versión de @magicfeedback/native\n font?: PopupFont; // fuente personalizada (family + url opcional)\n theme?: 'light' | 'dark';\n position?: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center';\n actions?: PopupActions;\n}\n\ninterface PositionRule {\n justifyContent: string;\n alignItems: string;\n padding: string;\n background: string;\n}\n\nconst POSITION_MAP: Record<string, PositionRule> = {\n center: { justifyContent: 'center', alignItems: 'center', padding: '0', background: 'rgba(0,0,0,0.5)' },\n bottom: { justifyContent: 'center', alignItems: 'flex-end', padding: '16px', background: 'transparent' },\n 'bottom-right': { justifyContent: 'flex-end', alignItems: 'flex-end', padding: '16px', background: 'transparent' },\n 'bottom-left': { justifyContent: 'flex-start', alignItems: 'flex-end', padding: '16px', background: 'transparent' },\n top: { justifyContent: 'center', alignItems: 'flex-start', padding: '16px', background: 'transparent' },\n 'top-right': { justifyContent: 'flex-end', alignItems: 'flex-start', padding: '16px', background: 'transparent' },\n 'top-left': { justifyContent: 'flex-start', alignItems: 'flex-start', padding: '16px', background: 'transparent' },\n};\n\nexport function buildSurveyHtml(opts: BuildSurveyHtmlOptions): string {\n const env = opts.env === 'development' ? 'dev' : 'prod';\n const version = opts.version ?? '2.2.4';\n const cdn = `https://cdn.jsdelivr.net/npm/@magicfeedback/native@${version}/dist/magicfeedback-sdk.browser.js`;\n const sid = JSON.stringify(opts.surveyId);\n const pid = JSON.stringify(opts.productId);\n const profileJson = JSON.stringify(opts.profile ?? []);\n const metaJson = JSON.stringify(opts.metadata ?? []);\n const fontFaceCss = opts.font ? buildFontFaceCss(opts.font.family, opts.font.url) : '';\n const fontFamilyValue = opts.font ? buildFontFamilyValue(opts.font.family) : '-apple-system,system-ui,sans-serif';\n const popupClass = opts.font?.family ? 'deepdots-popup deepdots-has-font' : 'deepdots-popup';\n\n const isDark = opts.theme === 'dark';\n const popupBg = isDark ? '#1e1e1e' : '#fff';\n const colorScheme = isDark ? 'dark' : 'light';\n const textPrimary = isDark ? '#f0f0f0' : '#111';\n const pos = POSITION_MAP[opts.position ?? 'center'] ?? POSITION_MAP.center;\n\n const backLabel = JSON.stringify(opts.actions?.back?.label ?? 'Back');\n const startLabel = JSON.stringify(opts.actions?.start?.label ?? 'Start survey');\n const completeLabel = JSON.stringify(opts.actions?.complete?.label ?? 'Complete survey');\n const submitLabel = JSON.stringify(opts.actions?.accept?.label ?? 'Send');\n\n return `<!DOCTYPE html><html><head>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n<meta name=\"color-scheme\" content=\"${colorScheme}\"/>\n<style>\n${magicfeedbackCss}\n${fontFaceCss}\nhtml,body{margin:0;padding:0;height:100%;font-family:${fontFamilyValue}}\nbody{display:flex;justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:${pos.background};padding:${pos.padding};box-sizing:border-box}\n.deepdots-popup{position:relative;display:flex;flex-direction:column;justify-content:flex-start;background:${popupBg};color-scheme:${colorScheme};border-radius:8px;padding:24px;box-shadow:0 4px 6px rgba(0,0,0,0.1);max-width:600px;width:90%;min-height:200px;box-sizing:border-box}\n.deepdots-popup-header{display:flex;justify-content:flex-end;align-items:center;width:100%}\n#dd-close{background:transparent;border:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:8px;cursor:pointer;color:${textPrimary};padding:4px}\n.deepdots-popup-container-content{display:flex;flex-direction:column;padding:0 20px 12px 20px;max-height:80vh;overflow:hidden;box-sizing:border-box}\n.deepdots-popup-main{display:flex;flex-direction:column;width:100%;max-height:80vh;overflow-y:auto;overflow-x:hidden;position:relative}\n#dd-form-wrapper{width:100%;flex:1 1 auto}\n#mf{width:100%;box-sizing:border-box;visibility:hidden}\n.deepdots-popup-main *{max-width:100%;box-sizing:border-box}\n#dd-logo{max-height:40px;max-width:100%;object-fit:contain}\n.deepdots-error-hint{display:none;margin:12px 0 0 0;padding:10px 12px;border-radius:6px;background:#FEF3C7;color:#92400E;border:1px solid #FCD34D;font-size:13px}\n.deepdots-popup-footer{display:flex;flex-direction:row-reverse;justify-content:space-between;align-items:center;gap:8px;margin-top:auto;width:100%;padding-top:16px}\n.dd-nav-btn{display:none;border:none;padding:12px 24px;border-radius:4px;cursor:pointer;font-size:14px;box-shadow:0 2px 4px rgba(0,0,0,0.1);align-items:center;justify-content:center;text-align:center}\n#dd-back{background:transparent;color:#333;border:1px solid #999}\n#dd-start,#dd-complete,#dd-submit{background:#1E293B;color:#fff;border:none}\n#dd-submit:disabled,#dd-start:disabled,#dd-back:disabled,#dd-complete:disabled{opacity:0.6;cursor:not-allowed}\n.mf-spinner{display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}\n.mf-spinner-circle{width:28px;height:28px;border:3px solid #e0e6ed;border-top-color:#1E293B;border-radius:50%;animation:ddspin 0.9s linear infinite}\n@keyframes ddspin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}\n@media (max-width:640px){\n .deepdots-popup{width:calc(100% - 24px);max-width:calc(100% - 24px);border-radius:12px}\n .deepdots-popup-footer{flex-direction:column-reverse;gap:12px}\n .dd-nav-btn{width:100%}\n .magicfeedback-checkbox-container,.magicfeedback-radio-container{margin:4px 0;padding:4px 8px}\n .magicfeedback-checkbox label,.magicfeedback-radio-container label{font-size:15px}\n .deepdots-popup input[type=\"radio\"]{margin:6px 0 6px 8px}\n .deepdots-popup input[type=\"checkbox\"]{width:16px;height:16px}\n}\n</style>\n</head><body>\n<div class=\"${popupClass}\" id=\"dd-popup\">\n <div class=\"deepdots-popup-header\">\n <button type=\"button\" id=\"dd-close\" aria-label=\"Close popup\">\n <svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 6L18 18M6 18L18 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n </div>\n <div class=\"deepdots-popup-container-content\">\n <div class=\"deepdots-popup-main\" id=\"dd-main\">\n <div id=\"dd-form-wrapper\">\n <div class=\"mf-spinner\" id=\"dd-spinner\" role=\"status\" aria-label=\"Loading survey\"><div class=\"mf-spinner-circle\"></div></div>\n <div id=\"mf\"></div>\n </div>\n <div class=\"deepdots-error-hint\" id=\"dd-error\" role=\"alert\" aria-live=\"polite\"></div>\n <div class=\"deepdots-popup-footer\" id=\"dd-footer\">\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-submit\">Send</button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-back\">Back</button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-complete\">Complete survey</button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-start\">Start survey</button>\n </div>\n </div>\n </div>\n</div>\n<script>\n(function(){\n function emit(s){ try{ if(window.ReactNativeWebView){ window.ReactNativeWebView.postMessage(s); } }catch(e){} }\n function emitJSON(n,p){ try{ emit(JSON.stringify({name:n,payload:p||{}})); }catch(e){} }\n window.DeepdotsClose = function(){ emitJSON('popup_close'); };\n\n var popup=document.getElementById('dd-popup');\n var main=document.getElementById('dd-main');\n var footer=document.getElementById('dd-footer');\n var spinner=document.getElementById('dd-spinner');\n var formHost=document.getElementById('mf');\n var errorHint=document.getElementById('dd-error');\n var backBtn=document.getElementById('dd-back');\n var startBtn=document.getElementById('dd-start');\n var completeBtn=document.getElementById('dd-complete');\n var submitBtn=document.getElementById('dd-submit');\n\n backBtn.textContent=${backLabel};\n startBtn.textContent=${startLabel};\n completeBtn.textContent=${completeLabel};\n submitBtn.textContent=${submitLabel};\n\n document.getElementById('dd-close').onclick=function(){ emitJSON('popup_close'); };\n backBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.back){ window.DeepdotsForm.back(); } };\n startBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.startForm){ window.DeepdotsForm.startForm(); } };\n completeBtn.onclick=function(){ emitJSON('popup_close'); };\n var surveyCompletedEmitted=false;\n submitBtn.onclick=function(){\n if(surveyCompletedEmitted) return;\n if(window.DeepdotsForm && window.DeepdotsForm.send){ window.DeepdotsForm.send(); }\n };\n\n function updateButtons(state){\n backBtn.style.display='none';\n startBtn.style.display='none';\n submitBtn.style.display='none';\n completeBtn.style.display='none';\n backBtn.style.width='';\n startBtn.style.width='';\n submitBtn.style.width='';\n completeBtn.style.width='';\n if(state==='start'){\n startBtn.style.display='inline-flex';\n startBtn.style.width='100%';\n } else if(state==='in_progress_first'){\n submitBtn.style.display='inline-flex';\n } else if(state==='in_progress_next'){\n backBtn.style.display='inline-flex';\n submitBtn.style.display='inline-flex';\n } else if(state==='completed'){\n completeBtn.style.display='inline-flex';\n completeBtn.style.width='100%';\n }\n }\n\n function setLoading(isLoading){\n spinner.style.display=isLoading?'flex':'none';\n if(!isLoading){ formHost.style.visibility='visible'; }\n footer.style.display=isLoading?'none':'flex';\n backBtn.disabled=isLoading; startBtn.disabled=isLoading; completeBtn.disabled=isLoading; submitBtn.disabled=isLoading;\n if(isLoading){ updateButtons(null); }\n }\n setLoading(true);\n\n var initialized=false;\n function initMF(){\n if(initialized || !window.magicfeedback) return initialized;\n initialized=true;\n try{\n window.magicfeedback.init({ debug:false, env:'${env}' });\n var form = window.magicfeedback.form(${sid}, ${pid}, ${profileJson}, ${metaJson});\n window.DeepdotsForm = form;\n var stylesInjected=false;\n form.generate('mf', {\n addButton:false,\n getMetaData:true,\n onLoadedEvent:function(args){\n var s=args && args.formData ? args.formData.style : null;\n if(s && !stylesInjected){\n stylesInjected=true;\n if(s.boxBackgroundColor){ popup.style.background=s.boxBackgroundColor; }\n if(s.contentAlign){ main.style.justifyContent = s.contentAlign==='center' ? 'center' : 'flex-start'; }\n if(s.buttonPrimaryColor){\n submitBtn.style.background=s.buttonPrimaryColor; submitBtn.style.border='none'; submitBtn.style.color='#fff';\n startBtn.style.background=s.buttonPrimaryColor; startBtn.style.border='none'; startBtn.style.color='#fff';\n completeBtn.style.background=s.buttonPrimaryColor; completeBtn.style.border='none'; completeBtn.style.color='#fff';\n }\n if(s.buttonSecondaryColor){\n backBtn.style.background='#fff'; backBtn.style.color=s.buttonSecondaryColor; backBtn.style.border='1px solid '+s.buttonSecondaryColor;\n }\n if(s.logo && !document.getElementById('dd-logo')){\n var logoImg=document.createElement('img');\n logoImg.id='dd-logo'; logoImg.src=s.logo; logoImg.alt='Logo';\n if(s.logoSize==='small'){ logoImg.style.maxHeight='30px'; }\n else if(s.logoSize==='medium'){ logoImg.style.maxHeight='50px'; }\n else if(s.logoSize==='large'){ logoImg.style.maxHeight='70px'; }\n if(s.logoPosition==='left'){ logoImg.style.margin='0 16px 20px 0'; logoImg.style.display='block'; logoImg.style.marginLeft='0'; }\n else if(s.logoPosition==='right'){ logoImg.style.margin='0 0 20px 16px'; logoImg.style.display='block'; logoImg.style.marginLeft='auto'; }\n else if(s.logoPosition==='center'){ logoImg.style.margin='0 auto 20px auto'; logoImg.style.display='block'; }\n main.insertBefore(logoImg, document.getElementById('dd-form-wrapper'));\n }\n if(s.startMessage && s.startMessage!==''){ updateButtons('start'); }\n else { updateButtons('in_progress_first'); }\n } else {\n updateButtons('in_progress_first');\n }\n emitJSON('loaded');\n setLoading(false);\n },\n beforeSubmitEvent:function(){\n setLoading(true);\n emitJSON('before_submit');\n },\n afterSubmitEvent:function(p){\n var error=p && p.error ? (typeof p.error==='string' ? p.error : String(p.error)) : '';\n if(error){\n setLoading(false);\n if(error.toLowerCase().indexOf('no response')!==-1){\n errorHint.textContent='Please answer the required question to continue.';\n errorHint.style.display='block';\n updateButtons('in_progress_next');\n return;\n }\n errorHint.textContent='An error occurred while submitting. Please try again or close the popup.';\n errorHint.style.display='block';\n return;\n }\n errorHint.style.display='none';\n setLoading(false);\n if(p && p.completed){\n surveyCompletedEmitted=true;\n updateButtons('completed');\n emitJSON('survey_completed', p);\n return;\n }\n if(p && p.total>1 && p.progress>0 && p.progress<p.total){ updateButtons('in_progress_next'); }\n else { updateButtons('in_progress_first'); }\n emitJSON('after_submit', p);\n },\n onBackEvent:function(p){\n if(p && p.progress===0){ updateButtons('in_progress_first'); }\n else { updateButtons('in_progress_next'); }\n emitJSON('back', p);\n }\n }).catch(function(e){ setLoading(false); emit('error:init'); });\n }catch(e){ setLoading(false); emit('error:exception'); }\n return true;\n }\n var s=document.createElement('script'); s.src=${JSON.stringify(cdn)}; s.async=true;\n s.onload=function(){ initMF(); };\n s.onerror=function(){ setLoading(false); emit('error:load'); };\n document.head.appendChild(s);\n var tries=0, t=setInterval(function(){ if(initMF()||++tries>40){ clearInterval(t); } },150);\n})();\n</script>\n</body></html>`;\n}\n","import type { DeepdotsEventType, PopupActions, PopupStyle } from '../types';\nimport type { PopupRenderer } from './renderer';\nimport { buildSurveyIdentity } from '../tracking/tracking-manager';\nimport { buildSurveyHtml } from '../ui/surveyHtml';\nimport { sdkWarn } from '../util/logger';\n\ntype EmitFn = (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void;\n\n/** Payload que recibe el host para montar el WebView del survey. */\nexport interface ReactNativeSurveyPayload {\n surveyId: string;\n productId: string;\n /** HTML autocontenido para `<WebView source={{ html }}>`. */\n html: string;\n}\n\nexport interface ReactNativeRendererOptions {\n /** Se llama al mostrar un popup: monta el WebView con `payload.html`. */\n onShow?: (payload: ReactNativeSurveyPayload) => void;\n /** Se llama al cerrar el popup: desmonta el WebView. */\n onHide?: () => void;\n}\n\n/**\n * Renderer de React Native: el SDK no puede pintar componentes RN, así que entrega el\n * HTML del survey al host (que lo monta en `react-native-webview`) y traduce los\n * mensajes del WebView a eventos de popup del SDK (→ `POST /sdk/popups`, Messaging #18–22).\n *\n * Uso:\n * ```tsx\n * const renderer = new ReactNativePopupRenderer({\n * onShow: (p) => setSurvey({ ...p, visible: true }),\n * onHide: () => setSurvey((s) => ({ ...s, visible: false })),\n * });\n * sdk.setRenderer(renderer);\n * // …\n * <WebView source={{ html: survey.html }}\n * onMessage={(e) => renderer.handleMessage(e.nativeEvent.data)} />\n * ```\n */\nexport class ReactNativePopupRenderer implements PopupRenderer {\n private emitFn: EmitFn | null = null;\n private onCloseFn: (() => void) | null = null;\n private currentSurveyId: string | null = null;\n private partialEmitted = false;\n\n constructor(private options: ReactNativeRendererOptions = {}) {}\n\n init(): void {\n /* nada que preparar */\n }\n\n show(\n surveyId: string,\n productId: string,\n actions: PopupActions | undefined,\n emit: EmitFn,\n onClose: () => void,\n env: string = 'production',\n userId?: string,\n style?: PopupStyle,\n sessionId?: string,\n miniService?: string,\n analyticsFeedbackSessionId?: string,\n ): void {\n this.emitFn = emit;\n this.onCloseFn = onClose;\n this.currentSurveyId = surveyId;\n this.partialEmitted = false;\n\n const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);\n const html = buildSurveyHtml({\n surveyId,\n productId,\n env,\n profile,\n metadata,\n font: style?.font,\n theme: style?.theme,\n position: style?.position,\n actions,\n });\n if (this.options.onShow) {\n this.options.onShow({ surveyId, productId, html });\n } else {\n sdkWarn(\n '[Deepdots] ReactNativePopupRenderer has no onShow: pass { onShow } to new ReactNativePopupRenderer({...}) to mount the survey WebView.',\n );\n }\n }\n\n hide(): void {\n this.options.onHide?.();\n this.currentSurveyId = null;\n }\n\n /**\n * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a\n * eventos del SDK: primera interacción → `popup_clicked` (PARTIAL), completado →\n * `survey_completed` (COMPLETED) y cierra el popup.\n */\n handleMessage(raw: string): void {\n const surveyId = this.currentSurveyId;\n if (!surveyId || !this.emitFn) return;\n\n let name: string | undefined;\n try {\n name = (JSON.parse(raw) as { name?: string }).name;\n } catch {\n name = raw; // mensajes simples (\"error:load\")\n }\n\n switch (name) {\n case 'loaded':\n case 'before_submit':\n case 'after_submit':\n case 'back':\n if (!this.partialEmitted) {\n this.partialEmitted = true;\n this.emitFn('popup_clicked', surveyId, { action: 'partial' });\n }\n break;\n case 'survey_completed':\n this.emitFn('survey_completed', surveyId);\n this.onCloseFn?.(); // el core enruta a hidePopup() → renderer.hide() → onHide\n break;\n case 'popup_close':\n this.onCloseFn?.();\n break;\n default:\n break;\n }\n }\n}\n\n/** Detección simple de entorno React Native. */\nexport function isReactNativeEnv(): boolean {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return typeof navigator !== 'undefined' && (navigator as any).product === 'ReactNative';\n}\n\n/** Factoría usada por `createDefaultRenderer` cuando se detecta RN (el host debería pasar onShow). */\nexport function createReactNativeRenderer(): PopupRenderer {\n return new ReactNativePopupRenderer();\n}\n","import {DeepdotsEventType, PopupActions, PopupStyle} from '../types';\nimport { isReactNativeEnv, createReactNativeRenderer } from './react-native-renderer';\n// renderPopup (DOM + @magicfeedback/native) se carga PEREZOSAMENTE solo al mostrar\n// un popup en navegador, para que importar el SDK sea seguro en React Native/SSR.\n\nexport interface PopupRenderer {\n /** Preparar recursos si aplica */\n init?(): void;\n /** Mostrar popup */\n show(\n surveyId: string,\n productId: string,\n actions: PopupActions | undefined,\n emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void,\n onClose: () => void,\n env?: string,\n userId?: string,\n style?: PopupStyle,\n sessionId?: string,\n miniService?: string,\n analyticsFeedbackSessionId?: string,\n ): void;\n /** Ocultar popup */\n hide(): void;\n}\n\n/** Renderer que no hace nada (SSR / entornos sin DOM) */\nexport class NoopPopupRenderer implements PopupRenderer {\n show(): void { /* no-op */ }\n hide(): void { /* no-op */ }\n}\n\n/** Renderer para navegadores usando la implementación actual basada en renderPopup */\nexport class BrowserPopupRenderer implements PopupRenderer {\n private container: HTMLElement | null = null;\n private visible = false;\n\n init(): void {\n if (typeof document === 'undefined') return;\n if (!this.container) {\n this.container = document.createElement('div');\n this.container.id = 'deepdots-popup-container';\n this.container.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: none;\n z-index: 999999;\n background: rgba(0, 0, 0, 0.5);\n justify-content: center;\n align-items: center;\n `;\n }\n if (!document.body.contains(this.container)) {\n document.body.appendChild(this.container);\n }\n }\n\n show(\n surveyId: string,\n productId: string,\n actions: PopupActions | undefined,\n emit: (type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>) => void,\n onClose: () => void,\n env: string = 'production',\n userId?: string,\n style?: PopupStyle,\n sessionId?: string,\n miniService?: string,\n analyticsFeedbackSessionId?: string,\n ): void {\n if (this.visible) return;\n if (!this.container || !document.body.contains(this.container)) this.init();\n if (!this.container) return; // aún sin DOM\n this.visible = true;\n const container = this.container;\n void import('../ui/renderPopup').then(({ renderPopup }) => {\n renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);\n });\n }\n\n hide(): void {\n this.visible = false;\n if (this.container) {\n this.container.style.display = 'none';\n this.container.innerHTML = '';\n }\n }\n}\n\n/** Factoría para obtener renderer por defecto */\nexport function createDefaultRenderer(): PopupRenderer {\n if (typeof document === 'undefined') {\n // Entorno sin DOM: probar RN primero\n if (isReactNativeEnv()) return createReactNativeRenderer();\n return new NoopPopupRenderer();\n }\n // Si estamos en React Native y hay WebView con document emulado, aún queremos RN renderer\n if (isReactNativeEnv()) return createReactNativeRenderer();\n return new BrowserPopupRenderer();\n}\n","/**\n * Capa de Analytics (eventos GA-style) — canal SEPARADO del feedback de survey.\n *\n * El feedback de survey se envía con `magicfeedback.send()`. La analítica de\n * navegación/comportamiento (page views, mini-services, tasks/funnels, meaningful\n * interactions, findability…) va por un ENDPOINT PROPIO, vinculada por `user_id`.\n *\n * De momento NO se hace la llamada: el `sink` por defecto hace `console.log` del\n * payload exacto que se enviaría (dry-run), para poder verificarlo. Cuando el\n * endpoint exista, se sustituye el sink por un POST.\n */\n\nimport type { DeviceInfo } from './device-info';\n\nexport interface AnalyticsEvent {\n name: string;\n timestamp: number;\n params?: Record<string, unknown>;\n}\n\nexport interface AnalyticsEnvelope {\n /** Clave pública del cliente (auth). */\n publicKey?: string;\n /** Id que vincula la analítica al usuario (el backend cose por aquí). */\n userId: string | null;\n /** Session id del backend si ya se conoce (puede ir null). */\n sessionId: string | null;\n context: {\n platform: string; // 'web' | 'android' | 'ios' (RN inyecta la suya)\n language?: string;\n /** Info de dispositivo (Technology #11–13). */\n device?: DeviceInfo;\n /** User attributes del cliente, usados para breakdowns (registration_status, pass_type, …). */\n attributes: Record<string, string>;\n /** Métricas del host: valores medibles persistentes → van en `feedback.metrics` del body. */\n metrics?: Record<string, string>;\n };\n events: AnalyticsEvent[];\n}\n\n/** Contexto del flush, para que el sink pueda adaptar el transporte. */\nexport interface AnalyticsFlushMeta {\n /**\n * `true` cuando el flush ocurre porque la página/app se está cerrando. El sink debe usar\n * un transporte que sobreviva al unload (sendBeacon) y no puede esperar respuestas.\n */\n final?: boolean;\n /**\n * `true` cuando este es el ÚLTIMO lote de la sesión (cierre de página, app a background,\n * cambio de usuario, tracking desactivado). El sink lo envía con `completed:true` y olvida\n * el `sessionId`, para que el lote siguiente abra un registro nuevo.\n */\n sessionEnd?: boolean;\n}\n\n/**\n * Envío del lote. Si devuelve una promesa que **rechaza**, el manager re-encola los\n * eventos para reintentarlos en el siguiente flush (fallo transitorio: red o 5xx).\n * Resolver = lote entregado o descartado definitivamente (no se reintenta).\n */\nexport type AnalyticsSink = (payload: AnalyticsEnvelope, meta?: AnalyticsFlushMeta) => void | Promise<void>;\n\n/** Identidad resuelta por el tracking, inyectada al construir el payload. */\nexport interface AnalyticsIdentity {\n userId: string | null;\n sessionId: string | null;\n}\n\n/**\n * Sink de dry-run: NO envía nada, solo vuelca lo que se enviaría por el `log` dado\n * (default `console.log`, o el logger inyectado por el host en init()).\n */\nexport function createDryRunSink(log: (...args: unknown[]) => void = console.log): AnalyticsSink {\n return (payload) =>\n log(\n '[DeepdotsAnalytics] (dry-run · NOT sent · no init.analytics) POST /sdk/feedback →',\n JSON.stringify(payload, null, 2),\n );\n}\n\n/** Sink por defecto: NO envía nada, solo pinta por consola lo que se enviaría. */\nexport const dryRunSink: AnalyticsSink = createDryRunSink();\n\nexport interface AnalyticsManagerOptions {\n sink?: AnalyticsSink;\n now?: () => number;\n publicKey?: string;\n language?: string;\n device?: DeviceInfo;\n /** Plataforma del envelope (default 'web'; RN inyecta 'android'/'ios'). */\n platform?: string;\n /** Nº máximo de eventos en buffer antes de solicitar un flush automático (default 20). */\n maxBatchSize?: number;\n /**\n * Techo de eventos retenidos en memoria contando los re-encolados por fallo de envío\n * (default 200). Al superarlo se descartan los más antiguos.\n */\n maxBufferedEvents?: number;\n /** Callback invocado cuando el buffer alcanza `maxBatchSize`. El caller hace el flush real. */\n onFlushNeeded?: () => void;\n}\n\nexport class AnalyticsManager {\n private sink: AnalyticsSink;\n private now: () => number;\n private publicKey?: string;\n private language?: string;\n private device?: DeviceInfo;\n private platform: string;\n\n private events: AnalyticsEvent[] = [];\n private attributes: Record<string, string> = {};\n /** Métricas del host (valores medibles persistentes). Sobrescriben por key; se reenvían en cada flush. */\n private metrics: Record<string, string> = {};\n /** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el \"más reciente\". */\n private activeMiniServices = new Map<string, number>();\n private maxBatchSize: number;\n private maxBufferedEvents: number;\n private onFlushNeeded?: () => void;\n\n constructor(options: AnalyticsManagerOptions = {}) {\n this.sink = options.sink ?? dryRunSink;\n this.now = options.now ?? (() => Date.now());\n this.publicKey = options.publicKey;\n this.language = options.language;\n this.device = options.device;\n this.platform = options.platform ?? 'web';\n this.maxBatchSize = options.maxBatchSize ?? 20;\n this.maxBufferedEvents = options.maxBufferedEvents ?? 200;\n this.onFlushNeeded = options.onFlushNeeded;\n }\n\n /** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */\n updateDevice(partial: Partial<DeviceInfo>): void {\n this.device = { ...this.device, ...partial } as DeviceInfo;\n }\n\n /** Mezcla user attributes (se coercionan a string). Mutable en runtime. */\n setUserAttributes(attrs: Record<string, string | number | boolean>): void {\n for (const [k, v] of Object.entries(attrs)) {\n if (!k) continue;\n this.attributes[k] = String(v);\n }\n }\n\n /**\n * Registra/actualiza una métrica (valor medible). Persistente: se reenvía en cada flush.\n * Repetir la misma key SOBRESCRIBE; el valor se coerciona a string; ignora key vacía.\n */\n setMetric(key: string, value: string | number | boolean): void {\n if (!key) return;\n this.metrics[key] = String(value);\n }\n\n /**\n * Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.\n * Admite varios activos a la vez (concurrencia); el \"actual\" para etiquetar es el más reciente.\n * Reentrar con un nombre ya activo refresca su orden y su tiempo de entrada.\n */\n enterMiniService(name: string, entryPointType?: string): void {\n this.activeMiniServices.delete(name); // reinsertar = pasa a ser el más reciente\n this.activeMiniServices.set(name, this.now());\n this.track('deepdots_mini_service_enter', { entry_point_type: entryPointType ?? null });\n }\n\n /**\n * Cierra el mini-service `name` emitiendo `mini_service_exit` con su duración (#27).\n * No-op si ese nombre no está activo. Cierre coherente con concurrencia.\n */\n exitMiniService(name: string): void {\n const enteredAt = this.activeMiniServices.get(name);\n if (enteredAt === undefined) return;\n const durationSeconds = Math.max(0, Math.round((this.now() - enteredAt) / 1000));\n this.activeMiniServices.delete(name); // dejar de etiquetar con este antes de emitir\n this.track('deepdots_mini_service_exit', { mini_service: name, duration_seconds: durationSeconds });\n }\n\n /** Cierra TODOS los mini-services activos (orden LIFO). Para el cierre por lifecycle (background/cierre). */\n exitAllMiniServices(): void {\n for (const name of Array.from(this.activeMiniServices.keys()).reverse()) {\n this.exitMiniService(name);\n }\n }\n\n /**\n * Olvida lo que pertenecía al usuario anterior (user attributes + métricas) al cambiar de\n * usuario. NO toca el buffer de eventos: el cierre de sesión ya lo vació con la identidad\n * vieja, y lo que llegue después es del usuario nuevo.\n */\n resetUserScope(): void {\n this.attributes = {};\n this.metrics = {};\n }\n\n /** Registra un evento de analítica (modelo GA: nombre + params). */\n track(name: string, params?: Record<string, unknown>): void {\n const current = this.getMiniService();\n const merged: Record<string, unknown> = {\n ...(current ? { mini_service: current } : {}),\n ...(params ?? {}),\n };\n this.events.push({\n name,\n timestamp: this.now(),\n params: Object.keys(merged).length ? merged : undefined,\n });\n this.enforceBufferCap();\n if (this.events.length >= this.maxBatchSize) {\n this.onFlushNeeded?.();\n }\n }\n\n /** Mini-service actual (el más reciente aún activo) para etiquetar eventos + metadata del survey (#33). */\n getMiniService(): string | null {\n let last: string | null = null;\n for (const name of this.activeMiniServices.keys()) last = name; // última clave = más reciente\n return last;\n }\n\n /** Nº de eventos pendientes de flush. */\n pending(): number {\n return this.events.length;\n }\n\n /** Construye el envelope que se enviaría al endpoint de analytics. */\n buildPayload(identity: AnalyticsIdentity): AnalyticsEnvelope {\n return {\n publicKey: this.publicKey,\n userId: identity.userId,\n sessionId: identity.sessionId,\n context: {\n platform: this.platform,\n language: this.language,\n device: this.device,\n attributes: { ...this.attributes },\n metrics: { ...this.metrics },\n },\n events: [...this.events],\n };\n }\n\n /**\n * Envía (vía sink) el lote acumulado y vacía el buffer. No-op si no hay eventos.\n *\n * Si el sink falla de forma transitoria (promesa rechazada o excepción síncrona) el lote\n * se **re-encola** al principio del buffer y se reintenta en el siguiente flush: un error\n * de red o un 5xx no debe hacer desaparecer los eventos.\n */\n flush(identity: AnalyticsIdentity, meta?: AnalyticsFlushMeta): AnalyticsEnvelope | null {\n if (this.events.length === 0) return null;\n const batch = this.events;\n const payload = this.buildPayload(identity);\n this.events = [];\n\n let result: void | Promise<void>;\n try {\n // sin meta se invoca con un solo argumento (compatibilidad con sinks del host)\n result = meta ? this.sink(payload, meta) : this.sink(payload);\n } catch {\n this.requeue(batch);\n return payload;\n }\n if (result && typeof (result as Promise<void>).then === 'function') {\n (result as Promise<void>).catch(() => this.requeue(batch));\n }\n return payload;\n }\n\n /**\n * Devuelve un lote fallido al buffer, delante de lo que haya llegado mientras se enviaba\n * (orden cronológico). Con el buffer lleno se descartan los eventos más antiguos.\n */\n private requeue(batch: AnalyticsEvent[]): void {\n this.events = [...batch, ...this.events];\n this.enforceBufferCap();\n }\n\n /** Techo del buffer: si el envío lleva rato fallando, se sacrifica lo más antiguo. */\n private enforceBufferCap(): void {\n const overflow = this.events.length - this.maxBufferedEvents;\n if (overflow > 0) this.events.splice(0, overflow);\n }\n}\n","/**\n * Mapeo del envelope de analytics → body de `POST /sdk/feedback`.\n *\n * La analítica se envía como un Feedback del modelo del Surveys SDK, agrupado por una\n * INTEGRACIÓN creada en la plataforma. Se manda en streaming con `completed:false`; el\n * backend cose por `sessionId` + `user_id`. El ÚLTIMO lote de una sesión (cierre de página,\n * app a background, cambio de usuario) va con `completed:true`: cierra el registro y el\n * siguiente lote omite el `sessionId` viejo para que el backend abra uno nuevo.\n * `feedback.finished` se queda siempre en `false` (no es la señal de cierre acordada).\n *\n * Encoding:\n * - todo va en `feedback.metadata`: contexto (user_id, session_id, platform…) + eventos\n * - cada entrada usa `value: string[]` (array de un elemento)\n * - eventos: {key: nombre_evento, value: [JSON(timestamp + params)]}\n * - identidad (user_id) → `profile` como `external-user-id`\n * - métricas del host (setMetric) → `feedback.metrics` (mismo shape que metadata, sin prefijo)\n * - `answers` vacío\n * - `text` vacío\n */\n\nimport type { AnalyticsEnvelope, AnalyticsSink } from './analytics-manager';\n\nexport interface AnalyticsKeys {\n publicKey: string;\n integration: string;\n}\n\nexport interface FeedbackKV {\n key: string;\n value: string[];\n}\n\nexport interface AnalyticsFeedbackBody {\n feedback: {\n text: string;\n answers: FeedbackKV[];\n metrics: FeedbackKV[];\n metadata: FeedbackKV[];\n profile: FeedbackKV[];\n finished: boolean;\n };\n publicKey: string;\n integration: string;\n completed: boolean;\n finished?: boolean;\n /** sessionId devuelto por el primer POST; permite que el backend agrupe todos los eventos en un solo registro. */\n sessionId?: string;\n}\n\n/** Inserta un par key/value en la lista solo si el valor está definido y no es vacío. */\nfunction pushKV(list: FeedbackKV[], key: string, value: unknown): void {\n if (value === undefined || value === null || value === '') return;\n list.push({ key, value: [String(value)] });\n}\n\nexport interface BuildBodyOptions {\n /**\n * `true` en el último lote de la sesión → `completed:true` (cierra el registro en backend).\n * El body SÍ lleva el `sessionId` actual (es el registro que se cierra); es el lote\n * SIGUIENTE el que lo omite.\n */\n sessionEnd?: boolean;\n}\n\nexport function buildAnalyticsFeedbackBody(\n envelope: AnalyticsEnvelope,\n keys: AnalyticsKeys,\n feedbackSessionId?: string,\n options?: BuildBodyOptions,\n): AnalyticsFeedbackBody {\n const { context } = envelope;\n\n const profile: FeedbackKV[] = [];\n pushKV(profile, 'external-user-id', envelope.userId);\n\n // Todo va en metadata: contexto del sistema (prefijo deepdots_) + atributos de usuario + eventos\n const metadata: FeedbackKV[] = [];\n pushKV(metadata, 'deepdots_user_id', envelope.userId);\n pushKV(metadata, 'deepdots_session_id', envelope.sessionId);\n pushKV(metadata, 'deepdots_platform', context.platform);\n pushKV(metadata, 'deepdots_language', context.language);\n if (context.device) {\n const d = context.device;\n pushKV(metadata, 'deepdots_device_type', d.device_type);\n pushKV(metadata, 'deepdots_os_version', d.os_version);\n pushKV(metadata, 'deepdots_device_model', d.device_model);\n pushKV(metadata, 'deepdots_app_version', d.app_version);\n pushKV(metadata, 'deepdots_user_agent', d.user_agent);\n pushKV(metadata, 'deepdots_timezone', d.timezone);\n pushKV(metadata, 'deepdots_referrer', d.referrer);\n pushKV(metadata, 'deepdots_viewport_size', d.viewport_size);\n pushKV(metadata, 'deepdots_screen_resolution', d.screen_resolution);\n pushKV(metadata, 'deepdots_pixel_ratio', d.pixel_ratio);\n pushKV(metadata, 'deepdots_entry_type', d.entry_type);\n pushKV(metadata, 'deepdots_page_load_ms', d.page_load_ms);\n pushKV(metadata, 'deepdots_connection_type', d.connection_type);\n pushKV(metadata, 'deepdots_country', d.country);\n pushKV(metadata, 'deepdots_city', d.city);\n }\n for (const [k, v] of Object.entries(context.attributes ?? {})) {\n pushKV(metadata, k, v);\n }\n for (const e of envelope.events) {\n metadata.push({\n key: e.name,\n value: [JSON.stringify({ timestamp: e.timestamp, ...(e.params ?? {}) })],\n });\n }\n\n // Métricas del host → campo dedicado `feedback.metrics` (mismo shape que metadata, sin prefijo).\n const metrics: FeedbackKV[] = [];\n for (const [k, v] of Object.entries(context.metrics ?? {})) {\n pushKV(metrics, k, v);\n }\n\n return {\n feedback: {\n text: '',\n answers: [],\n metrics,\n metadata,\n profile,\n finished: false,\n },\n publicKey: keys.publicKey,\n integration: keys.integration,\n // Único marcador de cierre acordado con backend: el último lote de la sesión.\n completed: options?.sessionEnd === true,\n ...(feedbackSessionId ? { sessionId: feedbackSessionId } : {}),\n };\n}\n\nexport interface FeedbackSinkOptions {\n /** Base URL del backend (api(.|-dev.)deepdots.com) ya resuelta por el entorno. */\n baseUrl: string;\n keys: AnalyticsKeys;\n /** Logger opcional (gated por debug). */\n log?: (...args: unknown[]) => void;\n /** fetch inyectable (tests / RN). */\n fetchImpl?: typeof fetch;\n /**\n * `navigator.sendBeacon` inyectable. Solo se usa en el flush final (cierre de página):\n * sobrevive al unload, a cambio de no poder leer la respuesta. Si no se pasa, el flush\n * final usa `fetch` con `keepalive`.\n */\n sendBeaconImpl?: (url: string, body: Blob | string) => boolean;\n /** Callback invocado cuando el backend devuelve un nuevo sessionId (primer POST). */\n onSessionId?: (id: string) => void;\n /** Callback invocado al cerrar la sesión (`completed:true`): el sessionId cacheado se olvida. */\n onSessionReset?: () => void;\n}\n\n/** Límite práctico de `fetch({keepalive:true})` y de `sendBeacon` (~64KB en Chrome). */\nconst KEEPALIVE_MAX_BYTES = 60_000;\n\n/** Un fallo transitorio merece reintento; un 4xx (payload/claves/Contact) no. */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nasync function safeBodyText(res: Response): Promise<string> {\n try {\n return (await res.text()).slice(0, 500);\n } catch {\n return '';\n }\n}\n\n/**\n * Sink real: transforma el envelope y hace `POST {baseUrl}/sdk/feedback`.\n *\n * Stateful: cachea el `sessionId` que devuelve el backend en la primera respuesta y lo\n * envía en los siguientes POSTs para que el backend agrupe todos los eventos en un único\n * registro de feedback.\n *\n * Garantías de entrega:\n * - mientras no se conozca el `sessionId`, los lotes se **serializan** (esperan la primera\n * respuesta): dos POSTs a la vez sin `sessionId` crearían dos registros y partirían los datos;\n * - `keepalive` para que el navegador no cancele el POST al navegar fuera, y `sendBeacon`\n * en el flush final (cierre de página), donde `fetch` puede morir con el documento;\n * - la promesa **rechaza** en fallo transitorio (red, 5xx, 408, 429) para que el\n * `AnalyticsManager` re-encole el lote; un 4xx se loguea y se descarta.\n */\nexport function createFeedbackSink(options: FeedbackSinkOptions): AnalyticsSink {\n let feedbackSessionId: string | undefined;\n /** Primer POST (aún sin sessionId) en vuelo: los siguientes lotes lo esperan. */\n let firstPostInFlight: Promise<void> | null = null;\n\n const url = `${options.baseUrl}/sdk/feedback`;\n\n const post = async (body: AnalyticsFeedbackBody, final: boolean, sessionEnd = false): Promise<void> => {\n const json = JSON.stringify(body);\n const small = json.length <= KEEPALIVE_MAX_BYTES;\n\n // Cierre de página: sendBeacon sobrevive al unload (a cambio, no hay respuesta que leer).\n if (final && options.sendBeaconImpl && small) {\n const payload = typeof Blob !== 'undefined' ? new Blob([json], { type: 'application/json' }) : json;\n if (options.sendBeaconImpl(url, payload)) {\n options.log?.('[DeepdotsAnalytics] final flush via sendBeacon');\n return;\n }\n options.log?.('[DeepdotsAnalytics] sendBeacon rejected the batch; falling back to fetch');\n }\n\n const f = options.fetchImpl ?? (typeof fetch !== 'undefined' ? fetch : undefined);\n if (!f) {\n options.log?.('[DeepdotsAnalytics] no fetch available; payload not sent', body);\n return;\n }\n\n const res = await f(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: json,\n // keepalive: el POST sobrevive a la navegación mientras el body sea pequeño.\n ...(small ? { keepalive: true } : {}),\n });\n\n if (!res.ok) {\n const detail = await safeBodyText(res);\n if (isRetryableStatus(res.status)) {\n // Rechazar → el manager re-encola el lote y se reintenta en el siguiente flush.\n throw new Error(`POST /sdk/feedback ${res.status} (retryable): ${detail}`);\n }\n options.log?.(\n `[DeepdotsAnalytics] POST /sdk/feedback rejected with ${res.status}; batch DISCARDED:`,\n detail,\n );\n return;\n }\n\n // El POST de cierre devuelve el sessionId del registro que acabamos de cerrar:\n // NO se re-cachea (si no, el lote siguiente volvería a apuntar al registro cerrado).\n if (sessionEnd) return;\n\n try {\n const data = (await res.json()) as { sessionId?: string };\n if (data?.sessionId && data.sessionId !== feedbackSessionId) {\n feedbackSessionId = data.sessionId;\n options.log?.('[DeepdotsAnalytics] feedbackSessionId cached:', feedbackSessionId);\n options.onSessionId?.(feedbackSessionId);\n }\n } catch {\n /* respuesta sin JSON — ignorar */\n }\n };\n\n return async (envelope: AnalyticsEnvelope, meta) => {\n const final = meta?.final === true;\n const sessionEnd = meta?.sessionEnd === true;\n\n // Sin sessionId todavía: esperar al primer POST para heredarlo y no partir el registro.\n // En el flush final no se espera (la página se está muriendo): mejor un registro\n // partido que perder el lote — el backend cose por user_id.\n if (!feedbackSessionId && firstPostInFlight && !final) {\n await firstPostInFlight;\n }\n\n const body = buildAnalyticsFeedbackBody(envelope, options.keys, feedbackSessionId, { sessionEnd });\n options.log?.('[DeepdotsAnalytics] POST /sdk/feedback →', body);\n\n const sent = post(body, final, sessionEnd);\n if (sessionEnd) {\n // Registro cerrado con `completed:true`: olvidar el sessionId para que el siguiente\n // lote lo OMITA y el backend abra uno nuevo (sesión nueva, o usuario nuevo).\n feedbackSessionId = undefined;\n firstPostInFlight = null;\n options.onSessionReset?.();\n } else if (!feedbackSessionId) {\n // nunca rechaza: es solo una barrera de orden, el fallo lo propaga `sent`\n firstPostInFlight = sent.then(\n () => undefined,\n () => undefined,\n );\n }\n await sent;\n };\n}\n","/**\n * Observador de navegación (Fase 2). Detecta cambios de pantalla y emite \"visitas\"\n * completadas (screen + entrada/salida/duración) cuando se abandona una pantalla.\n *\n * En Web se engancha a la History API (pushState/replaceState/popstate/hashchange).\n * La lógica de timing y normalización es pura y testeable sin DOM (vía `begin`/`visit`/`stop`\n * y un reloj inyectable).\n *\n * El consumidor (DeepdotsPopups) reenvía cada visita como un evento `page_view` por el\n * canal de analytics; el grafo se reconstruye en backend por orden + timestamp.\n */\n\nexport interface ScreenVisit {\n screen: string;\n entry: number;\n exit: number;\n durationSeconds: number;\n}\n\nexport type VisitListener = (visit: ScreenVisit) => void;\n\nconst UUID_PREFIX = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;\n\n/** Normaliza una URL a un nombre de pantalla: path (+hash route), sin query, IDs → :id. */\nexport function normalizeScreen(href: string): string {\n let pathAndHash: string;\n try {\n const u = new URL(href, 'http://deepdots.local');\n pathAndHash = u.pathname + (u.hash || '');\n } catch {\n pathAndHash = href;\n }\n // quitar query (incluida la que pueda venir dentro del hash route)\n pathAndHash = pathAndHash.replace(/\\?[^#]*/g, '');\n const collapsed = pathAndHash\n .split('/')\n .map((seg) => (/^[0-9]+$/.test(seg) || UUID_PREFIX.test(seg) ? ':id' : seg))\n .join('/');\n return collapsed || '/';\n}\n\nexport interface NavigationObserverOptions {\n now?: () => number;\n}\n\nexport class NavigationObserver {\n private now: () => number;\n private listeners = new Set<VisitListener>();\n private currentScreen: string | null = null;\n private entryAt = 0;\n private installed = false;\n private domHandlers: Array<() => void> = [];\n\n constructor(options: NavigationObserverOptions = {}) {\n this.now = options.now ?? (() => Date.now());\n }\n\n onVisit(listener: VisitListener): void {\n this.listeners.add(listener);\n }\n\n /** Fija la pantalla inicial (sin emitir visita; su duración se cierra al salir). */\n begin(href: string): void {\n this.currentScreen = normalizeScreen(href);\n this.entryAt = this.now();\n }\n\n /** Navega a una pantalla nueva: cierra la anterior (emite visita) y abre la nueva. */\n visit(href: string): void {\n const next = normalizeScreen(href);\n if (next === this.currentScreen) return; // misma pantalla → no es una visita nueva\n this.closeCurrent();\n this.currentScreen = next;\n this.entryAt = this.now();\n }\n\n /** Cierra la pantalla actual emitiendo su visita (p. ej. al cerrar la app/pestaña). */\n stop(): void {\n this.closeCurrent();\n this.currentScreen = null;\n }\n\n private closeCurrent(): void {\n if (this.currentScreen == null) return;\n const exit = this.now();\n const visit: ScreenVisit = {\n screen: this.currentScreen,\n entry: this.entryAt,\n exit,\n durationSeconds: Math.max(0, Math.round((exit - this.entryAt) / 1000)),\n };\n this.listeners.forEach((l) => {\n try {\n l(visit);\n } catch {\n /* listener no debe romper la navegación */\n }\n });\n }\n\n /** Instala los hooks de History en el navegador y registra la pantalla actual. */\n install(): void {\n if (this.installed || typeof window === 'undefined' || typeof history === 'undefined') return;\n this.installed = true;\n\n const handle = () => this.visit(window.location.href);\n\n const originalPush = history.pushState.bind(history);\n const originalReplace = history.replaceState.bind(history);\n history.pushState = (data: unknown, unused: string, url?: string | URL | null) => {\n const r = originalPush(data as never, unused, url as never);\n handle();\n return r;\n };\n history.replaceState = (data: unknown, unused: string, url?: string | URL | null) => {\n const r = originalReplace(data as never, unused, url as never);\n handle();\n return r;\n };\n window.addEventListener('popstate', handle);\n window.addEventListener('hashchange', handle);\n\n this.domHandlers.push(() => {\n history.pushState = originalPush;\n history.replaceState = originalReplace;\n window.removeEventListener('popstate', handle);\n window.removeEventListener('hashchange', handle);\n });\n\n this.begin(window.location.href);\n }\n\n uninstall(): void {\n this.domHandlers.forEach((off) => off());\n this.domHandlers = [];\n this.installed = false;\n }\n}\n","/**\n * Device info para el context de analytics (Technology #11–13).\n *\n * En Web enviamos `device_type` + `user_agent` crudo y dejamos que el backend\n * derive OS/navegador del UA (como hace GA). Los campos adicionales (timezone,\n * referrer, viewport, etc.) se recogen de las APIs del navegador sin permisos.\n * `country`/`city` se rellenan de forma asíncrona (ver geo-info.ts).\n * En KMP las APIs nativas dan os_version/device_model reales.\n */\n\nexport interface DeviceInfo {\n device_type: 'mobile' | 'tablet' | 'desktop';\n os_version?: string;\n device_model?: string;\n app_version?: string;\n user_agent?: string;\n timezone?: string;\n referrer?: string;\n viewport_size?: string;\n screen_resolution?: string;\n pixel_ratio?: number;\n entry_type?: string;\n page_load_ms?: number;\n connection_type?: string;\n country?: string;\n city?: string;\n}\n\nexport function parseDeviceType(ua: string): 'mobile' | 'tablet' | 'desktop' {\n const s = (ua || '').toLowerCase();\n if (/ipad|tablet|playbook|silk/.test(s) || (/android/.test(s) && !/mobile/.test(s))) {\n return 'tablet';\n }\n if (/mobi|iphone|ipod|windows phone/.test(s) || (/android/.test(s) && /mobile/.test(s))) {\n return 'mobile';\n }\n return 'desktop';\n}\n\nfunction getEntryType(): string | undefined {\n if (typeof performance === 'undefined') return undefined;\n const entries = performance.getEntriesByType('navigation');\n if (!entries.length) return undefined;\n return (entries[0] as PerformanceNavigationTiming).type;\n}\n\nfunction getPageLoadMs(): number | undefined {\n if (typeof performance === 'undefined') return undefined;\n const entries = performance.getEntriesByType('navigation');\n if (!entries.length) return undefined;\n const load = (entries[0] as PerformanceNavigationTiming).loadEventEnd;\n return load > 0 ? Math.round(load) : undefined;\n}\n\nfunction getConnectionType(): string | undefined {\n if (typeof navigator === 'undefined') return undefined;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (navigator as any).connection?.effectiveType ?? undefined;\n}\n\nexport function collectDeviceInfo(appVersion?: string): DeviceInfo {\n const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n return {\n device_type: parseDeviceType(ua),\n user_agent: ua || undefined,\n app_version: appVersion,\n timezone: typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions().timeZone : undefined,\n referrer: typeof document !== 'undefined' && document.referrer ? document.referrer : undefined,\n viewport_size:\n typeof window !== 'undefined' ? `${window.innerWidth}x${window.innerHeight}` : undefined,\n screen_resolution:\n typeof screen !== 'undefined' ? `${screen.width}x${screen.height}` : undefined,\n pixel_ratio: typeof window !== 'undefined' ? window.devicePixelRatio : undefined,\n entry_type: getEntryType(),\n page_load_ms: getPageLoadMs(),\n connection_type: getConnectionType(),\n };\n}\n","/**\n * Geolocalización por IP (Option B: client-side) robusta.\n *\n * Robustez:\n * - CACHE persistente en `KeyValueStorage` con TTL: en arranques posteriores el país/ciudad\n * está disponible de inmediato (sin llamada, sin gap de timing) y ahorra cuota del proveedor.\n * - CADENA de proveedores con fallback: si uno está bloqueado (ad-blocker), rate-limited (429)\n * o caído, se prueba el siguiente.\n * - TIMEOUT por proveedor vía `AbortController` para no colgarse en uno lento.\n *\n * Todo es fire-and-forget y tolerante a fallos: ante cualquier error devuelve `null`.\n */\n\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\n\nexport interface GeoInfo {\n country?: string;\n city?: string;\n}\n\n/** Clave de cache en storage. */\nexport const GEO_STORAGE_KEY = 'deepdots.geo';\n/** El país/ciudad casi no cambian: cache de 30 días. */\nexport const GEO_TTL_MS = 30 * 24 * 60 * 60 * 1000;\n/** Timeout por proveedor. */\nexport const GEO_TIMEOUT_MS = 3000;\n\ninterface CachedGeo extends GeoInfo {\n ts: number;\n}\n\n/** Lee el geo cacheado si sigue fresco (no supera el TTL). Tolerante a JSON corrupto. */\nexport function readCachedGeo(storage: KeyValueStorage, now: number): GeoInfo | null {\n try {\n const raw = storage.getItem(GEO_STORAGE_KEY);\n if (!raw) return null;\n const cached = JSON.parse(raw) as CachedGeo;\n if (typeof cached.ts !== 'number' || now - cached.ts > GEO_TTL_MS) return null;\n const geo: GeoInfo = {};\n if (cached.country) geo.country = cached.country;\n if (cached.city) geo.city = cached.city;\n return geo.country || geo.city ? geo : null;\n } catch {\n return null;\n }\n}\n\n/** Persiste el geo con la marca de tiempo actual. Tolerante a fallos de storage. */\nexport function writeCachedGeo(storage: KeyValueStorage, geo: GeoInfo, now: number): void {\n try {\n const payload: CachedGeo = { ...geo, ts: now };\n storage.setItem(GEO_STORAGE_KEY, JSON.stringify(payload));\n } catch {\n /* storage lleno / no disponible: se ignora */\n }\n}\n\nexport interface GeoProvider {\n url: string;\n parse: (data: Record<string, unknown>) => GeoInfo | null;\n}\n\n/** Proveedores en orden de preferencia. Todos devuelven JSON sin auth. */\nexport const DEFAULT_GEO_PROVIDERS: GeoProvider[] = [\n {\n // https://ipapi.co/json/\n url: 'https://ipapi.co/json/',\n parse: (d) => geoFrom(d.country_code, d.city),\n },\n {\n // https://ipwho.is/ (devuelve success:false con 200 en errores)\n url: 'https://ipwho.is/',\n parse: (d) => (d.success === false ? null : geoFrom(d.country_code, d.city)),\n },\n {\n // https://ipinfo.io/json (country = código ISO)\n url: 'https://ipinfo.io/json',\n parse: (d) => geoFrom(d.country, d.city),\n },\n];\n\nfunction geoFrom(country: unknown, city: unknown): GeoInfo | null {\n const c = typeof country === 'string' && country ? country : undefined;\n const t = typeof city === 'string' && city ? city : undefined;\n if (!c && !t) return null;\n return { country: c, city: t };\n}\n\nasync function fetchOne(provider: GeoProvider, f: typeof fetch, timeoutMs: number): Promise<GeoInfo | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await f(provider.url, { signal: controller.signal });\n if (!res.ok) return null;\n const data = (await res.json()) as Record<string, unknown>;\n return provider.parse(data);\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport interface FetchGeoOptions {\n fetchImpl?: typeof fetch;\n providers?: GeoProvider[];\n timeoutMs?: number;\n}\n\n/**\n * Intenta cada proveedor en orden (con timeout) y devuelve el primer geo válido.\n * `null` si ninguno responde. No lanza.\n */\nexport async function fetchGeo(options: FetchGeoOptions = {}): Promise<GeoInfo | null> {\n const f = options.fetchImpl ?? (typeof fetch !== 'undefined' ? fetch : undefined);\n if (!f) return null;\n const providers = options.providers ?? DEFAULT_GEO_PROVIDERS;\n const timeoutMs = options.timeoutMs ?? GEO_TIMEOUT_MS;\n for (const provider of providers) {\n const geo = await fetchOne(provider, f, timeoutMs);\n if (geo) return geo;\n }\n return null;\n}\n","/**\n * Resolución del idioma para el context de analytics.\n *\n * Prioridad: idioma explícito del host > `navigator.language` (web) > locale de `Intl`\n * (fallback automático que funciona en React Native con Hermes, donde `navigator.language`\n * no existe). Si nada resuelve, devuelve `undefined` y el campo se omite del metadata.\n */\n\n/** Forma mínima de `Intl` que necesitamos (inyectable para tests / entornos sin Intl). */\nexport interface IntlLike {\n DateTimeFormat: () => { resolvedOptions: () => { locale?: string } };\n}\n\nexport interface LanguageSources {\n /** Idioma forzado por el host (init.language). Máxima prioridad. */\n explicit?: string;\n /** `navigator` global (web). En RN suele no tener `language`. */\n navigator?: { language?: string };\n /** `Intl` global. Fallback cross-platform (web + RN/Hermes). */\n intl?: IntlLike;\n}\n\nfunction clean(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nexport function resolveLanguage(sources: LanguageSources): string | undefined {\n const explicit = clean(sources.explicit);\n if (explicit) return explicit;\n\n const navLang = clean(sources.navigator?.language);\n if (navLang) return navLang;\n\n try {\n const locale = clean(sources.intl?.DateTimeFormat().resolvedOptions().locale);\n if (locale) return locale;\n } catch {\n /* Intl no disponible o falló: se ignora */\n }\n\n return undefined;\n}\n","/**\n * Mide el tiempo ACTIVO (en primer plano), estilo GA4 (engagement time).\n * Acumula mientras está \"resumed\" (pestaña visible / app en foreground) y pausa en\n * background. `consume()` devuelve los ms activos desde la última lectura y reinicia,\n * dejando el timer corriendo. El consumidor emite un evento `user_engagement` con\n * `engagement_time_msec`; el backend lo suma por sesión → \"Average Time Spent per Session\" (#8).\n */\nexport class EngagementTracker {\n private now: () => number;\n private activeMs = 0;\n private lastResumeAt: number | null = null;\n\n constructor(now: () => number = () => Date.now()) {\n this.now = now;\n }\n\n /** Reanuda el conteo (pestaña visible / app en foreground). Idempotente. */\n resume(): void {\n if (this.lastResumeAt === null) this.lastResumeAt = this.now();\n }\n\n /** Pausa el conteo (pestaña oculta / app en background), acumulando el tramo activo. */\n pause(): void {\n if (this.lastResumeAt !== null) {\n this.activeMs += this.now() - this.lastResumeAt;\n this.lastResumeAt = null;\n }\n }\n\n /** Devuelve los ms activos acumulados desde la última lectura y reinicia (sin parar el timer). */\n consume(): number {\n if (this.lastResumeAt !== null) {\n const t = this.now();\n this.activeMs += t - this.lastResumeAt;\n this.lastResumeAt = t;\n }\n const ms = this.activeMs;\n this.activeMs = 0;\n return ms;\n }\n\n isActive(): boolean {\n return this.lastResumeAt !== null;\n }\n}\n","import type { KeyValueStorage } from '../tracking/tracking-manager';\nimport { STORAGE_KEYS } from '../tracking/tracking-manager';\n\n/** Valor de un atributo de contact (info interna del usuario que solo conoce el host). */\nexport type ContactAttributeValue = string | number | boolean;\nexport type ContactAttributes = Record<string, ContactAttributeValue>;\n\n/** Body de `POST /sdk/popups/contact`. */\nexport interface ContactBody {\n publicKey: string;\n userId: string;\n userAttributes: ContactAttributes;\n}\n\nexport interface ContactManagerOptions {\n storage: KeyValueStorage;\n publicKey: string;\n userId: string;\n /** Envío real (inyectable para tests): hace el `POST /sdk/popups/contact`. */\n post: (body: ContactBody) => Promise<void>;\n}\n\n/** Serialización estable (claves ordenadas) para el diff contra lo último enviado. */\nfunction stableSerialize(attrs: ContactAttributes): string {\n const sorted: ContactAttributes = {};\n for (const key of Object.keys(attrs).sort()) {\n sorted[key] = attrs[key];\n }\n return JSON.stringify(sorted);\n}\n\n/**\n * Gestiona los atributos de contact del usuario identificado (`userId` del init).\n * Solo envía cuando cambian respecto a lo último persistido en storage, para ahorrar\n * peticiones (el host puede llamar `setAttributes` en cada identificación sin miedo).\n */\nexport class ContactManager {\n private readonly storage: KeyValueStorage;\n private readonly publicKey: string;\n private readonly userId: string;\n private readonly post: (body: ContactBody) => Promise<void>;\n\n constructor(options: ContactManagerOptions) {\n this.storage = options.storage;\n this.publicKey = options.publicKey;\n this.userId = options.userId;\n this.post = options.post;\n }\n\n /**\n * Envía los atributos si cambiaron respecto a lo último enviado.\n * @returns `true` si se envió, `false` si no hubo cambios.\n */\n async setAttributes(attributes: ContactAttributes): Promise<boolean> {\n const serialized = stableSerialize(attributes);\n const last = this.storage.getItem(STORAGE_KEYS.contactAttributes);\n if (last === serialized) return false;\n\n await this.post({\n publicKey: this.publicKey,\n userId: this.userId,\n userAttributes: attributes,\n });\n this.storage.setItem(STORAGE_KEYS.contactAttributes, serialized);\n return true;\n }\n}\n","/**\n * Crash & error reporting (Stability #14–17) — captura de errores GESTIONADOS.\n *\n * - `reportError()`: API pública para el host (app viva) → emite `deepdots_app_crash` ya.\n * - handlers de `window` (errores no capturados): persisten a disco y se reenvían\n * en el siguiente arranque (el proceso puede morir antes del flush normal).\n *\n * El contexto de device/sesión se captura EN EL MOMENTO del crash y se guarda en el\n * record — no se lee del envelope en el replay (el replay puede ocurrir en otra\n * app_version/OS). Las dims OS/device en Web suelen ir vacías (se derivan del UA en backend).\n */\n\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\n\nexport type CrashSeverity = 'fatal' | 'error' | 'warning';\n\nexport interface CrashRecord {\n crashedAt: number;\n crashType: string;\n message: string;\n stack: string;\n fatal: boolean;\n handled: boolean;\n severity: CrashSeverity;\n sessionId: string | null;\n appVersion?: string;\n osVersion?: string;\n deviceModel?: string;\n context?: Record<string, string>;\n}\n\nexport interface ReportErrorOptions {\n severity?: CrashSeverity;\n handled?: boolean;\n context?: Record<string, unknown>;\n}\n\nexport interface DeviceSnapshot {\n appVersion?: string;\n osVersion?: string;\n deviceModel?: string;\n}\n\n/** Forma mínima de `global.ErrorUtils` de React Native. */\nexport interface ReactNativeErrorUtils {\n getGlobalHandler?(): ((error: unknown, isFatal?: boolean) => void) | undefined;\n setGlobalHandler(handler: (error: unknown, isFatal?: boolean) => void): void;\n}\n\nexport interface CrashReporterOptions {\n storage: KeyValueStorage;\n /** Emite un evento deepdots_app_crash AHORA (app viva). */\n emit: (params: Record<string, unknown>) => void;\n /** Snapshot de device en el momento del crash. */\n device: () => DeviceSnapshot;\n /** session_id en el momento del crash. */\n sessionId: () => string | null;\n now?: () => number;\n /** Kill-switch de consentimiento. */\n enabled?: () => boolean;\n}\n\nconst STACK_MAX = 8000;\nconst QUEUE_KEY = 'deepdots.crash.queue';\nconst MAX_QUEUED = 20;\n\n/** Convierte un CrashRecord en los params del evento `deepdots_app_crash` (omite undefined). */\nexport function crashRecordToParams(r: CrashRecord): Record<string, unknown> {\n const params: Record<string, unknown> = {\n crashed_at: r.crashedAt,\n crash_type: r.crashType,\n message: r.message,\n stack: r.stack,\n fatal: r.fatal,\n handled: r.handled,\n severity: r.severity,\n };\n if (r.sessionId) params.crashed_session_id = r.sessionId;\n if (r.appVersion) params.crashed_app_version = r.appVersion;\n if (r.osVersion) params.crashed_os_version = r.osVersion;\n if (r.deviceModel) params.crashed_device_model = r.deviceModel;\n if (r.context) {\n for (const [k, v] of Object.entries(r.context)) params[`ctx_${k}`] = v;\n }\n return params;\n}\n\nexport class CrashReporter {\n private options: CrashReporterOptions;\n private now: () => number;\n\n constructor(options: CrashReporterOptions) {\n this.options = options;\n this.now = options.now ?? (() => Date.now());\n }\n\n private isEnabled(): boolean {\n return this.options.enabled ? this.options.enabled() : true;\n }\n\n /** Construye un CrashRecord capturando contexto en el momento del crash. */\n private buildRecord(\n error: unknown,\n severity: CrashSeverity,\n handled: boolean,\n fatal: boolean,\n context?: Record<string, unknown>,\n ): CrashRecord {\n const err = error as { name?: string; message?: string; stack?: string };\n const isErr = error instanceof Error;\n const dev = this.options.device();\n const ctx = context\n ? Object.fromEntries(Object.entries(context).map(([k, v]) => [k, String(v)]))\n : undefined;\n return {\n crashedAt: this.now(),\n crashType: (isErr && err.name) || 'Error',\n message: isErr ? String(err.message ?? '') : String(error),\n stack: (isErr && typeof err.stack === 'string' ? err.stack : '').slice(0, STACK_MAX),\n fatal,\n handled,\n severity,\n sessionId: this.options.sessionId(),\n appVersion: dev.appVersion,\n osVersion: dev.osVersion,\n deviceModel: dev.deviceModel,\n context: ctx,\n };\n }\n\n /** API pública del host: reporta un error (app viva) → emite el evento ya. */\n reportError(error: unknown, options: ReportErrorOptions = {}): void {\n if (!this.isEnabled()) return;\n const severity = options.severity ?? 'error';\n const handled = options.handled ?? true;\n const fatal = severity === 'fatal';\n const record = this.buildRecord(error, severity, handled, fatal, options.context);\n this.options.emit(crashRecordToParams(record));\n }\n\n /** Captura un error no manejado (fatal) → persiste a disco para replay en el siguiente arranque. */\n captureUnhandled(error: unknown): void {\n this.persist(this.buildRecord(error, 'fatal', false, true));\n }\n\n /** Persiste un crash no capturado a disco para reenviarlo en el siguiente arranque. */\n private persist(record: CrashRecord): void {\n let queue: CrashRecord[] = [];\n try {\n const raw = this.options.storage.getItem(QUEUE_KEY);\n if (raw) queue = JSON.parse(raw) as CrashRecord[];\n if (!Array.isArray(queue)) queue = [];\n } catch {\n queue = [];\n }\n queue.push(record);\n if (queue.length > MAX_QUEUED) queue = queue.slice(queue.length - MAX_QUEUED);\n try {\n this.options.storage.setItem(QUEUE_KEY, JSON.stringify(queue));\n } catch {\n /* storage lleno / no disponible — se pierde el crash, no rompemos la app */\n }\n }\n\n /** Lee y vacía la cola de crashes pendientes (llamado en init para el replay). */\n drainPendingCrashes(): CrashRecord[] {\n let queue: CrashRecord[] = [];\n try {\n const raw = this.options.storage.getItem(QUEUE_KEY);\n if (raw) queue = JSON.parse(raw) as CrashRecord[];\n if (!Array.isArray(queue)) queue = [];\n } catch {\n queue = [];\n }\n if (queue.length) {\n try { this.options.storage.removeItem(QUEUE_KEY); } catch { /* noop */ }\n }\n return queue;\n }\n\n /** Instala los handlers globales de errores no capturados (no-op sin `window`). */\n install(): void {\n // En React Native `window` existe pero NO tiene addEventListener; comprobamos\n // la capacidad, no solo la existencia de `window`. En RN el host usa\n // installReactNative(ErrorUtils) para capturar los errores no manejados.\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') return;\n window.addEventListener('error', (e: ErrorEvent) => {\n if (!this.isEnabled()) return;\n this.captureUnhandled(e.error ?? e.message ?? 'error');\n });\n window.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => {\n if (!this.isEnabled()) return;\n this.captureUnhandled(e.reason ?? 'unhandledrejection');\n });\n }\n\n /** Engancha `global.ErrorUtils` de RN (no hay `window`): persiste y encadena al handler previo. */\n installReactNative(errorUtils: ReactNativeErrorUtils): void {\n const previous = errorUtils.getGlobalHandler?.();\n errorUtils.setGlobalHandler((error: unknown, isFatal?: boolean) => {\n if (this.isEnabled()) this.captureUnhandled(error);\n previous?.(error, isFatal);\n });\n }\n\n /** @internal Test seam: ejercita el camino de persistencia (window handlers no son testeables en vitest). */\n _persistForTest(error: unknown): void {\n this.captureUnhandled(error);\n }\n}\n","/**\n * Messaging (#18–22): tracking de notificaciones del host (push / in-app).\n * Un único evento reservado `deepdots_message` con un campo `stage` discriminador;\n * el funnel se correlaciona por `message_id` y se agrupa por `message_title`.\n */\n\nexport type MessageStage = 'delivered' | 'clicked' | 'converted';\n\n/** Canales válidos. Un mensaje tiene UN canal: se valida en runtime porque el host puede no ser TS. */\nexport const MESSAGE_CHANNELS = ['push', 'in_app'] as const;\nexport type MessageChannel = (typeof MESSAGE_CHANNELS)[number];\n\nexport interface TrackMessageOptions {\n /** Identificador del mensaje/notificación — correlaciona el funnel delivered→clicked→converted. */\n id: string;\n /** Título del mensaje — dimensión de agrupación de #18–22. */\n title: string;\n /** Canal de entrega. */\n channel: MessageChannel;\n /** Nombre de la campaña (opcional). */\n campaign?: string;\n /** Valor de conversión (opcional, típico en `stage: 'converted'`). */\n value?: number;\n currency?: string;\n /** Parámetros libres adicionales. */\n params?: Record<string, unknown>;\n}\n\n/** Construye los params del evento `deepdots_message` (omite opcionales ausentes). */\nexport function buildMessageParams(stage: MessageStage, o: TrackMessageOptions): Record<string, unknown> {\n const p: Record<string, unknown> = {\n stage,\n message_id: o.id,\n message_title: o.title,\n channel: o.channel,\n };\n if (o.campaign) p.campaign = o.campaign;\n if (o.value !== undefined) p.value = o.value;\n if (o.currency) p.currency = o.currency;\n return { ...p, ...(o.params ?? {}) };\n}\n\n/** Techo de `message_id` vigilados por sesión; los más antiguos se evictan (memoria acotada). */\nexport const MAX_TRACKED_MESSAGES = 500;\n\nexport type MessageRejectionReason = 'invalid_channel' | 'duplicate_stage' | 'channel_conflict';\n\nexport type MessageGuardVerdict =\n | { emit: true }\n | { emit: false; reason: MessageRejectionReason; detail: string };\n\n/**\n * Protege el funnel de Messaging de las formas imposibles que el host puede producir por error\n * (visto en producción: CTR > 100% por falta de `delivered` + el mismo `message_id` reportado en\n * dos canales). Tres reglas, todas dentro de la sesión (se reinicia en cada `init()`):\n *\n * 1. `channel` fuera de `MESSAGE_CHANNELS` → se descarta el evento.\n * 2. Un `(message_id, stage)` ya emitido → se emite una sola vez.\n * 3. Un `message_id` ya visto en un canal → se descartan los eventos de otro canal.\n *\n * Los eventos rechazados NO mutan el estado: un rechazo nunca \"consume\" el stage bueno.\n * Lo que el SDK no puede arreglar es la ausencia de `delivered`: eso lo instrumenta el host.\n */\nexport class MessageGuard {\n /** message_id → canal fijado + stages ya emitidos. El orden de inserción marca la eviction. */\n private seen = new Map<string, { channel: MessageChannel; stages: Set<MessageStage> }>();\n\n constructor(private maxTrackedMessages: number = MAX_TRACKED_MESSAGES) {}\n\n evaluate(stage: MessageStage, options: TrackMessageOptions): MessageGuardVerdict {\n const channel = options.channel;\n if (!(MESSAGE_CHANNELS as readonly string[]).includes(channel)) {\n return {\n emit: false,\n reason: 'invalid_channel',\n detail: `invalid channel \"${channel}\" (expected ${MESSAGE_CHANNELS.join(' | ')})`,\n };\n }\n\n const entry = this.seen.get(options.id);\n if (!entry) {\n this.remember(options.id, channel, stage);\n return { emit: true };\n }\n if (entry.channel !== channel) {\n return {\n emit: false,\n reason: 'channel_conflict',\n detail: `message_id \"${options.id}\" was already reported on channel \"${entry.channel}\"; discarding \"${channel}\"`,\n };\n }\n if (entry.stages.has(stage)) {\n return {\n emit: false,\n reason: 'duplicate_stage',\n detail: `stage \"${stage}\" was already sent for message_id \"${options.id}\"`,\n };\n }\n entry.stages.add(stage);\n return { emit: true };\n }\n\n /** Olvida todo lo vigilado (nueva sesión). */\n reset(): void {\n this.seen.clear();\n }\n\n private remember(id: string, channel: MessageChannel, stage: MessageStage): void {\n this.seen.set(id, { channel, stages: new Set([stage]) });\n while (this.seen.size > this.maxTrackedMessages) {\n const oldest = this.seen.keys().next();\n if (oldest.done) break;\n this.seen.delete(oldest.value);\n }\n }\n}\n","import {\n DeepdotsConfig,\n TriggerConfig,\n DeepdotsEvent,\n DeepdotsEventType,\n EventListener,\n DeepdotsInitParams,\n DeepdotsLogger,\n PopupDefinition,\n PopupTriggerCondition,\n PopupActions,\n PopupTrigger,\n PopupTriggerConditionStatus,\n POPUP_TRIGGER_CONDITION_STATUSES, POPUPSESSIONSTATUS,\n PopupStyle,\n SessionEndReason,\n} from '../types';\n// renderPopup se carga de forma PEREZOSA (dynamic import) para no arrastrar\n// `@magicfeedback/native` (browser) ni el CSS al importar el SDK en entornos sin DOM (React Native).\nimport { setupTrigger } from '../triggers';\nimport { resolveEnvironment } from '../config/env';\nimport { PopupRenderer, createDefaultRenderer } from '../platform/renderer';\nimport { TrackingManager, createDefaultStorage, type KeyValueStorage } from '../tracking/tracking-manager';\nimport { AnalyticsManager, createDryRunSink, type AnalyticsEnvelope } from '../analytics/analytics-manager';\nimport { createFeedbackSink } from '../analytics/feedback-payload';\nimport { NavigationObserver } from '../tracking/navigation-observer';\nimport { collectDeviceInfo } from '../analytics/device-info';\nimport { readCachedGeo, writeCachedGeo, fetchGeo } from '../analytics/geo-info';\nimport { resolveLanguage } from '../analytics/language';\nimport { EngagementTracker } from '../analytics/engagement-tracker';\nimport { ContactManager, type ContactAttributes } from '../contact/contact-manager';\nimport { CrashReporter, crashRecordToParams, type ReportErrorOptions, type ReactNativeErrorUtils } from '../analytics/crash-reporter';\nimport { setLogger } from '../util/logger';\nimport { buildMessageParams, MessageGuard, type MessageStage, type TrackMessageOptions } from '../analytics/messaging';\n\nconst EXIT_QUEUE_STORAGE_KEY = '__deepdots_exit_popup_queue__';\nconst DAY_IN_MS = 24 * 60 * 60 * 1000;\nconst ANALYTICS_MAX_BATCH_SIZE = 20;\nconst ANALYTICS_FLUSH_INTERVAL_MS = 30_000;\n/** Namespace reservado para los eventos que emite el propio SDK (page_view, message, mini_service…). */\nconst RESERVED_EVENT_PREFIX = 'deepdots_';\n/** Prefijo que se antepone a los eventos custom del host para poder identificarlos frente a los reservados. */\nconst CUSTOM_EVENT_PREFIX = 'deepdots_event_';\n\ninterface DeferredExitPopup {\n id: string;\n surveyId: string;\n dueAt: number;\n sourceUrl: string;\n}\n\ninterface LegacyPopupTriggerCondition {\n answered?: boolean;\n cooldownDays?: number;\n}\n\ninterface PopupProgressState {\n status: PopupTriggerConditionStatus;\n timestamp: number;\n}\n\ntype NormalizedPopupDefinition = PopupDefinition & {\n legacyConditions?: LegacyPopupTriggerCondition[];\n};\n\n/**\n * Main class for managing survey popups\n */\nexport class DeepdotsPopups {\n private config: DeepdotsConfig | null = null;\n private listeners: Map<DeepdotsEventType, Set<EventListener>> = new Map();\n private triggers: TriggerConfig[] = [];\n private initialized = false;\n private renderer: PopupRenderer = createDefaultRenderer();\n /** Logger para el output de debug. Default `console`; el host puede inyectar el suyo en init(). */\n private logger: DeepdotsLogger = console;\n private popupContainer: HTMLElement | null = null; // deprecated: mantenido para compatibilidad interna\n private popupDefinitions: NormalizedPopupDefinition[] = [];\n private popupsLoaded = false;\n private pendingAutoLaunch = false;\n\n private answeredSurveys: Set<string> = new Set();\n private surveyProgress: Map<string, PopupProgressState> = new Map();\n private lastShown: Map<string, number> = new Map(); // popupId -> timestamp mostrado\n private surveyToPopupId: Map<string, string> = new Map(); // surveyId -> popupId\n private deferredExitTimers: number[] = [];\n\n private baseUrl: string = '';\n private env: 'production' | 'development' = 'production';\n\n /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */\n private tracking: TrackingManager | null = null;\n /** Capa de analytics (canal separado del feedback). Null hasta init(). */\n private analytics: AnalyticsManager | null = null;\n /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */\n private analyticsFeedbackSessionId: string | undefined = undefined;\n /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */\n private analyticsFlushTimer: ReturnType<typeof setInterval> | undefined = undefined;\n /** Crash & error reporting (#14–17). Null hasta init(). */\n private crashReporter: CrashReporter | null = null;\n /** Protecciones del funnel de Messaging (#18–22). Vigencia de sesión: se reinicia en init(). */\n private messageGuard = new MessageGuard();\n /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */\n private navObserver: NavigationObserver | null = null;\n /** Tiempo activo (engagement time, #8). */\n private engagement: EngagementTracker | null = null;\n /** Atributos internos del usuario identificado → POST /sdk/popups/contact. Null si no hay userId. */\n private contact: ContactManager | null = null;\n /** Storage resuelto en init() (inyectado por el host o localStorage). Reusado al cambiar de usuario. */\n private storage: KeyValueStorage | null = null;\n /** Marca si ya se inició la navegación manual (setScreen) — para RN sin History API. */\n private navStarted = false;\n /**\n * `true` entre `session_start` y `session_end`. Hace idempotente tanto la apertura como\n * el cierre (p. ej. `visibilitychange` + `pagehide` seguidos) y permite abrir una sesión\n * nueva al volver a foreground o al conceder el consentimiento más tarde.\n */\n private sessionOpen = false;\n /**\n * Idioma resuelto en init() (explícito > navigator.language > Intl). Única fuente de\n * verdad para el context de analytics Y para la segmentación por idioma de los popups.\n */\n private language: string | undefined = undefined;\n\n /** Initialize the SDK with configuration */\n init(config: DeepdotsInitParams): void {\n this.logger = config.logger ?? console;\n setLogger(this.logger); // los módulos sin `this` (renderPopup, renderers) enrutan por aquí\n if (this.initialized) {\n // Un init() con OTRO userId es un CAMBIO DE USUARIO (login/logout del host): cierra\n // la sesión anterior y cambia la identidad en caliente. Reinicializar de verdad\n // duplicaría listeners, timers y la carga de popups.\n const nextUserId = config.userId || undefined;\n if (nextUserId !== this.config?.userId) {\n this.setUserId(nextUserId);\n return;\n }\n this.log('SDK already initialized');\n return;\n }\n\n const env = resolveEnvironment(config.nodeEnv);\n\n this.baseUrl = env.apiBaseUrl;\n this.env = config.nodeEnv || 'production';\n\n this.config = {\n apiKey: config.apiKey || undefined,\n debug: config.debug || false,\n userId: config.userId || undefined,\n } as DeepdotsConfig;\n\n // user_id lo gestiona el SDK (persistente); el session_id lo provee el backend\n // (respuesta de POST /sdk/popups) y se cachea — el SDK no genera ni expira sesiones.\n const storage = config.storage ?? createDefaultStorage();\n this.storage = storage;\n this.tracking = new TrackingManager({\n storage,\n clientUserId: this.config.userId,\n enabled: config.trackingEnabled ?? true,\n });\n\n // Contact: info interna del usuario que solo conoce el host (segmentación/targeting).\n // Solo trackeamos usuarios IDENTIFICADOS → se crea únicamente si hay userId del init.\n if (this.config.userId) {\n const userId = this.config.userId;\n const publicKey = this.config.apiKey ?? '';\n this.contact = new ContactManager({\n storage,\n publicKey,\n userId,\n post: (body) => this.postContact(body),\n });\n // Atributos de contact pasados en init: envío fire-and-forget (solo si cambiaron).\n if (config.contactAttributes) {\n void this.setContactAttributes(config.contactAttributes);\n }\n }\n this.log('tracking · user_id:', this.tracking.getUserId(), '· new_user:', this.tracking.isNewUser(), '· enabled:', this.tracking.isTrackingEnabled());\n\n // Analytics: se envía como Feedback a la integración (`POST /sdk/feedback`) si se\n // pasan claves en init.analytics; si no, queda en dry-run (solo console.log).\n const analyticsSink = config.analytics\n ? createFeedbackSink({\n baseUrl: this.baseUrl,\n keys: config.analytics,\n log: (...a) => this.log(...a),\n // sendBeacon: único transporte que sobrevive al cierre de la página.\n sendBeaconImpl:\n typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function'\n ? (url, body) => navigator.sendBeacon(url, body)\n : undefined,\n onSessionId: (id) => { this.analyticsFeedbackSessionId = id; },\n onSessionReset: () => { this.analyticsFeedbackSessionId = undefined; },\n })\n : createDryRunSink((...a) => this.logger.log(...a));\n const device = config.device ?? collectDeviceInfo(config.appVersion);\n this.language = resolveLanguage({\n explicit: config.language,\n navigator: typeof navigator !== 'undefined' ? navigator : undefined,\n intl: typeof Intl !== 'undefined' ? Intl : undefined,\n });\n this.messageGuard.reset();\n this.analytics = new AnalyticsManager({\n sink: analyticsSink,\n publicKey: config.analytics?.publicKey ?? this.config.apiKey,\n language: this.language,\n platform: config.platform ?? 'web',\n device,\n maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,\n onFlushNeeded: () => this.flushAnalytics(),\n });\n // Crash & error reporting (#14–17): captura errores no manejados (a disco, replay\n // en el siguiente arranque) y expone reportError() para el host (emite ya).\n this.crashReporter = new CrashReporter({\n storage,\n emit: (params) => this.track('deepdots_app_crash', params),\n device: () => ({ appVersion: device.app_version, osVersion: device.os_version, deviceModel: device.device_model }),\n sessionId: () => this.tracking?.getSessionId() ?? null,\n now: () => Date.now(),\n enabled: () => this.tracking?.isTrackingEnabled() ?? false,\n });\n if (this.tracking?.isTrackingEnabled()) {\n this.crashReporter.install();\n }\n // Geolocalización por IP: aplica el cache persistente de inmediato (sin gap de timing)\n // y refresca en background (cadena de proveedores + timeout), recacheando el resultado.\n const cachedGeo = readCachedGeo(storage, Date.now());\n if (cachedGeo) this.analytics.updateDevice(cachedGeo);\n fetchGeo()\n .then((geo) => {\n if (geo) {\n this.analytics?.updateDevice(geo);\n writeCachedGeo(storage, geo, Date.now());\n }\n })\n .catch(() => {});\n // Fase 2: navegación → eventos page_view por el canal de analytics.\n this.navObserver = new NavigationObserver();\n this.navObserver.onVisit((v) => this.track('deepdots_page_view', { screen: v.screen, duration_seconds: v.durationSeconds }));\n this.navObserver.install();\n // Engagement time (#8): cuenta tiempo activo en primer plano.\n this.engagement = new EngagementTracker();\n this.engagement.resume();\n this.setupAnalyticsFlush();\n // Marca de inicio de sesión (base para Crash-Free Users #14).\n this.openSession();\n // Drena SIEMPRE la cola (descarta los pendientes si tracking está off, spec §7);\n // solo reenvía como evento cuando el tracking está activo.\n const pendingCrashes = this.crashReporter.drainPendingCrashes();\n if (this.tracking?.isTrackingEnabled()) {\n for (const rec of pendingCrashes) {\n this.track('deepdots_app_crash', crashRecordToParams(rec));\n }\n }\n\n this.initialized = true;\n this.log('SDK initialized', this.config);\n if (this.renderer.init) this.renderer.init();\n this.setupPopupContainer();\n\n // Los popups SIEMPRE se reciben de la API (no se definen en init).\n this.fetchPopupsFromServer().then((defs) => {\n this.popupDefinitions = this.validatePopupDefinitions(defs);\n this.popupsLoaded = true;\n this.log('Popups loaded from API', this.popupDefinitions);\n this.configureTriggersFromDefinitions();\n this.processDeferredExitQueue();\n if (this.pendingAutoLaunch) {\n this.startTriggers();\n }\n });\n }\n\n /** User id actual (generado por el SDK o provisto por el cliente). Null si tracking off. */\n getUserId(): string | null {\n return this.tracking?.getUserId() ?? null;\n }\n\n /** Session id de navegación actual. Null si tracking off. */\n getSessionId(): string | null {\n return this.tracking?.getSessionId() ?? null;\n }\n\n /** Activa/desactiva el tracking (identidad + sesión + analytics). Kill-switch del contrato §7bis. */\n setTrackingEnabled(enabled: boolean): void {\n // Al revocar el consentimiento se cierra la sesión ANTES de apagar el canal: si no, lo\n // acumulado se quedaría sin enviar y el registro abierto sin cerrar.\n if (!enabled) this.closeSession('tracking_disabled');\n this.tracking?.setTrackingEnabled(enabled);\n // Consentimiento concedido (ahora o más tarde que el init): abre sesión.\n if (enabled) this.openSession();\n this.log('tracking · setTrackingEnabled:', enabled, '· session_id:', this.tracking?.getSessionId() ?? null);\n }\n\n // ───────── Analytics (canal separado del feedback, vinculado por user_id) ─────────\n\n /** Registra un evento de analítica (modelo GA: nombre + params). No-op si tracking off. */\n track(name: string, params?: Record<string, unknown>): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n // Los eventos del SDK ya van en el namespace `deepdots_`; los custom del host se\n // prefijan con `deepdots_event_` para poder distinguirlos en analítica.\n const eventName = name.startsWith(RESERVED_EVENT_PREFIX) ? name : `${CUSTOM_EVENT_PREFIX}${name}`;\n this.analytics?.track(eventName, params);\n this.log('analytics · track:', eventName, params ?? {});\n }\n\n /** User attributes para breakdowns (registration_status, pass_type, sector, pass_status…). */\n setUserAttributes(attributes: Record<string, string | number | boolean>): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.setUserAttributes(attributes);\n }\n\n /**\n * Registra/actualiza una métrica (valor medible) → `feedback.metrics` del body.\n * Persistente y sobrescribe por key. No-op si tracking off.\n */\n setMetric(key: string, value: string | number | boolean): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.setMetric(key, value);\n this.log('analytics · setMetric:', key, value);\n }\n\n /** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */\n reportError(error: unknown, options?: ReportErrorOptions): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.crashReporter?.reportError(error, options);\n }\n\n /** Engancha `global.ErrorUtils` de RN para capturar errores JS no manejados. No-op si tracking off. */\n installReactNativeCrashHandler(errorUtils: ReactNativeErrorUtils): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.crashReporter?.installReactNative(errorUtils);\n }\n\n /**\n * Info interna del usuario que solo conoce el host (idioma, edad, plan…), persistida en el\n * Contact del backend para segmentación/targeting de popups. Identificada por el `userId` del\n * init. Solo envía si los atributos cambiaron (diff en storage). No-op si tracking off o sin userId.\n * @returns `true` si se envió al backend, `false` si no hubo cambios o está deshabilitado.\n */\n async setContactAttributes(attributes: ContactAttributes): Promise<boolean> {\n if (!this.tracking?.isTrackingEnabled()) return false;\n if (!this.contact) return false;\n return this.contact.setAttributes(attributes);\n }\n\n /** Marca el inicio de un mini-service; etiqueta los eventos siguientes. No-op si tracking off. */\n enterMiniService(name: string, entryPointType?: string): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.enterMiniService(name, entryPointType);\n this.log('analytics · enterMiniService:', name, entryPointType ?? '');\n }\n\n /** Cierra el mini-service `name` (emite `mini_service_exit` con duración, #27). No-op si tracking off o si ese no está activo. */\n exitMiniService(name: string): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.exitMiniService(name);\n this.log('analytics · exitMiniService:', name);\n }\n\n /** Findability (#31/#35): registra una búsqueda. `has_results` se deriva de `resultsCount`. */\n trackSearch(query: string, resultsCount: number, params?: Record<string, unknown>): void {\n this.track('deepdots_search', { query, results_count: resultsCount, has_results: resultsCount > 0, ...(params ?? {}) });\n }\n\n /** Findability friction (#34/#35): señal de fricción con su `friction_topic`. */\n trackFindabilityFriction(frictionTopic: string, params?: Record<string, unknown>): void {\n this.track('deepdots_findability_friction', { friction_topic: frictionTopic, ...(params ?? {}) });\n }\n\n /** Funnel: un paso del embudo, correlacionado por `taskId`. El backend reconstruye conversión/drop-off/tiempo. */\n trackFunnelStep(funnel: string, step: string, taskId: string, params?: Record<string, unknown>): void {\n this.track('deepdots_funnel_step', { funnel, step, task_id: taskId, ...(params ?? {}) });\n }\n\n /**\n * Messaging (#18–22): registra una etapa del funnel de una notificación (push/in-app).\n * No-op si tracking off. El evento se descarta (con warning) si el `channel` no es válido,\n * si ese `(message_id, stage)` ya se emitió en la sesión, o si el `message_id` ya se reportó\n * en otro canal — ver `MessageGuard`.\n */\n trackMessage(stage: MessageStage, options: TrackMessageOptions): void {\n const verdict = this.messageGuard.evaluate(stage, options);\n if (!verdict.emit) {\n console.warn(`[DeepdotsPopups] trackMessage discarded (${verdict.reason}): ${verdict.detail}`);\n return;\n }\n this.track('deepdots_message', buildMessageParams(stage, options));\n }\n\n /**\n * Navegación MANUAL (entornos sin History API, p. ej. React Native con React Navigation).\n * El host la llama en cada cambio de pantalla; emite `page_view` al salir de la anterior.\n */\n setScreen(name: string): void {\n if (!this.navObserver) return;\n if (!this.navStarted) {\n this.navObserver.begin(name);\n this.navStarted = true;\n } else {\n this.navObserver.visit(name);\n }\n }\n\n /** App a foreground (RN: AppState 'active'): reanuda el engagement time y abre sesión nueva. */\n onForeground(): void {\n this.engagement?.resume();\n this.openSession();\n }\n\n /**\n * App a background (RN: AppState 'background'): FIN DE SESIÓN. Cierra pantalla actual\n * (page_view), mini-services (mini_service_exit), emite engagement + `session_end` y\n * envía el último lote con `completed:true`.\n *\n * Es la única señal disponible en móvil: el kill de la app (swipe) NO da callback.\n */\n onBackground(): void {\n this.closeSession('background');\n this.engagement?.pause();\n }\n\n /**\n * Cierre EXPLÍCITO de la sesión por parte del host (logout, fin de flujo…): emite\n * `session_end` y envía el último lote con `completed:true`.\n */\n endSession(): void {\n this.closeSession('manual');\n }\n\n /**\n * Cambio de usuario (login / logout / cambio de cuenta). Cierra la sesión del usuario\n * anterior (`session_end` con `reason: 'user_change'` + `completed:true`), cambia la\n * identidad y abre una sesión nueva. Sin `userId` vuelve al id anónimo del SDK.\n *\n * Equivale a llamar a `init()` otra vez con otro `userId`.\n */\n setUserId(userId?: string): void {\n const next = userId || undefined;\n if (!this.initialized || !this.config || next === this.config.userId) return;\n\n this.closeSession('user_change');\n\n const enabled = this.tracking?.isTrackingEnabled() ?? true;\n const storage = this.storage ?? createDefaultStorage();\n this.config.userId = next;\n this.tracking = new TrackingManager({ storage, clientUserId: next, enabled });\n // El Contact solo existe para usuarios identificados.\n this.contact = next\n ? new ContactManager({\n storage,\n publicKey: this.config.apiKey ?? '',\n userId: next,\n post: (body) => this.postContact(body),\n })\n : null;\n // Atributos, métricas y protecciones de messaging pertenecían al usuario anterior.\n this.analytics?.resetUserScope();\n this.messageGuard.reset();\n\n this.log('tracking · user_change · user_id:', this.tracking.getUserId());\n this.openSession();\n }\n\n /**\n * Cierra la sesión actual: vuelca todo lo que quedaba abierto y envía el último lote\n * marcado con `completed:true`, para que el backend cierre el registro. El `sessionId`\n * cacheado se olvida → el lote siguiente abre un registro nuevo.\n *\n * Idempotente: dos cierres seguidos (p. ej. `visibilitychange` + `pagehide`) no duplican\n * el `session_end`.\n */\n private closeSession(reason: SessionEndReason): void {\n if (!this.sessionOpen) return;\n if (!this.tracking?.isTrackingEnabled()) return;\n this.sessionOpen = false;\n\n this.navObserver?.stop(); // cierra la pantalla actual → page_view\n this.navStarted = false;\n this.analytics?.exitAllMiniServices(); // → mini_service_exit con duración\n this.flushEngagement(); // → user_engagement con el tiempo activo\n this.track('deepdots_session_end', { reason });\n // `final` solo cuando el documento se está muriendo: ahí hace falta sendBeacon.\n this.flushAnalytics({ final: reason === 'page_hide', sessionEnd: true });\n // El session_id del canal de analytics y el de popups pertenecían a la sesión cerrada.\n this.analyticsFeedbackSessionId = undefined;\n this.tracking?.setSessionId(null);\n this.log('tracking · session_end:', reason);\n }\n\n /**\n * Abre sesión (`session_start`) si no hay una abierta y el tracking está activo. Idempotente:\n * se llama en init(), al volver a foreground y al conceder el consentimiento.\n */\n private openSession(): void {\n if (this.sessionOpen) return;\n if (!this.tracking?.isTrackingEnabled()) return;\n this.sessionOpen = true;\n this.track('deepdots_session_start', {});\n }\n\n /** Payload que se ENVIARÍA al endpoint de analytics (no envía ni vacía el buffer). */\n previewAnalytics(): AnalyticsEnvelope {\n return (\n this.analytics?.buildPayload(this.analyticsIdentity()) ?? {\n userId: null,\n sessionId: null,\n context: { platform: 'web', attributes: {} },\n events: [],\n }\n );\n }\n\n /**\n * Envía el lote acumulado de analytics y vacía el buffer.\n * `final: true` (cierre de página/app) cambia el transporte a `sendBeacon`, que sobrevive\n * al unload; un lote que falle por red o 5xx se re-encola para el siguiente flush.\n */\n flushAnalytics(options?: { final?: boolean; sessionEnd?: boolean }): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n this.analytics?.flush(this.analyticsIdentity(), options);\n }\n\n /** Emite un evento `user_engagement` con el tiempo activo acumulado (#8). */\n private flushEngagement(): void {\n if (!this.tracking?.isTrackingEnabled()) return;\n const ms = this.engagement?.consume() ?? 0;\n if (ms > 0) this.track('deepdots_user_engagement', { engagement_time_msec: ms });\n }\n\n private analyticsIdentity() {\n return {\n userId: this.tracking?.getUserId() ?? null,\n sessionId: this.tracking?.getSessionId() ?? null,\n };\n }\n\n /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */\n private setupAnalyticsFlush(): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\n // flush periódico mientras la app está en primer plano\n this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);\n // al cerrar la página: FIN DE SESIÓN (page_view + mini_service_exit + engagement +\n // session_end) y último lote con completed:true vía sendBeacon.\n window.addEventListener('pagehide', () => {\n clearInterval(this.analyticsFlushTimer);\n this.closeSession('page_hide');\n });\n // al ocultar/mostrar la pestaña: pausar/reanudar engagement y enviar lo acumulado\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.flushEngagement();\n this.engagement?.pause();\n this.flushAnalytics();\n } else if (document.visibilityState === 'visible') {\n this.engagement?.resume();\n }\n });\n }\n\n /** Enable auto-launch functionality with configured triggers */\n autoLaunch(): void {\n if (!this.initialized) {\n throw new Error('SDK not initialized. Call init() first.');\n }\n if (!this.popupsLoaded) {\n this.pendingAutoLaunch = true;\n this.log('Auto-launch deferred until popups are loaded');\n return;\n }\n this.startTriggers();\n }\n\n /** Inicia los triggers configurados */\n private startTriggers(): void {\n this.log('Auto-launch enabled');\n this.triggers.forEach((trigger) => setupTrigger(this, trigger));\n }\n\n /** Configure triggers for auto-launching popups (manual) */\n configureTriggers(triggers: TriggerConfig[]): void {\n if (!this.initialized) {\n throw new Error('SDK not initialized. Call init() first.');\n }\n this.triggers = triggers;\n this.log('Triggers configured', triggers);\n }\n\n /** Deriva triggers desde las definiciones de popup */\n private configureTriggersFromDefinitions(): void {\n const derived: TriggerConfig[] = [];\n this.debug('Validated popup definitions', this.popupDefinitions);\n\n this.popupDefinitions.forEach((def) => {\n def.triggers.forEach((trigger) => {\n const type = this.mapPopupTriggerType(trigger.type);\n if (!type) {\n this.debug('Unsupported trigger type', trigger.type);\n return;\n }\n const value = type === 'time' && typeof trigger.value === 'number'\n ? trigger.value * 1000\n : trigger.value;\n derived.push({ type, value, surveyId: def.surveyId, popupId: def.id });\n });\n });\n\n if (derived.length) {\n this.configureTriggers(derived);\n }\n }\n\n /** Lógica para evaluar condiciones antes de mostrar una encuesta */\n triggerSurvey(surveyId: string, popupId?: string): void {\n const def = this.findPopupDefinition(surveyId, popupId);\n if (!def) {\n this.debug('No popup definition for trigger', { surveyId, popupId });\n return;\n }\n if (!this.shouldShow(def)) {\n this.debug('Conditions prevented showing popup', def.id);\n return;\n }\n this.showDefinition(def);\n }\n\n /** Trigger popups configured with trigger.type = 'event' and matching trigger.value */\n public triggerEvent(eventName: string): void {\n if (!this.initialized) {\n throw new Error('SDK not initialized. Call init() first.');\n }\n const normalized = String(eventName || '').trim();\n if (!normalized) {\n this.debug('Ignoring empty event trigger name');\n return;\n }\n\n const candidates = this.popupDefinitions.filter((def) => {\n return def.triggers.some((trigger) => {\n const triggerValue = String(trigger.value ?? '').trim();\n return trigger.type === 'event' && triggerValue === normalized;\n });\n });\n\n if (!candidates.length) {\n this.debug('No event popup definitions found', { eventName: normalized });\n return;\n }\n\n const matched = candidates.find((def) => this.shouldShow(def));\n if (!matched) {\n this.debug('Event popup blocked by conditions/segments', { eventName: normalized });\n return;\n }\n\n this.showDefinition(matched);\n }\n\n private showDefinition(def: PopupDefinition): void {\n this.log('Showing popup (definition)', def);\n this.surveyToPopupId.set(def.surveyId, def.id);\n this.lastShown.set(def.id, Date.now());\n this.renderPopup(def.surveyId, def.productId, def.actions, def.style);\n this.emitEvent('popup_shown', def.surveyId, { popupId: def.id });\n }\n\n private shouldShow(def: NormalizedPopupDefinition, pathUrl?: string, skipPathCheck = false): boolean {\n if (!skipPathCheck && !this.matchesSegmentsPath(def, pathUrl)) {\n return false;\n }\n if (!this.matchesSegmentsLang(def)) {\n return false;\n }\n\n const cooldowns = def.cooldown || [];\n if (cooldowns.length && !cooldowns.every((cooldown) => this.evaluateCooldown(def, cooldown))) {\n return false;\n }\n\n const legacyConditions = def.legacyConditions || [];\n if (!legacyConditions.length) return true;\n return legacyConditions.every((condition) => this.evaluateLegacyCondition(def, condition));\n }\n\n private matchesSegmentsLang(def: PopupDefinition): boolean {\n const langs = def.segments?.lang;\n if (!langs || langs.length === 0) return true;\n\n // Mismo idioma que reporta el context de analytics: explícito > navigator.language\n // > Intl. ⚠️ NO leer `navigator.language` aquí: en React Native `navigator` existe\n // pero no tiene `language`, y `undefined.toLowerCase()` reventaba la evaluación\n // COMPLETA del trigger (no solo este popup).\n const currentLang = (this.language ?? '').toLowerCase();\n\n if (!currentLang) {\n this.debug('No language available for lang comparison', { popupId: def.id, langs });\n return true;\n }\n\n // Acepta si el idioma actual empieza con alguno de los configurados\n // ej: \"en-US\" coincide con segment \"en\". Ignora entradas no-string del backend.\n const matches = langs.some(\n (lang) => typeof lang === 'string' && currentLang.startsWith(lang.toLowerCase()),\n );\n if (!matches) {\n this.debug('Lang mismatch for popup', { popupId: def.id, langs, currentLang });\n }\n return matches;\n }\n\n private matchesSegmentsPath(def: PopupDefinition, pathUrl?: string): boolean {\n const paths = def.segments?.path;\n if (!paths || paths.length === 0) return true;\n if (typeof window === 'undefined' || !window.location) {\n this.debug('No window.location available for path comparison', { popupId: def.id, paths });\n return true;\n }\n\n const normalizedHref = this.normalizeUrl(pathUrl || window.location.href || '');\n const currentUrl = this.safeParseUrl(normalizedHref);\n const normalizedPath = this.normalizeUrl(currentUrl?.pathname || window.location.pathname || '');\n\n const matches = paths.some((rawCandidate) => {\n const candidate = this.normalizeUrl(rawCandidate);\n let match = false;\n if (candidate.startsWith('http://') || candidate.startsWith('https://')) {\n match = normalizedHref === candidate;\n } else if (candidate.startsWith('/')) {\n match = normalizedHref.includes(candidate);\n } else {\n match = normalizedPath === candidate;\n }\n this.debug('Path comparison', {\n popupId: def.id,\n candidate,\n currentPath: normalizedPath,\n currentHref: normalizedHref,\n match,\n });\n return match;\n });\n\n if (!matches) {\n this.debug('No path match for popup', { popupId: def.id, paths, currentPath: normalizedPath });\n }\n\n return matches;\n }\n\n private evaluateCooldown(def: PopupDefinition, condition: PopupTriggerCondition): boolean {\n switch (condition.answered) {\n case 'SHOWED':\n return this.hasCooldownElapsed(this.lastShown.get(def.id), condition.cooldownDays);\n case 'PARTIAL':\n case 'COMPLETED': {\n const progress = this.surveyProgress.get(def.surveyId);\n if (!progress || progress.status !== condition.answered) {\n return true;\n }\n return this.hasCooldownElapsed(progress.timestamp, condition.cooldownDays);\n }\n default:\n return true;\n }\n }\n\n private evaluateLegacyCondition(def: PopupDefinition, condition: LegacyPopupTriggerCondition): boolean {\n if (!condition.answered && this.answeredSurveys.has(def.surveyId)) {\n return false;\n }\n return this.hasCooldownElapsed(this.lastShown.get(def.id), condition.cooldownDays);\n }\n\n private hasCooldownElapsed(timestamp: number | undefined, cooldownDays?: number): boolean {\n if (!timestamp || !cooldownDays) {\n return true;\n }\n return Date.now() - timestamp >= cooldownDays * DAY_IN_MS;\n }\n\n /** Marcar encuesta como contestada externamente */\n public markSurveyAnswered(surveyId: string): void {\n this.answeredSurveys.add(surveyId);\n this.markSurveyProgress(surveyId, 'COMPLETED');\n }\n\n /** Queue an exit popup so it can render after navigation */\n public queueExitPopup(surveyId: string, delaySeconds: number, sourceUrl?: string, popupId?: string): void {\n const def = this.findPopupDefinition(surveyId, popupId);\n if (!def) {\n this.debug('No popup definition for exit trigger', { surveyId, popupId });\n return;\n }\n\n const originUrl = sourceUrl || (typeof window !== 'undefined' ? window.location.href : '');\n if (!originUrl) {\n this.debug('Exit popup skipped: missing source URL', { surveyId });\n return;\n }\n\n if (!this.shouldShow(def, originUrl)) {\n this.debug('Exit popup skipped by conditions/path', { popupId: def.id, sourceUrl: originUrl });\n return;\n }\n\n const safeDelayMs = Number.isFinite(delaySeconds) ? Math.max(0, delaySeconds * 1000) : 0;\n const deferred: DeferredExitPopup = {\n id: def.id,\n surveyId: def.surveyId,\n sourceUrl: this.normalizeUrl(originUrl),\n dueAt: Date.now() + safeDelayMs,\n };\n\n const queue = this.getDeferredExitQueue().filter((item) => !(item.id === deferred.id && item.sourceUrl === deferred.sourceUrl));\n queue.push(deferred);\n this.setDeferredExitQueue(queue);\n this.scheduleDeferredExitPopup(deferred);\n this.debug('Exit popup queued', deferred);\n }\n\n private processDeferredExitQueue(): void {\n const queue = this.getDeferredExitQueue();\n if (!queue.length) return;\n\n const pending: DeferredExitPopup[] = [];\n const now = Date.now();\n queue.forEach((item) => {\n const def = this.popupDefinitions.find((popup) => popup.id === item.id && popup.surveyId === item.surveyId);\n if (!def) {\n return;\n }\n if (item.dueAt <= now) {\n this.tryShowDeferredExitPopup(item);\n return;\n }\n pending.push(item);\n this.scheduleDeferredExitPopup(item);\n });\n this.setDeferredExitQueue(pending);\n }\n\n private scheduleDeferredExitPopup(item: DeferredExitPopup): void {\n if (typeof window === 'undefined') return;\n const delay = Math.max(0, item.dueAt - Date.now());\n const timer = window.setTimeout(() => this.tryShowDeferredExitPopup(item), delay);\n this.deferredExitTimers.push(timer);\n }\n\n private tryShowDeferredExitPopup(item: DeferredExitPopup): void {\n const def = this.popupDefinitions.find((popup) => popup.id === item.id && popup.surveyId === item.surveyId);\n if (!def) {\n this.removeDeferredExitPopup(item);\n return;\n }\n\n const currentUrl = typeof window !== 'undefined' ? this.normalizeUrl(window.location.href || '') : '';\n if (!currentUrl || currentUrl === this.normalizeUrl(item.sourceUrl)) {\n this.removeDeferredExitPopup(item);\n this.debug('Exit popup dropped because route did not change', item);\n return;\n }\n\n if (!this.shouldShow(def, undefined, true)) {\n this.removeDeferredExitPopup(item);\n this.debug('Exit popup skipped at render-time conditions', item);\n return;\n }\n\n this.showDefinition(def);\n this.removeDeferredExitPopup(item);\n }\n\n private findPopupDefinition(surveyId: string, popupId?: string): NormalizedPopupDefinition | undefined {\n if (popupId) {\n const byId = this.popupDefinitions.find((popup) => popup.id === popupId);\n if (byId) return byId;\n }\n return this.popupDefinitions.find((popup) => popup.surveyId === surveyId);\n }\n\n private removeDeferredExitPopup(item: DeferredExitPopup): void {\n const queue = this.getDeferredExitQueue().filter((entry) => !(entry.id === item.id && entry.sourceUrl === item.sourceUrl));\n this.setDeferredExitQueue(queue);\n }\n\n private getDeferredExitQueue(): DeferredExitPopup[] {\n if (typeof window === 'undefined') return [];\n try {\n const raw = window.sessionStorage.getItem(EXIT_QUEUE_STORAGE_KEY);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed\n .filter((entry: unknown): entry is DeferredExitPopup => {\n if (typeof entry !== 'object' || entry === null) return false;\n const item = entry as Partial<DeferredExitPopup>;\n return typeof item.id === 'string'\n && typeof item.surveyId === 'string'\n && typeof item.sourceUrl === 'string'\n && typeof item.dueAt === 'number'\n && Number.isFinite(item.dueAt);\n })\n .map((item) => ({\n ...item,\n sourceUrl: this.normalizeUrl(item.sourceUrl),\n }));\n } catch {\n return [];\n }\n }\n\n private setDeferredExitQueue(queue: DeferredExitPopup[]): void {\n if (typeof window === 'undefined') return;\n try {\n if (!queue.length) {\n window.sessionStorage.removeItem(EXIT_QUEUE_STORAGE_KEY);\n return;\n }\n window.sessionStorage.setItem(EXIT_QUEUE_STORAGE_KEY, JSON.stringify(queue));\n } catch {\n // Ignore storage errors in host environments that block storage access.\n }\n }\n\n /** Fetch al servidor para obtener popups */\n private async fetchPopupsFromServer(): Promise<unknown[]> {\n const apiKey = this.config?.apiKey;\n const baseUrl = this.baseUrl;\n const userId = this.config?.userId;\n if (!apiKey || !baseUrl) {\n this.log('Missing apiKey or baseUrl. Skipping popups fetch.');\n return [];\n }\n const filter = userId ? { where: { userId } } : null;\n const query = filter ? `?filter=${encodeURIComponent(JSON.stringify(filter))}` : '';\n const endpoint = `${baseUrl}/sdk/${encodeURIComponent(apiKey)}/popups${query}`;\n try {\n const response = await fetch(endpoint);\n if (!response.ok) {\n this.log('Failed to fetch popups', response.status, response.statusText);\n return [];\n }\n const raw = await response.text();\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n this.log('Unexpected popups payload', parsed);\n return [];\n }\n this.debug('Fetched popups payload', parsed);\n return parsed;\n } catch (error) {\n this.log('Error fetching popups', error);\n return [];\n }\n }\n\n /** Envía los atributos de contact a la API (`POST /sdk/popups/contact`). */\n private async postContact(body: { publicKey: string; userId: string; userAttributes: ContactAttributes }): Promise<void> {\n const baseUrl = this.baseUrl;\n if (!baseUrl) return;\n const endpoint = `${baseUrl}/sdk/popups/contact`;\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n this.log('Failed to post contact', response.status, response.statusText);\n return;\n }\n this.log('contact · sent attributes for user:', body.userId);\n } catch (error) {\n this.log('Error posting contact', error);\n }\n }\n\n /** Notifica eventos de popup a la API */\n private async postPopupEvent(status: POPUPSESSIONSTATUS, popupId: string, userId?: string): Promise<void> {\n const apiKey = this.config?.apiKey;\n const baseUrl = this.baseUrl;\n if (!apiKey || !baseUrl) {\n return;\n }\n const endpoint = `${baseUrl}/sdk/popups`;\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n publicKey: apiKey,\n status,\n popupId,\n userId: userId || undefined,\n }),\n });\n if (!response.ok) {\n this.log('Failed to post popup event', response.status, response.statusText);\n return;\n }\n // Contrato Fase 1 §6: el backend devuelve el sessionId; lo cacheamos para\n // inyectarlo en las respuestas de survey (lo cose por user_id).\n try {\n const data = await response.json();\n if (data && typeof data.sessionId === 'string') {\n this.tracking?.setSessionId(data.sessionId);\n this.log('tracking · session_id from backend:', data.sessionId);\n }\n } catch {\n // respuesta sin cuerpo JSON: nada que cachear\n }\n } catch (error) {\n this.log('Error posting popup event', error);\n }\n }\n\n /** Add an event listener */\n on(eventType: DeepdotsEventType, listener: EventListener): void {\n if (!this.listeners.has(eventType)) {\n this.listeners.set(eventType, new Set());\n }\n this.listeners.get(eventType)!.add(listener);\n }\n\n /** Remove an event listener */\n off(eventType: DeepdotsEventType, listener: EventListener): void {\n const listeners = this.listeners.get(eventType);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n\n /** Setup popup container element */\n private setupPopupContainer(): void {\n if (typeof document === 'undefined') {\n return;\n }\n this.popupContainer = document.createElement('div');\n this.popupContainer.id = 'deepdots-popup-container';\n this.popupContainer.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: none;\n z-index: 999999;\n background: rgba(0, 0, 0, 0.5);\n justify-content: center;\n align-items: center;\n `;\n document.body.appendChild(this.popupContainer);\n }\n\n /** Render the popup UI */\n private renderPopup(surveyId: string, productId: string, actions?: PopupActions, style?: PopupStyle): void {\n const userId = this.tracking?.getUserId() ?? this.config?.userId;\n const sessionId = this.tracking?.getSessionId() ?? undefined;\n const miniService = this.analytics?.getMiniService() ?? undefined;\n const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;\n this.log('tracking · survey identity →', { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });\n if (this.renderer) {\n this.renderer.show(\n surveyId,\n productId,\n actions,\n (type, id, payload) => this.emitEvent(type, id, payload),\n () => this.hidePopup(),\n this.env,\n userId,\n style,\n sessionId,\n miniService,\n analyticsFeedbackSessionId,\n );\n return;\n }\n\n if (!this.popupContainer) return;\n const container = this.popupContainer;\n void import('../ui/renderPopup').then(({ renderPopup }) => {\n renderPopup(\n container,\n surveyId,\n productId,\n actions,\n (type, id, payload) => this.emitEvent(type, id, payload),\n () => this.hidePopup(),\n this.env,\n userId,\n style,\n sessionId,\n miniService,\n analyticsFeedbackSessionId,\n );\n });\n }\n\n /** Hide the popup */\n private hidePopup(): void {\n if (this.renderer) {\n this.renderer.hide();\n return;\n }\n if (this.popupContainer) {\n this.popupContainer.style.display = 'none';\n this.popupContainer.innerHTML = '';\n }\n }\n\n /** Emit an event */\n private emitEvent(type: DeepdotsEventType, surveyId: string, data?: Record<string, unknown>): void {\n const event: DeepdotsEvent = { type, surveyId, timestamp: Date.now(), data };\n this.log('Event emitted', event);\n\n const isPartialClick = type === 'popup_clicked' && data?.action === 'partial';\n if (isPartialClick) {\n this.markSurveyProgress(surveyId, 'PARTIAL');\n }\n if (type === 'survey_completed') {\n this.markSurveyAnswered(surveyId);\n }\n if (type === 'popup_shown' || type === 'survey_completed' || isPartialClick) {\n const popupIdFromData = data?.popupId as string | undefined;\n const popupId = popupIdFromData || this.surveyToPopupId.get(surveyId);\n if (popupId) {\n const userIdFromData = data?.userId as string | undefined;\n const status = type === 'popup_shown'\n ? POPUPSESSIONSTATUS.SHOWED\n : type === 'survey_completed'\n ? POPUPSESSIONSTATUS.COMPLETED\n : POPUPSESSIONSTATUS.PARTIAL;\n void this.postPopupEvent(status, popupId, userIdFromData || this.tracking?.getUserId() || this.config?.userId);\n } else {\n this.debug('No popupId available to post event', { type, surveyId });\n }\n }\n\n const listeners = this.listeners.get(type);\n if (listeners) {\n listeners.forEach((listener) => {\n try {\n listener(event);\n } catch (error) {\n if (this.logger.error) this.logger.error('Error in event listener:', error);\n else this.logger.log('Error in event listener:', error);\n }\n });\n }\n }\n\n /** Log debug messages */\n private log(...args: unknown[]): void {\n if (this.config?.debug) {\n this.logger.log('[DeepdotsPopups]', ...args);\n }\n }\n\n /** External debug method for triggers */\n public debug(...args: unknown[]): void {\n this.log(...args);\n }\n\n /** Set a custom renderer */\n public setRenderer(renderer: PopupRenderer): void {\n this.renderer = renderer;\n if (this.initialized && this.renderer.init) {\n this.renderer.init();\n }\n }\n\n private normalizeUrl(value: string): string {\n if (!value) return '';\n const withoutIndex = value.replace(/\\/index\\.html(?=($|[?#]))/i, '');\n return withoutIndex.length > 1 && withoutIndex.endsWith('/') ? withoutIndex.slice(0, -1) : withoutIndex;\n }\n\n private safeParseUrl(url: string): URL | null {\n if (!url) return null;\n try {\n return new URL(url, typeof window !== 'undefined' ? window.location.href : undefined);\n } catch {\n return null;\n }\n }\n\n private mapPopupTriggerType(type: PopupTrigger['type']): TriggerConfig['type'] | null {\n switch (type) {\n case 'time_on_page':\n return 'time';\n case 'scroll':\n return 'scroll';\n case 'exit':\n return 'exit';\n case 'click':\n return 'click';\n case 'event':\n return 'event';\n default:\n return null;\n }\n }\n\n private markSurveyProgress(surveyId: string, status: PopupTriggerConditionStatus): void {\n const current = this.surveyProgress.get(surveyId);\n if (current?.status === 'COMPLETED' && status !== 'COMPLETED') {\n return;\n }\n this.surveyProgress.set(surveyId, { status, timestamp: Date.now() });\n }\n\n private isPopupDefinition(value: unknown): value is NormalizedPopupDefinition {\n if (typeof value !== 'object' || value === null) return false;\n const popup = value as Partial<NormalizedPopupDefinition>;\n\n return typeof popup.id === 'string'\n && typeof popup.title === 'string'\n && typeof popup.message === 'string'\n && typeof popup.surveyId === 'string'\n && typeof popup.productId === 'string'\n && Array.isArray(popup.triggers)\n && popup.triggers.length > 0\n && popup.triggers.every((trigger) => this.isPopupTrigger(trigger))\n && (!popup.cooldown || popup.cooldown.every((condition) => this.isPopupTriggerCondition(condition)));\n }\n\n private isPopupTrigger(value: unknown): value is PopupTrigger {\n if (typeof value !== 'object' || value === null) return false;\n const trigger = value as Partial<PopupTrigger>;\n return this.isPopupTriggerType(trigger.type)\n && (typeof trigger.value === 'number' || typeof trigger.value === 'string');\n }\n\n private isPopupTriggerType(value: unknown): value is PopupTrigger['type'] {\n return value === 'time_on_page'\n || value === 'scroll'\n || value === 'exit'\n || value === 'click'\n || value === 'event';\n }\n\n private isPopupTriggerCondition(value: unknown): value is PopupTriggerCondition {\n if (typeof value !== 'object' || value === null) return false;\n const condition = value as Partial<PopupTriggerCondition>;\n return POPUP_TRIGGER_CONDITION_STATUSES.includes(condition.answered as PopupTriggerConditionStatus)\n && typeof condition.cooldownDays === 'number'\n && Number.isFinite(condition.cooldownDays)\n && condition.cooldownDays >= 0;\n }\n\n private isLegacyPopupTriggerCondition(value: unknown): value is LegacyPopupTriggerCondition {\n if (typeof value !== 'object' || value === null) return false;\n const condition = value as Partial<LegacyPopupTriggerCondition>;\n if (condition.answered !== undefined && typeof condition.answered !== 'boolean') {\n return false;\n }\n if (condition.cooldownDays !== undefined) {\n return typeof condition.cooldownDays === 'number'\n && Number.isFinite(condition.cooldownDays)\n && condition.cooldownDays >= 0;\n }\n return true;\n }\n\n private normalizeTriggers(value: unknown): PopupTrigger[] {\n const items = Array.isArray(value) ? value : value ? [value] : [];\n return items\n .filter((trigger): trigger is PopupTrigger => this.isPopupTrigger(trigger))\n .map((trigger) => ({\n type: trigger.type,\n value: trigger.value,\n }));\n }\n\n private normalizeCooldown(value: unknown): PopupTriggerCondition[] {\n if (!Array.isArray(value)) return [];\n return value.filter((condition): condition is PopupTriggerCondition => this.isPopupTriggerCondition(condition));\n }\n\n private normalizeLegacyConditions(value: unknown): LegacyPopupTriggerCondition[] {\n if (!Array.isArray(value)) return [];\n return value.filter((condition): condition is LegacyPopupTriggerCondition => this.isLegacyPopupTriggerCondition(condition));\n }\n\n private normalizePopupDefinition(def: unknown): NormalizedPopupDefinition | null {\n if (typeof def !== 'object' || def === null) return null;\n const raw = def as Partial<PopupDefinition> & {\n trigger?: PopupTrigger | PopupTrigger[];\n triggers?: PopupTrigger | PopupTrigger[];\n conditions?: LegacyPopupTriggerCondition[];\n cooldown?: PopupTriggerCondition[];\n };\n\n const rawTriggers = raw.triggers ?? raw.trigger;\n const triggers = this.normalizeTriggers(rawTriggers);\n if (!triggers.length) return null;\n\n const rawTriggerList = Array.isArray(rawTriggers) ? rawTriggers : rawTriggers ? [rawTriggers] : [];\n const legacyConditions = [\n ...this.normalizeLegacyConditions(raw.conditions),\n ...rawTriggerList.flatMap((trigger) => this.normalizeLegacyConditions((trigger as { condition?: unknown }).condition)),\n ];\n const cooldown = this.normalizeCooldown(raw.cooldown);\n\n return {\n ...(raw as PopupDefinition),\n triggers,\n cooldown,\n legacyConditions,\n };\n }\n\n private validatePopupDefinitions(defs: unknown[]): NormalizedPopupDefinition[] {\n return defs\n .map((def) => this.normalizePopupDefinition(def))\n .filter((def): def is NormalizedPopupDefinition => !!def && this.isPopupDefinition(def));\n }\n}\n","/**\n * Auto-wiring de React Native: concentra TODA la configuración en el SDK para que el\n * host solo tenga que envolver su app en un `<DeepdotsProvider config={...}>`.\n *\n * No importa React ni librerías nativas: el host (o el Provider) le pasa los módulos\n * nativos que tenga instalados (MMKV, device-info, AppState). Todo es opcional y degrada\n * con elegancia (sin MMKV → in-memory; sin device-info → sin device info).\n */\nimport type { DeepdotsPopups } from '../core/deepdots-popups';\nimport type { DeepdotsInitParams } from '../types';\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\nimport type { DeviceInfo } from '../analytics/device-info';\nimport type { PopupRenderer } from '../platform/renderer';\nimport type { ReactNativeErrorUtils } from '../analytics/crash-reporter';\n\n/** Forma mínima de `react-native-mmkv` (instancia MMKV). */\nexport interface MmkvLike {\n getString(key: string): string | undefined | null;\n set(key: string, value: string): void;\n delete(key: string): void;\n}\n\n/** Forma mínima de `react-native-device-info`. */\nexport interface DeviceInfoLike {\n isTablet?: () => boolean;\n getSystemVersion?: () => string;\n getDeviceId?: () => string;\n getVersion?: () => string;\n}\n\n/** Forma mínima de `AppState` de react-native. */\nexport interface AppStateLike {\n addEventListener(type: 'change', listener: (state: string) => void): { remove: () => void } | void;\n}\n\nexport interface ReactNativeSetupDeps {\n mmkv?: MmkvLike | null;\n deviceInfo?: DeviceInfoLike | null;\n appState?: AppStateLike | null;\n platform?: 'web' | 'android' | 'ios';\n renderer?: PopupRenderer;\n /** `global.ErrorUtils` de RN (para capturar errores JS no manejados). Default: `globalThis.ErrorUtils`. */\n errorUtils?: ReactNativeErrorUtils | null;\n}\n\n/** Adaptador KeyValueStorage (síncrono) sobre una instancia de MMKV. */\nexport function mmkvStorage(mmkv: MmkvLike): KeyValueStorage {\n return {\n getItem: (k) => mmkv.getString(k) ?? null,\n setItem: (k, v) => mmkv.set(k, v),\n removeItem: (k) => mmkv.delete(k),\n };\n}\n\n/** Recoge device info desde react-native-device-info (Technology #11–13). */\nexport function collectRnDevice(d: DeviceInfoLike): DeviceInfo {\n return {\n device_type: d.isTablet?.() ? 'tablet' : 'mobile',\n os_version: d.getSystemVersion?.(),\n device_model: d.getDeviceId?.(),\n app_version: d.getVersion?.(),\n };\n}\n\n/**\n * Configura el SDK para React Native en una sola llamada: storage persistente (MMKV),\n * device info, platform, init y lifecycle (AppState → onForeground/onBackground).\n * Devuelve una función de limpieza (quita el listener de AppState).\n */\nexport function setupReactNative(\n sdk: DeepdotsPopups,\n config: DeepdotsInitParams,\n deps: ReactNativeSetupDeps = {},\n): () => void {\n if (deps.renderer) sdk.setRenderer(deps.renderer);\n\n const storage = deps.mmkv ? mmkvStorage(deps.mmkv) : config.storage;\n const device = deps.deviceInfo ? collectRnDevice(deps.deviceInfo) : config.device;\n const platform = deps.platform ?? config.platform;\n\n sdk.init({ ...config, storage, device, platform });\n\n // Captura de errores JS no manejados en RN (no hay `window`): usa el ErrorUtils inyectado\n // o el global del runtime RN. Degrada si no existe.\n const errorUtils =\n deps.errorUtils ?? (globalThis as { ErrorUtils?: ReactNativeErrorUtils }).ErrorUtils;\n if (errorUtils && typeof errorUtils.setGlobalHandler === 'function') {\n sdk.installReactNativeCrashHandler(errorUtils);\n }\n\n let subscription: { remove: () => void } | void;\n if (deps.appState?.addEventListener) {\n subscription = deps.appState.addEventListener('change', (state) => {\n if (state === 'active') sdk.onForeground();\n // Solo 'background' es fin de sesión. En iOS 'inactive' es transitorio (llamada\n // entrante, app switcher) y cerrar sesión ahí partiría la sesión en dos: se envía\n // lo acumulado, por si el SO mata la app, pero sin cerrar el registro.\n else if (state === 'background') sdk.onBackground();\n else sdk.flushAnalytics();\n });\n }\n\n return () => {\n if (subscription && typeof subscription.remove === 'function') subscription.remove();\n };\n}\n"],"mappings":"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","names":["sdk"]}