@magicfeedback/popup-sdk 1.9.0 → 1.10.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.
- package/dist/assets/assets/style.css +303 -1
- package/dist/{chunk-MTV7T4OW.mjs → chunk-GXEPYT33.mjs} +304 -2
- package/dist/{chunk-MTV7T4OW.mjs.map → chunk-GXEPYT33.mjs.map} +1 -1
- package/dist/{chunk-AKVMUQXO.mjs → chunk-TSORXOQG.mjs} +304 -2
- package/dist/{chunk-AKVMUQXO.mjs.map → chunk-TSORXOQG.mjs.map} +1 -1
- package/dist/index.js +1516 -885
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -8
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.js +319 -8
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +13 -8
- package/dist/react-native.mjs.map +1 -1
- package/dist/{renderPopup-LEGTSBLD.mjs → renderPopup-424HR5LF.mjs} +1211 -887
- package/dist/renderPopup-424HR5LF.mjs.map +1 -0
- package/dist/{renderPopup-LEM2MLWT.mjs → renderPopup-WLOFOCDR.mjs} +9 -5
- package/dist/renderPopup-WLOFOCDR.mjs.map +1 -0
- package/package.json +5 -3
- package/dist/renderPopup-LEGTSBLD.mjs.map +0 -1
- package/dist/renderPopup-LEM2MLWT.mjs.map +0 -1
package/dist/react-native.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tracking/tracking-manager.ts","../src/ui/font.ts","../src/i18n/labels.ts","../src/ui/reveal.ts","../src/ui/surveyPalette.ts","../src/ui/busy.ts","../src/assets/style.css","../src/util/logger.ts","../src/ui/logo.ts","../src/ui/renderPopup.ts","../src/react-native/index.tsx","../src/types/index.ts","../src/triggers/index.ts","../src/config/env.ts","../src/platform/react-native-renderer.ts","../src/ui/surveyHtml.ts","../src/ui/preload.ts","../src/platform/renderer.ts","../src/core/deepdots-popups.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/core/popup-state-store.ts","../src/analytics/messaging.ts","../src/react-native/setup.ts"],"sourcesContent":["/**\n * Identidad + sesión para el tracking de navegación (Fase 1).\n * Implementa el \"Contrato común Web+KMP\".\n *\n * Modelo: el SDK gestiona el `user_id` (persistente). El `session_id` es propiedad\n * del BACKEND: llega en la respuesta de `POST /sdk/popups` y se cachea; el backend\n * cose sesiones por `user_id` + ventana. El SDK NO genera ni expira sesiones.\n *\n * Sin dependencias de DOM: recibe el storage y (opcionalmente) el reloj y el\n * generador de uuid por inyección. El binding a `localStorage` vive en `createDefaultStorage`.\n */\n\n/** Store clave/valor mínimo (espejo conceptual del `KeyValueStorage` de KMP). */\nexport interface KeyValueStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/** Impl en memoria — usada en tests y como fallback sin `localStorage`. */\nexport class InMemoryStorage implements KeyValueStorage {\n private map = new Map<string, string>();\n getItem(key: string): string | null {\n return this.map.has(key) ? (this.map.get(key) as string) : null;\n }\n setItem(key: string, value: string): void {\n this.map.set(key, value);\n }\n removeItem(key: string): void {\n this.map.delete(key);\n }\n}\n\n/** Storage por defecto en Web: `localStorage` si está disponible, si no in-memory. */\nexport function createDefaultStorage(): KeyValueStorage {\n try {\n if (typeof window !== 'undefined' && window.localStorage) {\n const ls = window.localStorage;\n const probe = '__deepdots_probe__';\n ls.setItem(probe, '1');\n ls.removeItem(probe);\n return {\n getItem: (k) => ls.getItem(k),\n setItem: (k, v) => ls.setItem(k, v),\n removeItem: (k) => ls.removeItem(k),\n };\n }\n } catch {\n // cae a in-memory\n }\n return new InMemoryStorage();\n}\n\n/** Claves de storage — namespace `deepdots.*`. Solo identidad (la sesión no se persiste). */\nexport const STORAGE_KEYS = {\n userId: 'deepdots.user_id',\n firstSeen: 'deepdots.user.first_seen',\n contactAttributes: 'deepdots.contact.attributes',\n} as const;\n\n/** Par clave/valor con el formato `NativeAnswer` que espera `@magicfeedback/native`. */\nexport interface IdentityAnswer {\n key: string;\n value: string[];\n}\n\n/**\n * Construye el `profile` y el `metadata` de identidad para inyectar en el survey\n * (`magicfeedback.form(appId, publicKey, profile, metadata)`), según el contrato §5.\n * `session_id` solo se incluye si el backend ya lo proveyó.\n */\nexport function buildSurveyIdentity(\n userId: string | null,\n sessionId: string | null,\n miniService: string | null = null,\n analyticsFeedbackSessionId: string | null = null,\n): { profile: IdentityAnswer[]; metadata: IdentityAnswer[] } {\n const profile: IdentityAnswer[] = userId ? [{ key: 'external-user-id', value: [userId] }] : [];\n const metadata: IdentityAnswer[] = [];\n if (sessionId) metadata.push({ key: 'session_id', value: [sessionId] });\n if (userId) metadata.push({ key: 'user_id', value: [userId] });\n if (miniService) metadata.push({ key: 'mini_service', value: [miniService] });\n if (analyticsFeedbackSessionId) metadata.push({ key: 'deepdots_analytics_feedback_session_id', value: [analyticsFeedbackSessionId] });\n return { profile, metadata };\n}\n\nexport interface TrackingManagerOptions {\n storage: KeyValueStorage;\n /** Id provisto por el cliente; si existe se usa y NO se persiste. */\n clientUserId?: string;\n /** Reloj inyectable (default `Date.now`). */\n now?: () => number;\n /** Generador de uuid inyectable (default uuid v4). */\n uuid?: () => string;\n /** Estado inicial del tracking (default `true`). */\n enabled?: boolean;\n}\n\nfunction defaultUuid(): string {\n const g = (typeof globalThis !== 'undefined' ? globalThis : {}) as {\n crypto?: { randomUUID?: () => string; getRandomValues?: (a: Uint8Array) => Uint8Array };\n };\n if (g.crypto?.randomUUID) return g.crypto.randomUUID();\n const bytes = new Uint8Array(16);\n if (g.crypto?.getRandomValues) {\n g.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0'));\n return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex\n .slice(6, 8)\n .join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;\n}\n\nexport class TrackingManager {\n private storage: KeyValueStorage;\n private clientUserId?: string;\n private now: () => number;\n private uuid: () => string;\n private enabled: boolean;\n\n /** session_id cacheado, provisto por el backend (no se genera ni se persiste). */\n private sessionId: string | null = null;\n private userWasJustCreated = false;\n\n constructor(options: TrackingManagerOptions) {\n this.storage = options.storage;\n this.clientUserId = options.clientUserId;\n this.now = options.now ?? (() => Date.now());\n this.uuid = options.uuid ?? defaultUuid;\n this.enabled = options.enabled ?? true;\n }\n\n isTrackingEnabled(): boolean {\n return this.enabled;\n }\n\n setTrackingEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /** Resuelve el user_id según la regla: cliente > persistido > generar+persistir. */\n getUserId(): string | null {\n if (!this.enabled) return null;\n if (this.clientUserId) return this.clientUserId;\n\n const persisted = this.storage.getItem(STORAGE_KEYS.userId);\n if (persisted) return persisted;\n\n const id = this.uuid();\n this.storage.setItem(STORAGE_KEYS.userId, id);\n this.storage.setItem(STORAGE_KEYS.firstSeen, String(this.now()));\n this.userWasJustCreated = true;\n return id;\n }\n\n /** `true` solo en la primera sesión tras crear el user_id propio del SDK. */\n isNewUser(): boolean {\n return this.userWasJustCreated;\n }\n\n /** session_id actual provisto por el backend, o null si aún no llegó. */\n getSessionId(): string | null {\n if (!this.enabled) return null;\n return this.sessionId;\n }\n\n /** Cachea el session_id devuelto por el backend (respuesta de POST /sdk/popups). */\n setSessionId(sessionId: string | null): void {\n if (!this.enabled) return;\n this.sessionId = sessionId;\n }\n}\n","/**\n * Helpers puros (sin DOM) para la fuente personalizable del popup/survey.\n * Espejo exacto en KMP: `ui/Font.kt`. Cualquier cambio aquí se replica allí.\n */\n\nconst FORMAT_BY_EXT: Record<string, string> = {\n woff2: 'woff2',\n woff: 'woff',\n ttf: 'truetype',\n otf: 'opentype',\n};\n\n/** Deriva el `format()` del `@font-face` a partir de la extensión de la URL. */\nexport function fontFormatFromUrl(url: string): string | undefined {\n const clean = url.split('?')[0].split('#')[0];\n const ext = clean.slice(clean.lastIndexOf('.') + 1).toLowerCase();\n return FORMAT_BY_EXT[ext];\n}\n\n// family/url provienen de la API y se interpolan en CSS y en el <style> del HTML del WebView.\n// Saneamos para evitar romper el string/rule (CSS injection) o cerrar </style> (script injection).\nfunction sanitizeFamily(family: string): string {\n return family.replace(/[^A-Za-z0-9 ._-]/g, '').trim();\n}\n\nfunction isSafeFontUrl(url: string): boolean {\n // Solo http(s)/data y sin caracteres que rompan url(\"...\") o el <style>:\n // comillas, <>, backslash, y cualquier whitespace o carácter de control.\n if (!/^(https?:|data:)/i.test(url)) return false;\n // eslint-disable-next-line no-control-regex\n return !/[\\x00-\\x20\"<>\\\\]/.test(url);\n}\n\n/** Valor a aplicar en `font-family`: nombre custom + fallback de sistema. */\nexport function buildFontFamilyValue(family: string): string {\n const safe = sanitizeFamily(family);\n return `\"${safe}\", -apple-system, system-ui, sans-serif`;\n}\n\n/** CSS del `@font-face`. Devuelve \"\" si no hay url (o no es segura). */\nexport function buildFontFaceCss(family: string, url?: string): string {\n if (!url || !isSafeFontUrl(url)) return '';\n const safe = sanitizeFamily(family);\n const fmt = fontFormatFromUrl(url);\n const src = fmt ? `url(\"${url}\") format(\"${fmt}\")` : `url(\"${url}\")`;\n return `@font-face{font-family:\"${safe}\";src:${src};font-display:swap;}`;\n}\n","import type { PopupActions } from '../types';\n\n/**\n * Textos que pinta el SDK alrededor del survey (botones del footer, barra de progreso,\n * aria-labels y avisos de error). NO son textos del survey: las preguntas, placeholders y\n * opciones las localiza `@magicfeedback/native` con `formData.lang[0]`, el idioma configurado\n * en la integración. Este módulo existe para que el chrome del popup hable ese mismo idioma\n * en lugar de quedarse siempre en inglés.\n *\n * Espejo del `i18n/DefaultLabels.kt` del SDK nativo (KMP): mismos locales y mismas reglas de\n * resolución, para que las tres rutas (popup DOM web, WebView de RN y Compose) digan lo mismo.\n */\n\n/** Locales con traducción propia. Cualquier otro idioma cae a `en`. */\nexport type PopupLocale =\n | 'en' | 'es' | 'da' | 'no' | 'sv' | 'fi' | 'de' | 'fr' | 'pt' | 'ar' | 'bn' | 'zh-CN';\n\nexport interface PopupLabels {\n /** Acción principal: enviar la respuesta. */\n accept: string;\n /** Acción de descarte. */\n decline: string;\n /** Botón de la pantalla de bienvenida. */\n start: string;\n /** Botón de la pantalla final. */\n complete: string;\n /** Volver a la pregunta anterior. */\n back: string;\n /** Sustantivo del contador de progreso (\"Question\" en `Question 2 of 5`). */\n question: string;\n /** Separador del contador de progreso (\"of\" en `Question 2 of 5`). */\n of: string;\n /** Distintivo de las preguntas de seguimiento dinámicas. */\n followUp: string;\n /** aria-label del botón de cerrar. */\n closeAria: string;\n /** aria-label del spinner mientras carga el survey. */\n loadingAria: string;\n /** Aviso al intentar avanzar con una pregunta obligatoria sin responder. */\n errorRequired: string;\n /** Aviso cuando el envío falla. */\n errorSubmit: string;\n}\n\nconst EN: PopupLabels = {\n accept: 'Send',\n decline: 'Cancel',\n start: 'Start survey',\n complete: 'Complete survey',\n back: 'Back',\n question: 'Question',\n of: 'of',\n followUp: 'Follow-up',\n closeAria: 'Close popup',\n loadingAria: 'Loading survey',\n errorRequired: 'Please answer the required question to continue.',\n errorSubmit: 'An error occurred while submitting. Please try again or close the popup.',\n};\n\nconst ES: PopupLabels = {\n accept: 'Enviar',\n decline: 'Cancelar',\n start: 'Empezar encuesta',\n complete: 'Completar encuesta',\n back: 'Atrás',\n question: 'Pregunta',\n of: 'de',\n followUp: 'Seguimiento',\n closeAria: 'Cerrar ventana emergente',\n loadingAria: 'Cargando encuesta',\n errorRequired: 'Responde la pregunta obligatoria para continuar.',\n errorSubmit: 'Se produjo un error al enviar. Inténtalo de nuevo o cierra la ventana.',\n};\n\nconst DA: PopupLabels = {\n accept: 'Send',\n decline: 'Annuller',\n start: 'Start undersøgelse',\n complete: 'Afslut undersøgelse',\n back: 'Tilbage',\n question: 'Spørgsmål',\n of: 'af',\n followUp: 'Opfølgning',\n closeAria: 'Luk pop op',\n loadingAria: 'Indlæser undersøgelse',\n errorRequired: 'Besvar venligst det obligatoriske spørgsmål for at fortsætte.',\n errorSubmit: 'Der opstod en fejl under afsendelsen. Prøv igen, eller luk pop op-vinduet.',\n};\n\nconst NO: PopupLabels = {\n accept: 'Send',\n decline: 'Avbryt',\n start: 'Start undersøkelse',\n complete: 'Fullfør undersøkelse',\n back: 'Tilbake',\n question: 'Spørsmål',\n of: 'av',\n followUp: 'Oppfølging',\n closeAria: 'Lukk popup',\n loadingAria: 'Laster undersøkelse',\n errorRequired: 'Svar på det obligatoriske spørsmålet for å fortsette.',\n errorSubmit: 'Det oppstod en feil under innsendingen. Prøv igjen, eller lukk popupen.',\n};\n\nconst SV: PopupLabels = {\n accept: 'Skicka',\n decline: 'Avbryt',\n start: 'Starta undersökning',\n complete: 'Slutför undersökning',\n back: 'Tillbaka',\n question: 'Fråga',\n of: 'av',\n followUp: 'Uppföljning',\n closeAria: 'Stäng popup',\n loadingAria: 'Laddar undersökning',\n errorRequired: 'Svara på den obligatoriska frågan för att fortsätta.',\n errorSubmit: 'Ett fel uppstod vid inskickningen. Försök igen eller stäng popupen.',\n};\n\nconst FI: PopupLabels = {\n accept: 'Lähetä',\n decline: 'Peruuta',\n start: 'Aloita kysely',\n complete: 'Viimeistele kysely',\n back: 'Takaisin',\n question: 'Kysymys',\n of: '/',\n followUp: 'Jatkokysymys',\n closeAria: 'Sulje ponnahdusikkuna',\n loadingAria: 'Ladataan kyselyä',\n errorRequired: 'Vastaa pakolliseen kysymykseen jatkaaksesi.',\n errorSubmit: 'Lähetyksessä tapahtui virhe. Yritä uudelleen tai sulje ponnahdusikkuna.',\n};\n\nconst DE: PopupLabels = {\n accept: 'Senden',\n decline: 'Abbrechen',\n start: 'Umfrage starten',\n complete: 'Umfrage abschließen',\n back: 'Zurück',\n question: 'Frage',\n of: 'von',\n followUp: 'Folgefrage',\n closeAria: 'Popup schließen',\n loadingAria: 'Umfrage wird geladen',\n errorRequired: 'Bitte beantworte die Pflichtfrage, um fortzufahren.',\n errorSubmit: 'Beim Senden ist ein Fehler aufgetreten. Versuche es erneut oder schließe das Popup.',\n};\n\nconst FR: PopupLabels = {\n accept: 'Envoyer',\n decline: 'Annuler',\n start: \"Commencer l'enquête\",\n complete: \"Terminer l'enquête\",\n back: 'Retour',\n question: 'Question',\n of: 'sur',\n followUp: 'Question complémentaire',\n closeAria: 'Fermer la fenêtre',\n loadingAria: \"Chargement de l'enquête\",\n errorRequired: 'Veuillez répondre à la question obligatoire pour continuer.',\n errorSubmit: \"Une erreur s'est produite lors de l'envoi. Réessayez ou fermez la fenêtre.\",\n};\n\nconst PT: PopupLabels = {\n accept: 'Enviar',\n decline: 'Cancelar',\n start: 'Iniciar questionário',\n complete: 'Concluir questionário',\n back: 'Voltar',\n question: 'Pergunta',\n of: 'de',\n followUp: 'Seguimento',\n closeAria: 'Fechar janela',\n loadingAria: 'A carregar questionário',\n errorRequired: 'Responda à pergunta obrigatória para continuar.',\n errorSubmit: 'Ocorreu um erro ao enviar. Tente novamente ou feche a janela.',\n};\n\n// ⚠️ Árabe: el texto es RTL pero el chrome del popup sigue maquetado LTR (ver nota en el\n// README). Traducir ya es mejor que dejarlo en inglés; la dirección es una decisión aparte.\nconst AR: PopupLabels = {\n accept: 'إرسال',\n decline: 'إلغاء',\n start: 'ابدأ الاستبيان',\n complete: 'إنهاء الاستبيان',\n back: 'رجوع',\n question: 'سؤال',\n of: 'من',\n followUp: 'سؤال متابعة',\n closeAria: 'إغلاق النافذة المنبثقة',\n loadingAria: 'جارٍ تحميل الاستبيان',\n errorRequired: 'يرجى الإجابة على السؤال المطلوب للمتابعة.',\n errorSubmit: 'حدث خطأ أثناء الإرسال. يرجى المحاولة مرة أخرى أو إغلاق النافذة.',\n};\n\nconst BN: PopupLabels = {\n accept: 'পাঠান',\n decline: 'বাতিল',\n start: 'জরিপ শুরু করুন',\n complete: 'জরিপ সম্পূর্ণ করুন',\n back: 'পিছনে',\n question: 'প্রশ্ন',\n of: '/',\n followUp: 'ফলো-আপ',\n closeAria: 'পপআপ বন্ধ করুন',\n loadingAria: 'জরিপ লোড হচ্ছে',\n errorRequired: 'চালিয়ে যেতে আবশ্যক প্রশ্নের উত্তর দিন।',\n errorSubmit: 'জমা দেওয়ার সময় একটি ত্রুটি ঘটেছে। আবার চেষ্টা করুন বা পপআপ বন্ধ করুন।',\n};\n\nconst ZH_CN: PopupLabels = {\n accept: '发送',\n decline: '取消',\n start: '开始问卷',\n complete: '完成问卷',\n back: '返回',\n question: '问题',\n of: '/',\n followUp: '追问',\n closeAria: '关闭弹窗',\n loadingAria: '正在加载问卷',\n errorRequired: '请回答必答题后继续。',\n errorSubmit: '提交时出错。请重试或关闭弹窗。',\n};\n\n/** Tabla completa de traducciones, indexada por locale. */\nexport const LABELS: Record<PopupLocale, PopupLabels> = {\n en: EN,\n es: ES,\n da: DA,\n no: NO,\n sv: SV,\n fi: FI,\n de: DE,\n fr: FR,\n pt: PT,\n ar: AR,\n bn: BN,\n 'zh-CN': ZH_CN,\n};\n\n/** Locales con traducción propia del SDK. */\nexport const SUPPORTED_LANGUAGES: PopupLocale[] = [\n // Los 11 idiomas que la plataforma ofrece para un survey, más zh-CN (que el SDK nativo ya\n // traía). Si la plataforma añade uno nuevo, hay que añadirlo aquí o el chrome saldrá en\n // inglés bajo un survey traducido.\n 'en', 'es', 'da', 'no', 'sv', 'fi', 'de', 'fr', 'pt', 'ar', 'bn', 'zh-CN',\n];\n\n/**\n * Normaliza un tag BCP-47 al locale con traducción. Reglas (idénticas a KMP):\n * - el prefijo de idioma manda, sin importar región ni caja (`da`, `da-DK`, `da_DK` → `da`);\n * - las variantes noruegas (`nb`, `nn`) colapsan en `no`;\n * - las variantes chinas (`zh`, `zh-Hans`, `zh-CN`, `zh-TW`) colapsan en `zh-CN`;\n * - `pt-BR` y `pt-PT` comparten juego, igual que `de-AT`/`de-CH` o `fr-CA`;\n * - cualquier otra cosa, incluido un valor no-string del backend, cae a `en`.\n */\nexport function resolveLocale(lang?: string | null): PopupLocale {\n if (typeof lang !== 'string') return 'en';\n const normalized = lang.trim().toLowerCase().replace(/_/g, '-');\n if (!normalized) return 'en';\n if (normalized === 'zh' || normalized.startsWith('zh-')) return 'zh-CN';\n const primary = normalized.split('-')[0];\n switch (primary) {\n case 'en': return 'en';\n case 'es': return 'es';\n case 'da': return 'da';\n case 'no':\n case 'nb':\n case 'nn': return 'no';\n case 'sv': return 'sv';\n case 'fi': return 'fi';\n case 'de': return 'de';\n case 'fr': return 'fr';\n case 'pt': return 'pt';\n case 'ar': return 'ar';\n case 'bn': return 'bn';\n default: return 'en';\n }\n}\n\n/**\n * Locales que se escriben de derecha a izquierda. Espejo de `RTL_LANGUAGES` de\n * `@magicfeedback/native` (2.2.22), que estampa `dir=\"rtl\"` en el contenedor del survey para\n * esos idiomas: el chrome del popup tiene que voltear con él o media tarjeta queda mirando a\n * un lado y media al otro.\n */\nexport const RTL_LOCALES: PopupLocale[] = ['ar'];\n\n/** Si el chrome debe pintarse de derecha a izquierda para `lang`. */\nexport function isRtlLanguage(lang?: string | null): boolean {\n return RTL_LOCALES.includes(resolveLocale(lang));\n}\n\n/** Valor del atributo `dir` del chrome para `lang`. Siempre explícito: nunca se hereda del host. */\nexport function directionFor(lang?: string | null): 'rtl' | 'ltr' {\n return isRtlLanguage(lang) ? 'rtl' : 'ltr';\n}\n\n/** Textos del chrome del popup para `lang`. Cae a inglés si el idioma no está soportado. */\nexport function getLabels(lang?: string | null): PopupLabels {\n return LABELS[resolveLocale(lang)];\n}\n\n/** Etiquetas de los cuatro botones del footer, ya resueltas. */\nexport interface ResolvedActionLabels {\n accept: string;\n decline: string;\n start: string;\n complete: string;\n back: string;\n}\n\n/**\n * Resuelve las etiquetas de los botones con la prioridad acordada: lo que configure la\n * plataforma por popup (`actions.*.label`) gana siempre; si falta o viene en blanco, se usa\n * la traducción del SDK para `lang`.\n */\nexport function resolveActionLabels(actions: PopupActions | undefined, lang?: string | null): ResolvedActionLabels {\n const labels = getLabels(lang);\n const pick = (value: string | undefined, fallback: string): string => {\n const trimmed = typeof value === 'string' ? value.trim() : '';\n return trimmed ? value as string : fallback;\n };\n return {\n accept: pick(actions?.accept?.label, labels.accept),\n decline: pick(actions?.decline?.label, labels.decline),\n start: pick(actions?.start?.label, labels.start),\n complete: pick(actions?.complete?.label, labels.complete),\n back: pick(actions?.back?.label, labels.back),\n };\n}\n\n/**\n * Fuente JavaScript del resolutor de locale, para incrustar en el HTML del WebView (React\n * Native): allí el idioma del survey solo se conoce ya dentro del WebView, así que la\n * normalización tiene que viajar con el HTML. Es un espejo literal de `resolveLocale`, y\n * `labels.test.ts` compara las dos implementaciones contra la misma batería de tags para que\n * no se separen en silencio.\n */\nexport const RESOLVE_LOCALE_JS = `function ddResolveLocale(lang){\n if(typeof lang!=='string') return 'en';\n var n=lang.trim().toLowerCase().replace(/_/g,'-');\n if(!n) return 'en';\n if(n==='zh'||n.indexOf('zh-')===0) return 'zh-CN';\n var p=n.split('-')[0];\n if(p==='en') return 'en';\n if(p==='es') return 'es';\n if(p==='da') return 'da';\n if(p==='no'||p==='nb'||p==='nn') return 'no';\n if(p==='sv') return 'sv';\n if(p==='fi') return 'fi';\n if(p==='de') return 'de';\n if(p==='fr') return 'fr';\n if(p==='pt') return 'pt';\n if(p==='ar') return 'ar';\n if(p==='bn') return 'bn';\n return 'en';\n}`;\n","/**\n * Apertura diferida del popup: el chrome se monta invisible y se enseña cuando el survey ya\n * está pintado, en vez de enseñar un spinner girando mientras se espera.\n *\n * Vive en su propio módulo porque lo comparten las dos rutas de render y `renderPopup` arrastra\n * `@magicfeedback/native`: importar la constante desde allí metería el survey entero en el bundle\n * de React Native. La ruta del WebView (`surveyHtml`) hornea el valor en el HTML y lleva su\n * propio espejo del algoritmo en ES5, porque ese código corre dentro del WebView.\n */\n\n/**\n * Techo de la espera. Pasado este tiempo el popup se abre igualmente (con spinner, el\n * comportamiento de siempre), para que una red mala retrase la apertura pero nunca la impida.\n *\n * 1200 ms sale de medir contra producción: el `GET .../info` del survey tarda unos 250 ms de\n * mediana desde Europa, pero con el servicio frío se han visto 775 ms. El techo tiene que quedar\n * por encima de esa cola: si cayera justo encima, el popup se abriría con el spinner para\n * quitarlo 30 ms después, que es peor que cualquiera de las dos opciones.\n */\nexport const REVEAL_TIMEOUT_MS = 1200;\n\n/**\n * Llama a `reveal` cuando no queden imágenes por llegar dentro de `root`, o ya mismo si no hay\n * ninguna pendiente. Las imágenes (los emojis del rating son SVG, y el logo del survey) llegan\n * DESPUÉS de que el formulario esté montado: sin esperarlas, el popup aparece y se rellena\n * delante del usuario. Quien llama debe mantener su propio techo de tiempo para las que no\n * lleguen nunca.\n */\nexport function revealWhenPainted(root: ParentNode, reveal: () => void): void {\n const pending = Array.from(root.querySelectorAll('img')).filter((img) => !img.complete);\n if (!pending.length) {\n reveal();\n return;\n }\n let left = pending.length;\n const onSettled = () => {\n left -= 1;\n if (left <= 0) reveal();\n };\n pending.forEach((img) => {\n img.addEventListener('load', onSettled, { once: true });\n img.addEventListener('error', onSettled, { once: true });\n });\n}\n","/**\n * Color de marca dentro del survey.\n *\n * El survey pinta sus controles (chips del rating, bordes, foco, selección) con `--mf-primary`,\n * que `@magicfeedback/native` rellena desde `formData.style.primaryColor`. Las integraciones\n * configuran `buttonPrimaryColor` para el botón, pero muchas dejan `primaryColor` vacío, así que\n * el popup acababa mezclando un botón con el color de la marca y unos controles con el `#5d7bad`\n * por defecto del paquete.\n *\n * Aquí se rellena ese hueco: sin `primaryColor`, se usa `buttonPrimaryColor`. Cuando la\n * integración sí define `primaryColor`, ese manda y el SDK no interviene.\n */\n\n/** Estilo del survey, tal y como llega en `formData.style`. */\nexport interface SurveyPaletteStyle {\n primaryColor?: string;\n buttonPrimaryColor?: string;\n}\n\n/**\n * Solo hex. El estilo viene de la API y se interpola en CSS, así que cualquier otra cosa\n * (`red; background: url(...)`, `var(--x)`, …) se descarta en vez de colarse en la hoja.\n */\nconst HEX_COLOR = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n/** Color que debe usar el survey, o null para dejar el del paquete. */\nexport function resolveSurveyPrimaryColor(style?: SurveyPaletteStyle | null): string | null {\n for (const candidate of [style?.primaryColor, style?.buttonPrimaryColor]) {\n const value = (candidate ?? '').trim();\n if (HEX_COLOR.test(value)) return value;\n }\n return null;\n}\n\n/**\n * Las cinco variables que `applyPrimaryColor` del Surveys SDK escribe en el contenedor. Las\n * derivadas hay que redeclararlas y no solo cambiar `--mf-primary`: un `color-mix` escrito en\n * `:root` ya se resolvió allí contra el color por defecto, y heredaría ese valor ya calculado.\n */\nexport function surveyPrimaryVars(color: string): Array<[string, string]> {\n return [\n ['--mf-primary', color],\n ['--mf-primary-hover', `color-mix(in srgb, ${color} 85%, black)`],\n ['--mf-primary-light', `color-mix(in srgb, ${color} 15%, white)`],\n ['--mf-primary-border', `color-mix(in srgb, ${color} 35%, transparent)`],\n ['--mf-border-focus', color],\n ];\n}\n\n/** Pinta el color de marca del survey en `el`. No-op si no hay color utilizable. */\nexport function applySurveyPrimaryColor(el: HTMLElement, style?: SurveyPaletteStyle | null): void {\n const color = resolveSurveyPrimaryColor(style);\n if (!color) return;\n for (const [name, value] of surveyPrimaryVars(color)) {\n el.style.setProperty(name, value);\n }\n}\n\n/**\n * Espejo ES5 de lo anterior, para el JS que corre DENTRO del WebView (React Native y KMP), que no\n * comparte módulos con el bundle. `surveyPalette.test.ts` evalúa este texto y compara el\n * resultado con el del módulo, para que las dos implementaciones no se separen.\n */\nexport const PRIMARY_COLOR_JS = `\nfunction ddResolveSurveyPrimaryColor(style){\n var hex=/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n var candidates=[style&&style.primaryColor, style&&style.buttonPrimaryColor];\n for(var i=0;i<candidates.length;i++){\n var value=(candidates[i]||'').trim();\n if(hex.test(value)){ return value; }\n }\n return null;\n}\nfunction ddApplySurveyPrimaryColor(el, style){\n var color=ddResolveSurveyPrimaryColor(style);\n if(!color||!el){ return; }\n el.style.setProperty('--mf-primary', color);\n el.style.setProperty('--mf-primary-hover', 'color-mix(in srgb, '+color+' 85%, black)');\n el.style.setProperty('--mf-primary-light', 'color-mix(in srgb, '+color+' 15%, white)');\n el.style.setProperty('--mf-primary-border', 'color-mix(in srgb, '+color+' 35%, transparent)');\n el.style.setProperty('--mf-border-focus', color);\n}`;\n","/**\n * Bloqueo del survey mientras carga.\n *\n * El spinner de las transiciones entre páginas no tapa nada (es un círculo de 28px en posición\n * absoluta) y `setLoading(true)` solo ocultaba el footer y deshabilitaba SUS botones, así que el\n * contenido del survey seguía respondiendo: el usuario podía cambiar de opción mientras se\n * enviaba la página. Ese cambio ya no viajaba con ella, porque la respuesta se había mandado, de\n * modo que creía haber corregido su respuesta y no era así.\n *\n * Se bloquea el contenedor entero en vez de deshabilitar campo por campo: el survey lo pinta\n * `@magicfeedback/native` y su marcado cambia con cada tipo de pregunta.\n */\n\n/** Marca (o suelta) el contenedor del survey como ocupado. No-op sin elemento. */\nexport function setSurveyBusy(el: HTMLElement | null | undefined, busy: boolean): void {\n if (!el) return;\n // Cadena vacía al soltar, no 'auto': así el CSS del host sigue mandando sobre el elemento.\n el.style.pointerEvents = busy ? 'none' : '';\n el.setAttribute('aria-busy', busy ? 'true' : 'false');\n // `inert` lo saca además del tab order y del lector de pantalla. No está en WebViews\n // antiguos, donde `pointer-events` ya cubre el caso del vídeo del cliente.\n if ('inert' in el) {\n (el as HTMLElement & { inert?: boolean }).inert = busy;\n }\n}\n\n/**\n * Espejo ES5 para el JS que corre DENTRO del WebView (React Native y KMP), que no comparte\n * módulos con el bundle. `busy.test.ts` lo evalúa y compara su efecto con el del módulo, para\n * que las dos implementaciones no se separen.\n */\nexport const BUSY_JS = `\nfunction ddSetSurveyBusy(el, busy){\n if(!el){ return; }\n el.style.pointerEvents = busy ? 'none' : '';\n el.setAttribute('aria-busy', busy ? 'true' : 'false');\n if('inert' in el){ el.inert = busy; }\n}`;\n","/**\n * NO EDITAR A MANO — generado por scripts/vendor-survey-css.mjs\n *\n * Copia de @magicfeedback/native@2.2.22 → dist/styles/magicfeedback-default.css\n * (la hoja canónica del paquete, la que apunta su campo \"style\"), con estos cambios locales:\n *\n * 1. Sin el @import de Google Fonts (Nunito): no queremos una petición externa desde la\n * página del host, y la tipografía la decide el popup.\n * 2. .magicfeedback-container hereda la fuente del popup (var(--deepdots-font, inherit)).\n * 3. Los selectores desnudos (h2, p, input, textarea, select, ::placeholder, .error) van\n * acotados a .deepdots-popup: este CSS se inyecta en el <head> del host y sin acotar\n * repintaría su web.\n * 4. El grupo .magicfeedback-radio se cualifica como div.magicfeedback-radio (la clase acaba\n * también en el <input> del boolean).\n * 5. Todo :hover va dentro de @media (hover: hover): en táctil WebKit deja el hover pegado\n * en el último punto tocado y la opción de la página siguiente parecía seleccionada.\n * 6. Al final, un bloque de reglas propias del popup (fuente en los controles y padding del\n * radio nativo).\n *\n * Las variables --mf-* se dejan en :root a propósito: el modal del priority-list se portea a\n * <body>, fuera de .deepdots-popup, y ahí las necesita.\n *\n * Para subir de versión: npm i @magicfeedback/native@<v> && node scripts/vendor-survey-css.mjs\n */\n\n/**\n * @magicfeedback/native - Default Styles\n * Clean, minimal, and elegant survey rendering\n */\n\n/* @import de Google Fonts (Nunito) eliminado al vendorizar: la fuente la pone el popup. */\n\n/* ========================================\n CSS VARIABLES - Design SYSTEM\n ======================================== */\n:root {\n /* Colors - Minimal Pastel */\n /* --mf-primary is the one source of truth for brand/accent color —\n every token below derives from it via color-mix() (or references\n it directly) so changing this single value, here or via a\n per-integration override, keeps hover/light/border states in\n sync automatically instead of going stale independently. Scoped\n to genuinely interactive/accent surfaces (buttons, selected\n chips, focus rings) — general body text stays on its own neutral\n scale below, not tied to the brand color. */\n --mf-primary: #5d7bad;\n --mf-primary-hover: color-mix(in srgb, var(--mf-primary) 85%, black);\n --mf-primary-light: color-mix(in srgb, var(--mf-primary) 15%, white);\n /* a translucent tint — mixed with transparent (opacity), not white,\n so it reads as a soft shadow of the brand color rather than a\n pastel of it. */\n --mf-primary-border: color-mix(in srgb, var(--mf-primary) 35%, transparent);\n\n --mf-text-primary: #1E293B;\n --mf-text-secondary: #475569;\n --mf-text-muted: #94A3B8;\n\n --mf-bg-primary: #F8FAFC;\n --mf-bg-secondary: #FFFFFF;\n --mf-bg-hover: #EEF2F6;\n\n --mf-border: #E2E8F0;\n --mf-border-focus: var(--mf-primary);\n\n --mf-success: #16A34A;\n --mf-error: #EF4444;\n --mf-warning: #F59E0B;\n\n --mf-surface: #FFFFFF;\n --mf-surface-alt: #F1F5F9;\n --mf-accent: #38BDF8;\n\n /* Spacing */\n --mf-space-xs: 0.25rem;\n --mf-space-sm: 0.5rem;\n --mf-space-md: 0.75rem;\n --mf-space-lg: 1rem;\n --mf-space-xl: 1.5rem;\n\n /* Border Radius */\n --mf-radius-sm: 0.5rem;\n --mf-radius-md: 0.75rem;\n --mf-radius-lg: 1.25rem;\n --mf-radius-full: 9999px;\n\n /* Shadows */\n --mf-shadow-sm: 0 1px 2px 0 rgba(15, 23, 42, 0.06);\n --mf-shadow-md: 0 8px 20px rgba(15, 23, 42, 0.08);\n --mf-shadow-lg: 0 16px 30px rgba(15, 23, 42, 0.12);\n --mf-shadow-focus: 0 0 0 3px rgba(30, 41, 59, 0.15);\n --mf-shadow-card: 0 18px 40px rgba(15, 23, 42, 0.08);\n\n /* Typography */\n --mf-font-sans: \"Nunito\", \"Quicksand\", \"Avenir Next\", \"Trebuchet MS\", sans-serif;\n --mf-font-size-sm: 0.875rem;\n --mf-font-size-base: 1rem;\n --mf-font-size-lg: 1.125rem;\n --mf-font-size-xl: 1.25rem;\n --mf-line-height: 1.6;\n\n /* Font Weights */\n --mf-font-weight-normal: 400;\n --mf-font-weight-medium: 500;\n --mf-font-weight-semibold: 600;\n --mf-font-weight-bold: 700;\n --mf-font-weight-extrabold: 800;\n\n /* Transitions */\n --mf-transition: all 0.2s ease;\n --mf-transition-fast: all 0.15s ease;\n}\n\n/* ========================================\n ANIMATIONS\n ======================================== */\n@keyframes mf-fade-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes mf-slide-in {\n from {\n opacity: 0;\n transform: translateX(-16px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n@keyframes mf-scale-in {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes mf-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.8;\n }\n}\n\n/* ========================================\n BASE CONTAINER\n ======================================== */\n.magicfeedback-container {\n /* Local: hereda la fuente del popup; --deepdots-font viene de PopupStyle.font. */\n font-family: var(--deepdots-font, inherit);\n color: var(--mf-text-primary);\n line-height: var(--mf-line-height);\n animation: mf-fade-in 0.4s ease-out;\n border-radius: var(--mf-radius-lg);\n}\n\n/* ========================================\n MESSAGES\n ======================================== */\n.magicfeedback-success-message,\n.magicfeedback-info-message,\n.magicfeedback-start-message {\n padding: var(--mf-space-lg);\n margin: var(--mf-space-lg) 0;\n animation: mf-scale-in 0.3s ease-out;\n}\n\n/* .magicfeedback-info-message/-start-message are set via innerHTML (see\n renderInfoPage.ts / questions.service.ts's renderStartMessage) — whatever\n rich text a survey author writes lands here as real HTML, not just the\n <p>/<img> pair this used to style. Covering the rest of what a rich-text\n editor commonly outputs (headings, lists, links, emphasis, quotes, code,\n rules) keeps that content on the same type scale and palette as the rest\n of the widget instead of falling back to unstyled browser defaults. */\n.magicfeedback-success-message > :first-child,\n.magicfeedback-info-message > :first-child,\n.magicfeedback-start-message > :first-child {\n margin-top: 0;\n}\n\n.magicfeedback-success-message > :last-child,\n.magicfeedback-info-message > :last-child,\n.magicfeedback-start-message > :last-child {\n margin-bottom: 0;\n}\n\n.magicfeedback-success-message p,\n.magicfeedback-info-message p,\n.magicfeedback-start-message p {\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-secondary);\n margin: 0 0 var(--mf-space-md);\n line-height: var(--mf-line-height);\n}\n\n.magicfeedback-info-message h1,\n.magicfeedback-info-message h2,\n.magicfeedback-info-message h3,\n.magicfeedback-info-message h4,\n.magicfeedback-info-message h5,\n.magicfeedback-info-message h6,\n.magicfeedback-start-message h1,\n.magicfeedback-start-message h2,\n.magicfeedback-start-message h3,\n.magicfeedback-start-message h4,\n.magicfeedback-start-message h5,\n.magicfeedback-start-message h6 {\n color: var(--mf-text-primary);\n font-weight: var(--mf-font-weight-bold);\n line-height: 1.3;\n margin: var(--mf-space-lg) 0 var(--mf-space-sm);\n text-align: start;\n}\n\n.magicfeedback-info-message h1, .magicfeedback-start-message h1 { font-size: var(--mf-font-size-xl); }\n.magicfeedback-info-message h2, .magicfeedback-start-message h2 { font-size: var(--mf-font-size-lg); }\n.magicfeedback-info-message h3,\n.magicfeedback-info-message h4,\n.magicfeedback-info-message h5,\n.magicfeedback-info-message h6,\n.magicfeedback-start-message h3,\n.magicfeedback-start-message h4,\n.magicfeedback-start-message h5,\n.magicfeedback-start-message h6 { font-size: var(--mf-font-size-base); }\n\n.magicfeedback-info-message strong,\n.magicfeedback-info-message b,\n.magicfeedback-start-message strong,\n.magicfeedback-start-message b {\n font-weight: var(--mf-font-weight-bold);\n color: var(--mf-text-primary);\n}\n\n.magicfeedback-info-message a,\n.magicfeedback-start-message a {\n color: var(--mf-primary);\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n\n@media (hover: hover) {\n .magicfeedback-info-message a:hover,\n .magicfeedback-start-message a:hover {\n color: var(--mf-primary-hover);\n }\n}\n\n.magicfeedback-info-message ul,\n.magicfeedback-info-message ol,\n.magicfeedback-start-message ul,\n.magicfeedback-start-message ol {\n margin: 0 0 var(--mf-space-md);\n padding-inline-start: var(--mf-space-xl);\n color: var(--mf-text-secondary);\n line-height: var(--mf-line-height);\n}\n\n.magicfeedback-info-message li + li,\n.magicfeedback-start-message li + li {\n margin-top: var(--mf-space-xs);\n}\n\n.magicfeedback-info-message blockquote,\n.magicfeedback-start-message blockquote {\n margin: 0 0 var(--mf-space-md);\n padding: var(--mf-space-sm) var(--mf-space-md);\n border-inline-start: 3px solid var(--mf-primary-border);\n color: var(--mf-text-secondary);\n font-style: italic;\n}\n\n.magicfeedback-info-message code,\n.magicfeedback-start-message code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 0.9em;\n background: var(--mf-bg-hover);\n padding: 0.15em 0.4em;\n border-radius: var(--mf-radius-sm);\n}\n\n.magicfeedback-info-message pre,\n.magicfeedback-start-message pre {\n margin: 0 0 var(--mf-space-md);\n padding: var(--mf-space-md);\n background: var(--mf-bg-hover);\n border-radius: var(--mf-radius-md);\n overflow-x: auto;\n}\n\n.magicfeedback-info-message pre code,\n.magicfeedback-start-message pre code {\n background: none;\n padding: 0;\n}\n\n.magicfeedback-info-message hr,\n.magicfeedback-start-message hr {\n border: none;\n border-top: 1px solid var(--mf-border);\n margin: var(--mf-space-lg) 0;\n}\n\n.magicfeedback-success-message img,\n.magicfeedback-info-message img,\n.magicfeedback-start-message img {\n max-width: 100%;\n height: auto;\n margin: var(--mf-space-lg) auto;\n display: block;\n border-radius: var(--mf-radius-md);\n}\n\n.magicfeedback-image {\n max-height: 400px;\n height: auto;\n max-width: 100%;\n margin: var(--mf-space-lg) auto;\n display: block;\n border-radius: var(--mf-radius-md);\n}\n\n/* ========================================\n FORM STRUCTURE\n ======================================== */\n.magicfeedback-form {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-lg);\n padding: clamp(16px, 2.5vw, 28px);\n}\n\n.magicfeedback-questions {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-xl);\n width: 100%;\n}\n\n.magicfeedback-div {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-sm);\n animation: mf-slide-in 0.3s ease-out;\n background: var(--mf-bg-secondary);\n border-radius: var(--mf-radius-md);\n}\n\n/* ========================================\n LABELS & TYPOGRAPHY\n ======================================== */\n.magicfeedback-label {\n font-size: var(--mf-font-size-lg);\n font-weight: var(--mf-font-weight-bold);\n color: var(--mf-text-primary);\n line-height: var(--mf-line-height);\n text-align: start;\n letter-spacing: -0.01em;\n}\n\n.magicfeedback-sublabel {\n font-size: var(--mf-font-size-base);\n font-weight: var(--mf-font-weight-normal);\n color: var(--mf-text-secondary);\n margin-bottom: var(--mf-space-sm);\n}\n\n.deepdots-popup h2 {\n font-size: var(--mf-font-size-xl);\n font-weight: 600;\n color: var(--mf-text-primary);\n margin: 0 0 var(--mf-space-xl) 0;\n text-align: center;\n letter-spacing: -0.01em;\n}\n\n.deepdots-popup p {\n margin: 0;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-secondary);\n}\n\n/* ========================================\n FORM INPUTS\n ======================================== */\n.deepdots-popup input[type=\"date\"],\n.deepdots-popup input[type=\"text\"],\n.deepdots-popup input[type=\"number\"],\n.deepdots-popup input[type=\"email\"],\n.deepdots-popup input[type=\"password\"],\n.deepdots-popup textarea,\n.deepdots-popup select {\n padding: 0.75rem 0.9rem;\n font-size: var(--mf-font-size-base);\n font-family: inherit;\n color: var(--mf-text-primary);\n background-color: var(--mf-surface);\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n outline: none;\n transition: var(--mf-transition);\n -webkit-appearance: none;\n appearance: none;\n box-shadow: var(--mf-shadow-sm);\n}\n\n.deepdots-popup textarea {\n min-height: 100px;\n resize: vertical;\n}\n\n.deepdots-popup input::placeholder,\n.deepdots-popup textarea::placeholder {\n color: var(--mf-text-muted);\n}\n\n/* Remove number input spinners */\n.deepdots-popup input::-webkit-outer-spin-button,\n.deepdots-popup input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.deepdots-popup input[type=\"number\"] {\n /* -moz-appearance: textfield; removed for lint compatibility */\n}\n\n/* Custom Select Arrow */\n.deepdots-popup select {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right var(--mf-space-md) center;\n padding-inline-end: var(--mf-space-xl);\n}\n\n/* Error State */\n.deepdots-popup input.error,\n.deepdots-popup select.error,\n.deepdots-popup textarea.error {\n border-color: var(--mf-error);\n}\n\n.magicfeedback-error {\n margin-top: var(--mf-space-xs);\n font-size: var(--mf-font-size-sm);\n color: var(--mf-error);\n}\n\n/* ========================================\n CHECKBOX & RADIO OPTIONS\n ======================================== */\n.magicfeedback-checkbox,\ndiv.magicfeedback-radio {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-sm);\n width: 100%;\n}\n\n.magicfeedback-checkbox-container,\n.magicfeedback-radio-container {\n display: flex;\n align-items: center;\n gap: var(--mf-space-sm);\n padding: var(--mf-space-md);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-checkbox-container:hover,\n .magicfeedback-radio-container:hover {\n background-color: var(--mf-bg-hover);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-checkbox-container:has(input:checked),\n.magicfeedback-radio-container:has(input:checked) {\n background-color: var(--mf-primary-light);\n border-color: var(--mf-primary);\n}\n\n.magicfeedback-checkbox input[type=\"checkbox\"],\n.magicfeedback-radio-container input[type=\"radio\"] {\n width: 18px;\n height: 18px;\n margin: 0;\n cursor: pointer;\n flex-shrink: 0;\n accent-color: var(--mf-primary);\n}\n\n.magicfeedback-checkbox label,\n.magicfeedback-radio-container label {\n font-size: var(--mf-font-size-base);\n text-align: start;\n color: var(--mf-text-primary);\n cursor: pointer;\n margin: 0;\n flex: 1;\n}\n\n/* ========================================\n BOOLEAN OPTIONS\n ======================================== */\n.magicfeedback-boolean-container {\n display: flex;\n gap: var(--mf-space-md);\n width: 100%;\n}\n\n.magicfeedback-boolean-option {\n flex: 1;\n position: relative;\n}\n\n.magicfeedback-boolean-option input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.magicfeedback-boolean-option label {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--mf-space-md);\n font-size: var(--mf-font-size-base);\n font-weight: 500;\n color: var(--mf-text-secondary);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n margin: 0;\n width: -webkit-fill-available;\n height: inherit;\n}\n\n@media (hover: hover) {\n .magicfeedback-boolean-option label:hover {\n background-color: var(--mf-bg-hover);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-boolean-option input[type=\"radio\"]:checked + label {\n color: var(--mf-primary);\n background-color: var(--mf-primary-light);\n border-color: var(--mf-primary);\n font-weight: 600;\n}\n\n/* ========================================\n RATING SYSTEMS\n ======================================== */\n.magicfeedback-rating,\n.magicfeedback-rating-number,\n.magicfeedback-rating-star {\n width: 100%;\n}\n\n.magicfeedback-rating-container,\n.magicfeedback-rating-number-container,\n.magicfeedback-rating-star-container {\n display: flex;\n flex-wrap: wrap;\n gap: var(--mf-space-sm);\n justify-content: center;\n align-items: center;\n padding: var(--mf-space-md) 0;\n list-style: none;\n margin: 0;\n}\n\n/* Caption line below the row — min on one side, max on the other (swapped\n for order: \"rtl\") — same simple, non-responsive pattern as the\n rating-number row caption. Used by rating-star and rating-emoji. */\n.magicfeedback-rating-placeholder {\n display: flex;\n justify-content: space-between;\n gap: var(--mf-space-md);\n width: 100%;\n margin-top: var(--mf-space-sm);\n font-size: var(--mf-font-size-sm);\n font-weight: var(--mf-font-weight-semibold);\n color: var(--mf-text-secondary);\n}\n\n.magicfeedback-rating-placeholder-value {\n overflow-wrap: anywhere;\n}\n\n.magicfeedback-rating-placeholder-value:last-child {\n text-align: end;\n}\n\n/* Rating with Images */\n.magicfeedback-rating-option-label-container input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.magicfeedback-rating-option-label-container img {\n width: 56px;\n height: 56px;\n cursor: pointer;\n transition: var(--mf-transition-fast);\n border-radius: var(--mf-radius-full);\n border: 2px solid transparent;\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-option-label-container img:hover {\n transform: scale(1.1);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-rating-option-label-container input[type=\"radio\"]:checked + img {\n border-color: var(--mf-primary);\n box-shadow: 0 0 0 3px var(--mf-primary-light);\n}\n\n.magicfeedback-rating-option-label-container label {\n display: block;\n text-align: center;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-secondary);\n margin-top: var(--mf-space-xs);\n cursor: pointer;\n}\n\n/* Rating Numbers — each option is its own outlined chip in the survey's\n primary color, with a gap between them. Row and column use the same\n chip; column additionally has room to show each option's own text\n plainly next to its number. */\n\n/* Every size here is width:100%/flex:1 combined with the chip's own\n padding and border. Without border-box, the browser's default\n content-box adds that padding/border on TOP of the declared size, so\n the chip quietly overflows past its flex slot — reading as \"not quite\n full width\" or, worse, pushing into the next chip. */\n.magicfeedback-rating-number-container,\n.magicfeedback-rating-number-option,\n.magicfeedback-rating-number-option-label-container,\n.magicfeedback-rating-number-value {\n box-sizing: border-box;\n}\n\n/* The shared rating-container rule (above, reused by star rating) sets\n align-items:center for its row-of-chips layout — in a stacked column\n that still nudges a 100%-wide chip inward instead of a true 0 offset.\n Force stretch so every chip fills the exact same width, flush on\n both sides. */\n.magicfeedback-rating-number-container-column,\n.magicfeedback-rating-number-container-column-reverse {\n align-items: stretch;\n}\n\n/* Caption line below the row — the single place min/max labels live\n for row mode, shared by mobile and desktop. */\n.magicfeedback-rating-number-scale-labels {\n display: flex;\n justify-content: space-between;\n gap: var(--mf-space-md);\n margin-top: var(--mf-space-sm);\n font-size: var(--mf-font-size-sm);\n font-weight: var(--mf-font-weight-semibold);\n color: var(--mf-text-secondary);\n}\n\n.magicfeedback-rating-number-scale-label {\n overflow-wrap: anywhere;\n}\n\n.magicfeedback-rating-number-scale-label:last-child {\n text-align: end;\n}\n\n/* Column, no per-option text: each row is its own separated chip, so\n the min/max labels bookend the stack from outside it — one line\n above the first chip, one below the last. Margin goes on whichever\n side actually faces the list — the \"before\" label (always the first\n child) gets space below it, the \"after\" one (always right after the\n container, extra option row or not) gets space above it — so both\n sit the same distance from the chips regardless of which one it is. */\n.magicfeedback-rating-number-scale-label-block {\n font-size: var(--mf-font-size-sm);\n font-weight: var(--mf-font-weight-semibold);\n color: var(--mf-text-secondary);\n text-align: center;\n}\n\n.magicfeedback-rating-number-scale-label-block:first-child {\n margin-bottom: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-container + .magicfeedback-rating-number-scale-label-block {\n margin-top: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-option {\n display: flex;\n}\n\n.magicfeedback-rating-number-option-label-container {\n position: relative;\n width: 100%;\n display: flex;\n cursor: pointer;\n}\n\n.magicfeedback-rating-number-option-label-container input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.magicfeedback-rating-number-value {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n transition: var(--mf-transition-fast);\n color: var(--mf-text-secondary);\n font-weight: var(--mf-font-weight-semibold);\n}\n\n/* Row: individual outlined chips in the survey's own primary color —\n var(--mf-primary) so whatever brand color a given survey sets carries\n through automatically, no color hardcoded here. */\n.magicfeedback-rating-number-container-row,\n.magicfeedback-rating-number-container-row-reverse {\n flex-wrap: nowrap;\n gap: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-option-row,\n.magicfeedback-rating-number-option-row-reverse {\n flex: 1 1 0;\n min-width: 0;\n}\n\n.magicfeedback-rating-number-option-row .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-value {\n min-height: 56px;\n padding: var(--mf-space-sm);\n border: 2px solid var(--mf-primary-border);\n border-radius: var(--mf-radius-md);\n background: var(--mf-bg-secondary);\n color: var(--mf-primary);\n font-size: var(--mf-font-size-lg);\n font-weight: var(--mf-font-weight-bold);\n}\n\n/* More than 6 options (e.g. a 0-10 NPS scale): still one row, never\n wraps — but scaled down on purpose, so it reads as a compact chip\n rather than the same 56px button stretched thin. */\n.magicfeedback-rating-number-container-row--dense .magicfeedback-rating-number-option-row .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-container-row--dense .magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-value {\n min-height: 36px;\n padding: var(--mf-space-xs);\n border-radius: var(--mf-radius-sm);\n font-size: 0.8rem;\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-number-option-row .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value,\n .magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {\n background: var(--mf-primary-light);\n }\n}\n\n.magicfeedback-rating-number-option-row .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value {\n color: #fff;\n background-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-md);\n}\n\n/* Extra option: always its own row below the scale, and styled like every\n other chip now — \"doesn't apply\" is still not a point on the 1..N\n continuum, but it no longer needs a different border style to say so. */\n.magicfeedback-rating-number-extra-row {\n display: flex;\n justify-content: center;\n margin-top: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-option--extra {\n flex: 0 1 auto;\n}\n\n.magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container {\n width: auto;\n}\n\n.magicfeedback-rating-number-option--extra .magicfeedback-rating-number-value {\n min-height: 36px;\n padding: 0 var(--mf-space-md);\n border: 2px solid var(--mf-primary-border);\n border-radius: var(--mf-radius-md);\n background: var(--mf-bg-secondary);\n color: var(--mf-primary);\n font-size: var(--mf-font-size-sm);\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {\n background: var(--mf-primary-light);\n }\n}\n\n.magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value {\n color: #fff;\n background-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-md);\n}\n\n/* Column: the same separated chips as row, just stacked — each option is\n its own outlined chip with a gap between them, not one shared box. */\n.magicfeedback-rating-number-container-column,\n.magicfeedback-rating-number-container-column-reverse {\n gap: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-option-column,\n.magicfeedback-rating-number-option-column-reverse {\n width: 100%;\n}\n\n.magicfeedback-rating-number-option-column .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-value {\n justify-content: flex-start;\n gap: var(--mf-space-sm);\n min-height: 48px;\n padding: var(--mf-space-sm) var(--mf-space-md);\n border: 2px solid var(--mf-primary-border);\n border-radius: var(--mf-radius-md);\n background: var(--mf-bg-secondary);\n color: var(--mf-primary);\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value,\n .magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {\n background: var(--mf-primary-light);\n }\n}\n\n.magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value {\n color: #fff;\n background-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-md);\n}\n\n.magicfeedback-rating-number-value-num {\n font-weight: var(--mf-font-weight-bold);\n}\n\n/* Column with per-option text: a min-width so \"1\"..\"9\" and \"10\" line up,\n with the label text starting at the same x. Skipped entirely when the\n column has no per-option text at all (--bare below) — with nothing to\n align against, the same left-shift bug from row mode would resurface. */\n.magicfeedback-rating-number-container-column:not(.magicfeedback-rating-number-container-column--bare) .magicfeedback-rating-number-value-num,\n.magicfeedback-rating-number-container-column-reverse:not(.magicfeedback-rating-number-container-column--bare) .magicfeedback-rating-number-value-num {\n flex: none;\n min-width: 1.4em;\n}\n\n/* Column, no per-option text at all (only min/max, or nothing): a bare\n numbered list has nothing to left-align against, so center each\n number instead of stranding it on the left with blank space beside it. */\n.magicfeedback-rating-number-container-column--bare .magicfeedback-rating-number-value {\n justify-content: center;\n}\n\n.magicfeedback-rating-number-value-label {\n font-weight: var(--mf-font-weight-medium);\n}\n\n.magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value {\n color: #fff;\n background-color: var(--mf-primary);\n}\n\n/* Star Rating */\n.magicfeedback-rating-star-container {\n justify-content: center;\n}\n\n.magicfeedback-rating-star-option:not(:checked) > .rating__star {\n font-size: 40px;\n color: var(--mf-border);\n cursor: pointer;\n margin: 0 var(--mf-space-xs);\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-star-option:not(:checked) > .rating__star:hover,\n .magicfeedback-rating-star-option:not(:checked) > .rating__star:hover ~ span {\n color: #fbbf24;\n transform: scale(1.1);\n }\n}\n\n.magicfeedback-rating-star-selected {\n color: #fbbf24 !important;\n}\n\n/* ========================================\n MULTIPLE CHOICE WITH IMAGES\n ======================================== */\n.magicfeedback-multiple-choice-image-option {\n display: flex;\n border-radius: var(--mf-radius-md);\n overflow: hidden;\n transition: var(--mf-transition-fast);\n}\n\n.magicfeedback-image-option-label-container {\n cursor: pointer;\n border-radius: var(--mf-radius-md);\n overflow: hidden;\n transition: var(--mf-transition-fast);\n}\n\n.magicfeedback-image-option-label-container input[type=\"radio\"],\n.magicfeedback-image-option-label-container input[type=\"checkbox\"] {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.magicfeedback-image-option-label-container img {\n width: 100%;\n height: auto;\n display: block;\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-image-option-label-container:hover img {\n border-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-md);\n }\n}\n\n.magicfeedback-image-option-label-container input:checked + img {\n border-color: var(--mf-primary);\n box-shadow: 0 0 0 3px var(--mf-primary-light);\n}\n\n/* ========================================\n MATRIX QUESTIONS\n ======================================== */\n.magicfeedback-multi-question-matrix-container {\n width: 100%;\n overflow-x: auto;\n border-radius: var(--mf-radius-md);\n}\n\n.magicfeedback-multi-question-matrix-table {\n width: 100%;\n border-collapse: separate;\n border-spacing: 0 var(--mf-space-xs);\n}\n\n.magicfeedback-multi-question-matrix-table th {\n padding: var(--mf-space-md);\n text-align: center;\n font-size: var(--mf-font-size-sm);\n font-weight: 500;\n color: var(--mf-text-secondary);\n}\n\n.magicfeedback-multi-question-matrix-row-tr {\n background-color: var(--mf-bg-primary);\n border-radius: var(--mf-radius-md);\n transition: var(--mf-transition-fast);\n}\n\n.magicfeedback-multi-question-matrix-row-tr:nth-child(even) {\n background-color: var(--mf-bg-secondary);\n}\n\n@media (hover: hover) {\n .magicfeedback-multi-question-matrix-row-tr:hover {\n background-color: var(--mf-bg-hover);\n box-shadow: var(--mf-shadow-sm);\n }\n}\n\n.magicfeedback-multi-question-matrix-table td {\n padding: var(--mf-space-md);\n text-align: center;\n}\n\n.magicfeedback-multi-question-matrix-label {\n text-align: start;\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-primary);\n}\n\n.magicfeedback-multi-question-matrix-table input[type=\"radio\"] {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--mf-primary);\n}\n\n/* ========================================\n PRIORITY LIST\n ======================================== */\n.magicfeedback-priority-list-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mf-space-md);\n margin: var(--mf-space-sm) 0;\n background-color: var(--mf-bg-primary);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-priority-list-item:hover {\n box-shadow: var(--mf-shadow-sm);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-priority-list-item-label {\n flex: 1;\n font-size: var(--mf-font-size-base);\n text-align: start;\n color: var(--mf-text-primary);\n margin: 0;\n}\n\n.magicfeedback-priority-list-arrow-up,\n.magicfeedback-priority-list-arrow-down {\n padding: var(--mf-space-xs);\n font-size: var(--mf-font-size-lg);\n color: var(--mf-text-secondary);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n user-select: none;\n}\n\n@media (hover: hover) {\n .magicfeedback-priority-list-arrow-up:hover,\n .magicfeedback-priority-list-arrow-down:hover {\n color: var(--mf-primary);\n transform: scale(1.2);\n }\n}\n\n/* ========================================\n POINT SYSTEM\n ======================================== */\n.magicfeedback-point-system-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mf-space-md);\n margin: var(--mf-space-sm) 0;\n background-color: var(--mf-bg-primary);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n min-height: 45px;\n}\n\n.magicfeedback-point-system-item label {\n flex: 1;\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-primary);\n margin: 0;\n}\n\n.magicfeedback-point-system-input-container {\n display: flex;\n align-items: center;\n gap: var(--mf-space-sm);\n}\n\n.magicfeedback-point-system-item input {\n width: 80px;\n min-width: 80px;\n padding: var(--mf-space-sm);\n text-align: center;\n font-size: var(--mf-font-size-base);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n margin: 0;\n}\n\n.magicfeedback-point-system-total {\n display: flex;\n justify-content: flex-end;\n margin-top: var(--mf-space-md);\n font-size: var(--mf-font-size-base);\n font-weight: 600;\n color: var(--mf-text-primary);\n}\n\n/* ========================================\n CONSENT\n ======================================== */\n.magicfeedback-consent-container {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--mf-space-sm);\n padding: var(--mf-space-md);\n background-color: var(--mf-bg-secondary);\n border-radius: var(--mf-radius-md);\n margin: var(--mf-space-md) 0;\n}\n\n.magicfeedback-consent-container input[type=\"checkbox\"] {\n margin-top: 2px;\n flex-shrink: 0;\n accent-color: var(--mf-primary);\n}\n\n.magicfeedback-consent-container label {\n flex: 1;\n font-size: var(--mf-font-size-sm);\n text-align: start;\n color: var(--mf-text-secondary);\n line-height: var(--mf-line-height);\n cursor: pointer;\n margin-bottom: 0;\n}\n\n/* ========================================\n ACTION BUTTONS\n ======================================== */\n.magicfeedback-action-container {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--mf-space-md);\n margin-top: var(--mf-space-xl);\n padding-top: var(--mf-space-lg);\n border-top: 1px solid var(--mf-border);\n}\n\n.magicfeedback-submit {\n padding: 0.8rem 1.6rem;\n font-size: var(--mf-font-size-base);\n font-weight: 800;\n color: #FFFFFF;\n background-color: var(--mf-primary);\n border: 2px solid var(--mf-primary);\n border-radius: var(--mf-radius-full);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n box-shadow: var(--mf-shadow-md);\n}\n\n@media (hover: hover) {\n .magicfeedback-submit:hover {\n background-color: var(--mf-primary-hover);\n box-shadow: var(--mf-shadow-lg);\n transform: translateY(-2px);\n }\n}\n\n.magicfeedback-submit:active {\n transform: translateY(0);\n box-shadow: var(--mf-shadow-sm);\n}\n\n.magicfeedback-submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n}\n\n.magicfeedback-back {\n padding: 0.65rem 1rem;\n font-size: var(--mf-font-size-base);\n font-weight: 700;\n color: var(--mf-primary);\n background-color: var(--mf-bg-secondary);\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-full);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-back:hover {\n border-color: var(--mf-primary);\n background-color: var(--mf-bg-hover);\n }\n}\n\n/* ========================================\n SKIP & COUNTER\n ======================================== */\n.magicfeedback-skip-container {\n display: flex;\n align-items: center;\n gap: var(--mf-space-xs);\n padding: 0;\n margin: 0;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-muted);\n background: transparent;\n border: none;\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-skip-container:hover {\n color: var(--mf-primary);\n }\n}\n\n.magicfeedback-skip-container input[type=\"checkbox\"] {\n margin: 0;\n cursor: pointer;\n accent-color: var(--mf-primary);\n}\n\n.magicfeedback-counter {\n display: flex;\n justify-content: flex-end;\n margin-top: var(--mf-space-md);\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-muted);\n}\n\n/* ========================================\n MODAL STYLES\n ======================================== */\n/* Modal backdrop */\n.magicfeedback-modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n background: rgba(0,0,0,0.4);\n display: none; /* toggled to flex when open */\n align-items: center;\n justify-content: center;\n z-index: 9999;\n}\n\n/* Modal container */\n.magicfeedback-modal {\n background: #fff;\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(0,0,0,0.2);\n max-width: 520px;\n width: 90%;\n max-height: 80vh;\n /* Flex column so the options list scrolls while the actions footer\n (counter + confirm button) stays pinned and always visible. */\n display: flex;\n flex-direction: column;\n overflow: hidden;\n padding: 16px;\n position: relative;\n /* Portalled to document.body (see renderPriorityList.ts) to escape the\n host page's overflow/z-index — so it never inherits\n .magicfeedback-container's neutral text color and instead inherits\n whatever color the host page's own <body> happens to set. Needs its\n own explicit baseline rather than relying on that inheritance. */\n color: var(--mf-text-primary);\n}\n\n/* Modal actions */\n.magicfeedback-modal-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 12px;\n flex-shrink: 0;\n}\n\n.magicfeedback-modal-counter {\n margin-inline-end: auto;\n display: flex;\n align-items: center;\n font-size: 14px;\n color: var(--mf-text-secondary);\n}\n\n/* Modal list */\n.magicfeedback-modal-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n /* Scrollable area; footer stays fixed below it. */\n flex: 1 1 auto;\n overflow-y: auto;\n min-height: 0;\n}\n\n.magicfeedback-modal-row {\n display: flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n}\n\n/* Priority list header */\n.magicfeedback-priority-list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 8px;\n}\n\n.magicfeedback-priority-list-reorder {\n margin-top: 8px;\n}\n\n.magicfeedback-priority-list-list {\n padding: 0;\n list-style: none;\n}\n\n.magicfeedback-priority-list-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin: 5px 0;\n}\n\n.magicfeedback-priority-list-item-label {\n font-size: 15px;\n}\n\n.magicfeedback-priority-list-arrows {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n/* Buttons */\n.magicfeedback-button {\n appearance: none;\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-full);\n background: var(--mf-bg-secondary);\n color: var(--mf-text-primary);\n padding: 0.6rem 1rem;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n box-shadow: var(--mf-shadow-sm);\n}\n\n@media (hover: hover) {\n .magicfeedback-button:hover {\n background: var(--mf-bg-hover);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-button:active {\n transform: translateY(1px);\n}\n\n.magicfeedback-button-primary {\n border-color: var(--mf-primary);\n background: var(--mf-primary);\n color: #fff;\n box-shadow: var(--mf-shadow-md);\n}\n\n@media (hover: hover) {\n .magicfeedback-button-primary:hover {\n background: var(--mf-primary-hover);\n }\n}\n\n.magicfeedback-button-primary:active {\n background: var(--mf-primary-hover);\n}\n\n/* Warning flash */\n.magicfeedback-warning {\n color: #C70039;\n}\n\n/* Start message button */\n.magicfeedback-start-message-button {\n appearance: none;\n border: 2px solid var(--mf-primary);\n border-radius: var(--mf-radius-full);\n background: var(--mf-primary);\n color: #fff;\n padding: 0.8rem 1.4rem;\n font-size: 15px;\n font-weight: 800;\n cursor: pointer;\n box-shadow: var(--mf-shadow-md);\n}\n\n@media (hover: hover) {\n .magicfeedback-start-message-button:hover {\n background: var(--mf-primary-hover);\n }\n}\n\n/* ========================================\n RESPONSIVE DESIGN\n ======================================== */\n@media (max-width: 768px) {\n :root {\n --mf-space-lg: 1.25rem;\n --mf-space-xl: 1.5rem;\n }\n\n .magicfeedback-container {\n padding: 12px;\n border-radius: var(--mf-radius-md);\n }\n\n .magicfeedback-form {\n padding: 14px;\n border-radius: var(--mf-radius-md);\n }\n\n .magicfeedback-div {\n padding: 12px;\n gap: var(--mf-space-lg)\n }\n\n .magicfeedback-label {\n font-size: var(--mf-font-size-base);\n }\n\n .deepdots-popup input[type=\"date\"],\n .deepdots-popup input[type=\"text\"],\n .deepdots-popup input[type=\"number\"],\n .deepdots-popup input[type=\"email\"],\n .deepdots-popup input[type=\"password\"],\n .deepdots-popup textarea,\n .deepdots-popup select {\n padding: 0.85rem 0.95rem;\n font-size: 1rem;\n min-height: 46px;\n }\n\n /*\n .magicfeedback-radio-container,\n .magicfeedback-checkbox-container,\n .magicfeedback-boolean-container,\n .magicfeedback-consent-container {\n width: 100%;\n }\n */\n\n .magicfeedback-radio-container,\n .magicfeedback-checkbox-container {\n padding: 10px 12px;\n border-radius: var(--mf-radius-md);\n border: 1px solid var(--mf-border);\n gap: 10px;\n }\n\n .magicfeedback-rating-container,\n .magicfeedback-rating-number-container,\n .magicfeedback-rating-star-container {\n gap: 10px;\n }\n\n .magicfeedback-boolean-container {\n flex-direction: column;\n gap: 10px;\n }\n\n .magicfeedback-rating-number-container-row,\n .magicfeedback-rating-number-container-row-reverse {\n gap: 6px;\n }\n\n .magicfeedback-rating-number-option-row .magicfeedback-rating-number-value,\n .magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-value {\n min-height: 44px;\n padding: var(--mf-space-xs);\n font-size: 0.9rem;\n }\n\n .magicfeedback-action-container {\n flex-direction: column-reverse;\n }\n\n .magicfeedback-submit,\n .magicfeedback-back {\n width: 100%;\n text-align: center;\n min-height: 48px;\n }\n}\n\n/* ========================================\n ACCESSIBILITY\n ======================================== */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n\n/* ========================================\n UTILITY CLASSES\n ======================================== */\n.magicfeedback-visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n\n.magicfeedback-modal-close {\n position: absolute;\n top: 8px;\n inset-inline-end: 8px;\n background: transparent;\n border: none;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n color: #666;\n}\n\n.magicfeedback-modal-close:focus {\n color: #000;\n outline: none;\n}\n\n@media (hover: hover) {\n .magicfeedback-modal-close:hover {\n color: #000;\n outline: none;\n }\n}\n\n/* ========================================\n FILE / IMAGE UPLOAD\n ======================================== */\n.magicfeedback-upload {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-md);\n width: 100%;\n margin: 10px 0;\n}\n\n/* The native input is an invisible, focusable overlay covering the dropzone. */\n.magicfeedback-upload-native {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n opacity: 0;\n cursor: pointer;\n z-index: 2;\n}\n\n.magicfeedback-upload-dropzone {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--mf-space-sm);\n box-sizing: border-box;\n width: 100%;\n min-height: 150px;\n padding: var(--mf-space-xl);\n text-align: center;\n border: 2px dashed var(--mf-border);\n border-radius: var(--mf-radius-md);\n background-color: var(--mf-surface-alt);\n cursor: pointer;\n transition: var(--mf-transition);\n}\n\n@media (hover: hover) {\n .magicfeedback-upload-dropzone:hover {\n border-color: var(--mf-primary);\n background-color: var(--mf-bg-hover);\n }\n}\n\n.magicfeedback-upload-dropzone:focus-within {\n border-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-focus);\n}\n\n.magicfeedback-upload-dropzone.is-dragover {\n border-color: var(--mf-primary);\n border-style: solid;\n background-color: var(--mf-primary-light);\n box-shadow: var(--mf-shadow-focus);\n}\n\n.magicfeedback-upload-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 46px;\n height: 46px;\n color: var(--mf-primary);\n background-color: var(--mf-surface);\n border-radius: var(--mf-radius-full);\n box-shadow: var(--mf-shadow-sm);\n pointer-events: none;\n}\n\n.magicfeedback-upload-icon svg {\n width: 24px;\n height: 24px;\n}\n\n.magicfeedback-upload-cta {\n font-family: inherit;\n font-weight: var(--mf-font-weight-semibold);\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-primary);\n pointer-events: none;\n}\n\n.magicfeedback-upload-hint {\n font-family: inherit;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-muted);\n pointer-events: none;\n}\n\n.magicfeedback-upload-list {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-sm);\n width: 100%;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.magicfeedback-upload-list:empty {\n display: none;\n}\n\n.magicfeedback-upload-file-item {\n display: flex;\n align-items: center;\n gap: var(--mf-space-md);\n padding: var(--mf-space-sm) var(--mf-space-md);\n background-color: var(--mf-surface);\n border: 1px solid var(--mf-border);\n border-radius: var(--mf-radius-sm);\n box-shadow: var(--mf-shadow-sm);\n animation: mf-scale-in 0.2s ease-out;\n}\n\n.magicfeedback-upload-thumb {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n overflow: hidden;\n color: var(--mf-text-secondary);\n background-color: var(--mf-surface-alt);\n border-radius: var(--mf-radius-sm);\n}\n\n.magicfeedback-upload-thumb img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.magicfeedback-upload-thumb svg {\n width: 20px;\n height: 20px;\n}\n\n.magicfeedback-upload-file-info {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-width: 0;\n text-align: start;\n}\n\n.magicfeedback-upload-file-name {\n font-family: inherit;\n font-size: var(--mf-font-size-sm);\n font-weight: var(--mf-font-weight-semibold);\n color: var(--mf-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.magicfeedback-upload-file-size {\n font-family: inherit;\n font-size: 0.75rem;\n color: var(--mf-text-muted);\n}\n\n.magicfeedback-upload-remove {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n padding: 0;\n color: var(--mf-text-muted);\n background-color: transparent;\n border: none;\n border-radius: var(--mf-radius-full);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n.magicfeedback-upload-remove:focus-visible {\n color: var(--mf-error);\n background-color: var(--mf-primary-light);\n outline: none;\n}\n\n@media (hover: hover) {\n .magicfeedback-upload-remove:hover {\n color: var(--mf-error);\n background-color: var(--mf-primary-light);\n outline: none;\n }\n}\n\n.magicfeedback-upload-remove svg {\n width: 16px;\n height: 16px;\n}\n\n.magicfeedback-upload-error {\n display: none;\n font-family: inherit;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-error);\n}\n\n.magicfeedback-upload-error.is-visible {\n display: block;\n}\n\n/* ========================================\n RTL (Arabic)\n ========================================\n The SDK stamps dir=\"rtl\" (and .magicfeedback-rtl) on the survey container\n for right-to-left languages. Everything else in this stylesheet uses logical\n properties (text-align: start, padding-inline-*, inset-inline-*), so it\n mirrors on its own; only the handful of rules that cannot be expressed\n logically live here.\n\n Rules are scoped to [dir=\"rtl\"] inside our own subtree so a host page that is\n itself RTL, or one that embeds an Arabic survey in an LTR page, both render\n correctly. */\n\n/* background-position has no logical form: move the select caret to the\n left edge, opposite the padding that inset-inline-end already mirrored. */\n[dir=\"rtl\"] select {\n background-position: left var(--mf-space-md) center;\n}\n\n/* Emails, URLs, numbers, dates and phone numbers are latin/numeric content\n even in an Arabic survey: keep their own direction LTR so punctuation\n (@, ., +, /) lands where the user expects, but align them to the start of\n the RTL line so they still read as part of the form. */\n[dir=\"rtl\"] input[type=\"email\"],\n[dir=\"rtl\"] input[type=\"url\"],\n[dir=\"rtl\"] input[type=\"number\"],\n[dir=\"rtl\"] input[type=\"tel\"],\n[dir=\"rtl\"] input[type=\"date\"],\n[dir=\"rtl\"] input[type=\"time\"] {\n direction: ltr;\n text-align: right;\n}\n\n/* Mirror of mf-slide-in: questions enter from the reading edge. */\n@keyframes mf-slide-in-rtl {\n from {\n opacity: 0;\n transform: translateX(16px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n[dir=\"rtl\"] .magicfeedback-div {\n animation-name: mf-slide-in-rtl;\n}\n\n/* ========================================\n LOCAL — reglas del popup, no vienen del paquete\n ======================================== */\n\n/* Los controles de formulario no heredan font-family: cuando el popup trae fuente propia\n (clase deepdots-has-font), se les fuerza la del contenedor. */\n.deepdots-popup.deepdots-has-font button,\n.deepdots-popup.deepdots-has-font input,\n.deepdots-popup.deepdots-has-font textarea,\n.deepdots-popup.deepdots-has-font select {\n font-family: var(--deepdots-font, inherit);\n}\n\n/* WebKit sí aplica el padding al radio nativo: con padding el control se dibuja enorme (se vio\n en iOS, WebView de RN y Safari, no en Chrome). El paquete ya no lo pone, pero la regla es\n barata y evita que vuelva. */\n.deepdots-popup input[type=\"radio\"] {\n padding: 0;\n}\n","import type { DeepdotsLogger } from '../types';\n\n/**\n * Logger de módulo compartido: el core lo fija en init() con el logger inyectado\n * por el host (o `console` por defecto), y los módulos que no reciben `this`\n * (renderPopup, renderers) enrutan por aquí. `warn`/`error` caen a `log` si el\n * logger del host no los provee (solo `log` es obligatorio en DeepdotsLogger).\n */\nlet active: DeepdotsLogger = console;\n\nexport function setLogger(logger: DeepdotsLogger | undefined): void {\n active = logger ?? console;\n}\n\nexport function sdkLog(...args: unknown[]): void {\n active.log(...args);\n}\n\nexport function sdkWarn(...args: unknown[]): void {\n (active.warn ?? active.log)(...args);\n}\n\nexport function sdkError(...args: unknown[]): void {\n (active.error ?? active.log)(...args);\n}\n","/**\n * Logo del popup (viene en `formData.style` del survey, no del `init`).\n *\n * Vive encima de la barra de progreso, como hermano del header: la marca abre la tarjeta y el\n * progreso queda pegado a la pregunta. Antes se insertaba DENTRO del contenido (web) o del área\n * scrollable (WebView de RN), así que la barra quedaba por encima del logo y en RN el logo se\n * iba con el scroll.\n *\n * El hueco inferior lo pone el bloque siguiente (`.deepdots-progress` cuando se ve, o el\n * `padding-top` del contenido cuando no), así que aquí solo se separa del header.\n */\nexport interface PopupLogoStyle {\n logo?: string;\n logoSize?: string;\n logoPosition?: string;\n}\n\nconst BASE_CSS = 'max-height:40px; max-width:100%; object-fit:contain; display:block; margin:12px 0 0 0;';\n\nconst MAX_HEIGHT: Record<string, string> = {\n small: '30px',\n medium: '50px',\n large: '70px',\n};\n\n/**\n * Inserta el logo en `parent`, justo antes de `anchor` (la barra de progreso).\n * No-op si el estilo no trae logo o si ya existe uno con ese `id`.\n */\nexport function insertPopupLogo(\n parent: HTMLElement,\n anchor: HTMLElement | null,\n style: PopupLogoStyle | undefined,\n id: string,\n): HTMLImageElement | null {\n if (!style?.logo) return null;\n if (parent.ownerDocument.getElementById(id)) return null;\n\n const img = parent.ownerDocument.createElement('img');\n img.id = id;\n img.src = style.logo;\n img.alt = 'Logo';\n img.style.cssText = BASE_CSS;\n\n if (style.logoSize && MAX_HEIGHT[style.logoSize]) {\n img.style.maxHeight = MAX_HEIGHT[style.logoSize];\n }\n // Márgenes lógicos (inline-start/end) en vez de left/right: con un survey RTL (árabe) el\n // popup se pinta con dir=\"rtl\" y el logo tiene que acompañar al resto del chrome. En LTR\n // el resultado es idéntico al de antes.\n switch (style.logoPosition) {\n case 'left':\n img.style.margin = '12px 0 0 0';\n img.style.setProperty('margin-inline-start', '0');\n img.style.setProperty('margin-inline-end', '16px');\n break;\n case 'right':\n img.style.margin = '12px 0 0 0';\n img.style.setProperty('margin-inline-start', 'auto');\n img.style.setProperty('margin-inline-end', '0');\n break;\n case 'center':\n img.style.margin = '12px auto 0 auto';\n break;\n }\n\n parent.insertBefore(img, anchor);\n return img;\n}\n","import {DeepdotsEventType, PopupActions, PopupStyle, FormData} from '../types';\nimport { buildSurveyIdentity } from '../tracking/tracking-manager';\nimport type { PopupRenderOptions } from '../platform/renderer';\nimport { buildFontFaceCss, buildFontFamilyValue } from './font';\nimport { insertPopupLogo } from './logo';\nimport { applySurveyPrimaryColor } from './surveyPalette';\nimport { setSurveyBusy } from './busy';\nimport { sdkLog, sdkWarn, sdkError } from '../util/logger';\nimport { directionFor, getLabels, resolveActionLabels } from '../i18n/labels';\nimport { REVEAL_TIMEOUT_MS, revealWhenPainted as revealWhenImagesLoaded } from './reveal';\nimport magicfeedback from \"@magicfeedback/native\";\nimport magicfeedbackCss from '../assets/style.css';\n\n// Reexportado desde aquí porque este era su sitio original: la constante se mudó a `reveal.ts`\n// para que la ruta del WebView pueda leerla sin arrastrar `@magicfeedback/native`.\nexport { REVEAL_TIMEOUT_MS } from './reveal';\n\n// Inserta la hoja de estilos de MagicFeedback directamente en el popup para garantizar estilos incluso si el bundler no la inyecta globalmente.\nfunction ensureMagicFeedbackStyles(_popup: HTMLElement) {\n const STYLE_ID = 'magicfeedback-sdk-styles';\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = magicfeedbackCss;\n document.head.appendChild(style);\n}\n\n/**\n * Id del bloque que alinea el contenido del survey con el chrome del popup. Exportado para que\n * los tests puedan localizarlo sin depender de la posición en el `<head>`.\n */\nexport const SURVEY_ALIGNMENT_STYLE_ID = 'deepdots-survey-alignment';\n\n/**\n * El margen lateral lo pone la tarjeta del popup, pero el CSS de `@magicfeedback/native` añade el\n * suyo en tres capas: el contenedor, el formulario (hasta 28px) y el bloque de cada pregunta\n * (12px, con fondo igual al de la tarjeta, así que ni se ve). Sumados, dejan el enunciado y las\n * opciones bastante más adentro que el logo, el título y la barra de progreso, que sí llegan al\n * borde. Aquí se anula solo el lateral: el vertical es el que separa las preguntas.\n *\n * Va acotado a `.deepdots-popup` porque esta hoja se inyecta en el `<head>` del host y sin acotar\n * repintaría sus propios formularios; de paso, la especificidad extra hace que gane sobre el CSS\n * del paquete sin depender del orden.\n */\nfunction ensureSurveyAlignmentStyles() {\n if (document.getElementById(SURVEY_ALIGNMENT_STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = SURVEY_ALIGNMENT_STYLE_ID;\n style.textContent = `\n .deepdots-popup .magicfeedback-container,\n .deepdots-popup .magicfeedback-form,\n .deepdots-popup .magicfeedback-div { padding-left: 0; padding-right: 0; }\n `;\n document.head.appendChild(style);\n}\n\n// Añade estilos de spinner si no existen\nfunction ensureSpinnerStyles(_popup: HTMLElement) {\n if (document.getElementById('deepdots-spinner-styles')) return;\n const style = document.createElement('style');\n style.id = 'deepdots-spinner-styles';\n style.textContent = `\n /* El enunciado trae margin-top:20px del CSS de surveys, que sumaba al hueco de la barra\n de progreso y descompensaba la simetría. Solo se anula en el primero: entre preguntas\n de la misma página sigue separando. */\n .deepdots-popup .magicfeedback-questions .magicfeedback-div:first-child .magicfeedback-label { margin-top: 0; }\n @keyframes ddspin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n .mf-spinner { display:flex; justify-content:center; align-items:center; padding:8px 0; }\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 `;\n document.head.appendChild(style);\n}\n\nfunction ensureResponsiveStyles(_popup: HTMLElement) {\n if (document.getElementById('deepdots-responsive-styles')) return;\n const style = document.createElement('style');\n style.id = 'deepdots-responsive-styles';\n style.textContent = `\n /* Responsive adjustments */\n @media (max-width: 640px) {\n .deepdots-popup {\n width: calc(100% - 24px) !important;\n max-width: calc(100% - 24px) !important;\n height: auto !important;\n max-height: 90vh !important;\n border-radius: 12px !important;\n padding: calc(16px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom)) 16px !important;\n box-sizing: border-box;\n }\n .deepdots-popup .mf-spinner-circle { width: 32px; height: 32px; border-width: 4px; }\n .deepdots-popup button { font-size: 16px !important; }\n .deepdots-popup-header button { width:48px; height:48px; }\n .deepdots-popup-header button svg { width:26px; height:26px; }\n /* Apilados en el orden del DOM: la acción principal (Send/Start/Complete) arriba y\n Back debajo. */\n .deepdots-popup-footer { flex-direction: column !important; gap: 4px !important; }\n .deepdots-popup-footer button { width: 100%; }\n }\n @media (max-width: 400px) {\n .deepdots-popup { padding: calc(12px + env(safe-area-inset-top)) 12px calc(12px + env(safe-area-inset-bottom)) 12px !important; }\n .deepdots-popup-header button { width:48px; height:48px; }\n .deepdots-popup-header button svg { width:26px; height:26px; }\n }\n @media (orientation: landscape) and (max-height: 480px) {\n .deepdots-popup {\n height: 100vh !important;\n max-height: 100vh !important;\n overflow-y: auto !important;\n }\n }\n `;\n document.head.appendChild(style);\n}\n\n/**\n * CSS del host, en un <style> propio añadido DESPUÉS de los del SDK y de\n * `@magicfeedback/native` para que gane en cascada sin tocar ninguno de los dos.\n * Se reemplaza en cada render por si cambia entre popups.\n */\nfunction ensureCustomCss(css?: string) {\n const STYLE_ID = 'deepdots-custom-css';\n const existing = document.getElementById(STYLE_ID);\n if (!css) {\n existing?.remove();\n return;\n }\n const el = (existing as HTMLStyleElement | null) ?? document.createElement('style');\n el.id = STYLE_ID;\n if (el.textContent !== css) el.textContent = css;\n // Reanexar mueve el nodo al final del head: garantiza que va tras las hojas del SDK.\n document.head.appendChild(el);\n}\n\n// Inyecta (una sola vez) el @font-face de la fuente personalizada.\nfunction ensureFontFace(family: string, url: string) {\n const css = buildFontFaceCss(family, url);\n if (!css) return;\n const STYLE_ID = 'deepdots-font-face';\n let el = document.getElementById(STYLE_ID) as HTMLStyleElement | null;\n if (!el) {\n el = document.createElement('style');\n el.id = STYLE_ID;\n document.head.appendChild(el);\n }\n if (el.textContent !== css) el.textContent = css;\n}\n\n/**\n * Renderiza el popup dentro del contenedor dado usando MagicFeedback para la encuesta.\n */\nexport async function renderPopup(\n container: HTMLElement,\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 options?: PopupRenderOptions,\n): Promise<void> {\n let surveyCompletedEmitted = false;\n let stylesInjected = false;\n // Profundidad de navegación dentro del survey: +1 al avanzar, -1 al volver. Sustituye a\n // `total > 1 && progress > 0 && progress < total`, que escondía el Back cuando la pantalla\n // siguiente era una follow-up dinámica (no entran en el grafo: suman +0.5 al progress y\n // dejan el total igual, así que un survey de una pregunta con follow-up nunca lo mostraba).\n let pageDepth = 0;\n let onStartPage = false;\n\n // Textos del chrome (botones, progreso, aria-labels, errores). Arrancan con el idioma que\n // conoce el init y se re-resuelven en `onLoadedEvent` con el idioma del survey\n // (`formData.lang[0]`, el configurado en la integración), que es el que manda: un survey\n // en danés debe traer también su footer en danés aunque el host no declare idioma.\n let labels = getLabels(options?.language);\n let actionLabels = resolveActionLabels(actions, options?.language);\n // Dirección del chrome. Desde `@magicfeedback/native` 2.2.22 el survey se voltea solo\n // (`dir=\"rtl\"` en SU contenedor) para los idiomas RTL; si el header, el footer y la barra\n // de progreso se quedaran en LTR, media tarjeta miraría a cada lado. Se marca siempre,\n // también en `ltr`, para no heredar la dirección de la página del host.\n let direction = directionFor(options?.language);\n\n const isDark = style?.theme === 'dark';\n const theme = {\n popupBg: isDark ? '#1e1e1e' : '#fff',\n // El popup fija su propio fondo, así que debe fijar también el esquema de color.\n // Sin esto los controles de formulario (input/textarea/select) caen al estilo por\n // defecto del navegador: en un host con `prefers-color-scheme: dark` se pintan\n // oscuros con texto blanco sobre el fondo claro del popup.\n colorScheme: isDark ? 'dark' : 'light',\n textPrimary: isDark ? '#f0f0f0' : '#111',\n closeBtnHoverColor: isDark ? '#fff' : '#000000',\n textMuted: isDark ? '#9ca3af' : '#6b7280',\n progressTrack: isDark ? '#3f3f46' : '#e5e7eb',\n };\n\n const positionMap: Record<string, { justifyContent: string; alignItems: string; padding?: string; background: string }> = {\n 'center': { justifyContent: 'center', alignItems: 'center', 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 const pos = positionMap[style?.position ?? 'center'] ?? positionMap['center'];\n\n // Crear popup base\n const popup = document.createElement('div');\n popup.id = 'dd-popup';\n popup.className = 'deepdots-popup';\n popup.setAttribute('dir', direction);\n popup.style.cssText = `\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n background: ${theme.popupBg};\n color-scheme: ${theme.colorScheme};\n border-radius: 8px;\n padding: 24px;\n box-shadow: 0 4px 6px rgba(0,0,0,0.1);\n max-width: 600px;\n width: 90%;\n max-height: 90vh;\n min-height: 200px;\n `;\n\n const font = style?.font;\n if (font?.family) {\n const familyValue = buildFontFamilyValue(font.family);\n // El survey vive dentro del contenedor y hereda esta fuente.\n popup.style.fontFamily = familyValue;\n // La variable habilita que el <h2> (Montserrat por defecto) también la use.\n popup.style.setProperty('--deepdots-font', familyValue);\n // Los controles de formulario (button/input/textarea/select) NO heredan\n // font-family por defecto; esta clase activa la regla que los fuerza a heredar.\n popup.classList.add('deepdots-has-font');\n if (font.url) ensureFontFace(font.family, font.url);\n }\n\n // Sección header (título + botón cerrar)\n const header = document.createElement('div');\n header.className = 'deepdots-popup-header';\n header.style.cssText = 'display:flex; justify-content:space-between; align-items:center; gap:12px; width:100%; flex:0 0 auto;';\n\n // Título: `textContent`, nunca innerHTML — el valor viene de la API.\n const titleEl = document.createElement('h2');\n titleEl.id = 'dd-title';\n titleEl.className = 'deepdots-popup-title';\n // text-transform/text-align/margin neutralizan la regla `.deepdots-popup h2` del CSS del\n // survey (uppercase, centrado, margin-bottom 40px), pensada para los enunciados.\n titleEl.style.cssText = `margin:0; font-size:17px; font-weight:600; line-height:1.3; color:${theme.textPrimary}; text-transform:none; text-align:start; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;`;\n titleEl.hidden = true;\n const setTitle = (value?: string) => {\n if (!value) return;\n titleEl.textContent = value;\n titleEl.hidden = false;\n };\n setTitle(options?.title);\n header.appendChild(titleEl);\n\n // Botón de cierre (X)\n const closeBtn = document.createElement('button');\n closeBtn.id = 'dd-close';\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', labels.closeAria);\n closeBtn.innerHTML = `\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M5 5L19 19M5 19L19 5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"butt\"/>\n </svg>\n `;\n closeBtn.style.cssText = `\n background:transparent;\n border:none;\n width:32px;\n height:32px;\n display:flex;\n align-items:center;\n justify-content:center;\n border-radius: 8px;\n cursor:pointer;\n color:${theme.textPrimary};\n padding:4px;\n transition: color .15s ease, transform .15s ease;\n box-shadow: none !important;\n `;\n closeBtn.onmouseenter = () => {\n closeBtn.style.color = theme.closeBtnHoverColor;\n closeBtn.style.transform = 'scale(1.06)';\n };\n closeBtn.onmouseleave = () => {\n closeBtn.style.color = theme.textPrimary;\n closeBtn.style.transform = 'scale(1)';\n };\n closeBtn.onclick = () => {\n emit('popup_clicked', surveyId, {action: 'close_icon'});\n onClose();\n };\n closeBtn.style.flex = '0 0 auto';\n closeBtn.style.marginLeft = 'auto';\n header.appendChild(closeBtn);\n\n // Barra de progreso (\"Question X of Y\" + barra), espejo de LineProgressQuestion (MagicSurvey).\n // `undefined` en el init deja decidir a la plataforma (style.showProgressBar).\n let progressEnabled = options?.showProgressBar === true;\n let progressShowUnit = true;\n let progressUnit: 'percentage' | 'fraction' = 'fraction';\n\n const progressEl = document.createElement('div');\n progressEl.id = 'dd-progress';\n progressEl.className = 'deepdots-progress';\n // Sin padding horizontal propio: se alinea con el título y con el contenido. El vertical es\n // el mismo arriba y abajo, y del mismo valor que el padding de la tarjeta.\n progressEl.style.cssText = 'display:none; flex-direction:column; gap:8px; width:100%; flex:0 0 auto; padding:16px 0; box-sizing:border-box;';\n const progressHead = document.createElement('div');\n progressHead.className = 'deepdots-progress-head';\n progressHead.style.cssText = 'display:flex; flex-direction:row; justify-content:space-between; align-items:center; width:100%; gap:8px;';\n const progressLabel = document.createElement('span');\n progressLabel.id = 'dd-progress-label';\n progressLabel.style.cssText = 'font-size:13px; line-height:1.2;';\n // \"Question 1\" en negrita y \"of 3\" en regular gris, como el mockup.\n const progressCurrent = document.createElement('span');\n progressCurrent.id = 'dd-progress-current';\n progressCurrent.style.cssText = `font-weight:700; color:${theme.textPrimary};`;\n const progressTotal = document.createElement('span');\n progressTotal.id = 'dd-progress-total';\n progressTotal.style.cssText = `font-weight:400; color:${theme.textMuted};`;\n progressLabel.appendChild(progressCurrent);\n progressLabel.appendChild(progressTotal);\n const progressFollowUp = document.createElement('span');\n progressFollowUp.id = 'dd-progress-followup';\n progressFollowUp.textContent = labels.followUp;\n progressFollowUp.style.cssText = 'display:none; font-size:12px; font-weight:600; color:#fff; background:rgba(59,130,246,0.44); border-radius:999px; padding:2px 10px;';\n progressHead.appendChild(progressLabel);\n progressHead.appendChild(progressFollowUp);\n const progressTrack = document.createElement('div');\n progressTrack.className = 'deepdots-progress-track';\n progressTrack.style.cssText = `width:100%; height:4px; border-radius:999px; background:${theme.progressTrack}; overflow:hidden;`;\n const progressBar = document.createElement('div');\n progressBar.id = 'dd-progress-bar';\n progressBar.style.cssText = 'height:100%; width:0%; background:#22C55E; border-radius:999px; transition:width 450ms ease;';\n progressTrack.appendChild(progressBar);\n progressEl.appendChild(progressHead);\n progressEl.appendChild(progressTrack);\n\n function updateProgress(p: { progress?: number; total?: number; completed?: boolean; followup?: boolean }) {\n const total = typeof p.total === 'number' ? p.total : 0;\n const progress = typeof p.progress === 'number' ? p.progress : 0;\n if (!progressEnabled || onStartPage || p.completed || total <= 1) {\n progressEl.style.display = 'none';\n // Sin barra, el hueco cabecera→pregunta lo pone el contenido.\n containerContent.style.paddingTop = '16px';\n return;\n }\n progressEl.style.display = 'flex';\n // Con barra, el hueco inferior ya lo pone el bloque de progreso: no duplicarlo.\n containerContent.style.paddingTop = '0';\n // La barra usa el valor real (las follow-up suman +0.5 y avanzan media casilla); la\n // etiqueta redondea hacia abajo, porque una follow-up es un paso DENTRO de la misma\n // pregunta y no la siguiente.\n const current = Math.min(total, Math.max(1, progress + 1));\n const pct = Math.min(100, Math.max(0, (current / total) * 100));\n progressBar.style.width = `${pct}%`;\n const label = Math.min(total, Math.max(1, Math.floor(progress) + 1));\n progressLabel.style.display = progressShowUnit ? 'block' : 'none';\n if (progressUnit === 'percentage') {\n progressCurrent.textContent = `${Math.round(pct)}%`;\n progressTotal.textContent = '';\n } else {\n progressCurrent.textContent = `${labels.question} ${label}`;\n progressTotal.textContent = ` ${labels.of} ${total}`;\n }\n progressFollowUp.style.display = p.followup ? 'inline-block' : 'none';\n }\n\n ensureMagicFeedbackStyles(popup);\n // Después del CSS del paquete y antes del CSS del host, que sigue teniendo la última palabra.\n ensureSurveyAlignmentStyles();\n ensureSpinnerStyles(popup);\n ensureResponsiveStyles(popup);\n // El último en entrar: el CSS del host gana sobre todo lo anterior.\n ensureCustomCss(options?.surveyCss);\n\n const containerContent = document.createElement('div');\n containerContent.id = 'dd-content';\n // Estaba escrito 'conetent'; el HTML del WebView siempre usó la forma correcta. Con\n // `surveyCss` esta clase pasa a ser superficie pública, así que las dos rutas deben coincidir.\n containerContent.className = 'deepdots-popup-container-content';\n // El popup es una columna: solo el bloque de preguntas hace scroll, para que el footer con\n // las acciones quede siempre pegado al borde inferior (antes vivía dentro del área\n // scrollable y en un survey largo había que bajar hasta el final para ver \"Send\").\n // Sin sangrado extra: el contenido se alinea con el título y con la barra de progreso\n // (antes sumaba 20px a los lados sobre el padding de la tarjeta). `overflow:hidden` deja\n // el scroll en `main`, no en el contenedor.\n containerContent.style.cssText = 'display:flex; flex-direction:column; flex:1 1 auto; min-height:0; padding:16px 0 0 0; overflow:hidden;'\n\n // Sección principal (main) - Contenedor formulario + spinner\n const main = document.createElement('div');\n main.id = 'dd-main';\n main.className = 'deepdots-popup-main';\n main.style.cssText = 'display:flex; flex-direction:column; width:100%; flex:1 1 auto; min-height:0; overflow-y:auto;';\n\n const formWrapper = document.createElement('div');\n formWrapper.id = 'dd-form-wrapper';\n formWrapper.style.cssText = 'width:100%; flex: 1 1 auto;';\n\n // Contenedor de aviso de error de validación\n const errorHint = document.createElement('div');\n errorHint.id = 'dd-error';\n errorHint.className = 'deepdots-error-hint';\n errorHint.style.cssText = `\n display: none;\n margin: 12px 0 0 0;\n padding: 10px 12px;\n border-radius: 6px;\n background: #FEF3C7; /* amber-100 */\n color: #92400E; /* amber-700 */\n border: 1px solid #FCD34D; /* amber-300 */\n font-size: 13px;\n `;\n errorHint.setAttribute('role', 'alert');\n errorHint.setAttribute('aria-live', 'polite');\n\n const spinnerEl = document.createElement('div');\n spinnerEl.className = 'mf-spinner';\n spinnerEl.setAttribute('role', 'status');\n spinnerEl.setAttribute('aria-label', labels.loadingAria);\n spinnerEl.innerHTML = '<div class=\"mf-spinner-circle\"></div>';\n spinnerEl.style.cssText = 'position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);';\n\n const formDivId = `magicfeedback-form-${surveyId}`;\n const formHost = document.createElement('div');\n formHost.id = formDivId;\n formHost.style.cssText = 'width:100%; visibility:hidden;';\n\n formWrapper.appendChild(spinnerEl);\n formWrapper.appendChild(formHost);\n main.appendChild(formWrapper);\n // Insertar el hint justo antes del footer\n main.appendChild(errorHint);\n\n // Sección footer (acciones) - botones en extremos\n const footer = document.createElement('div');\n footer.id = 'dd-footer';\n footer.className = 'deepdots-popup-footer';\n footer.setAttribute('data-actions-wrapper', 'true');\n footer.style.cssText = 'display:flex; flex-direction: row-reverse ;justify-content:space-between; align-items:center; gap:4px; margin-top:auto; width:100%; padding-top:12px;';\n\n // Botones\n const backButton = document.createElement('button');\n backButton.id = 'dd-back';\n backButton.className = 'dd-nav-btn';\n backButton.textContent = actionLabels.back;\n // Secundario como botón de texto: sin borde, fondo ni sombra, para que el primario sea\n // la única CTA con peso visual.\n backButton.style.cssText = `\n background: transparent;\n color: ${theme.textMuted};\n border: none;\n min-height: 44px;\n padding: 12px 24px;\n border-radius: 999px; /* píldora, como el Button de Material3 en KMP */\n cursor: pointer;\n font-size: 15px;\n font-weight: 600;\n transition: filter .15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n `;\n backButton.onmouseenter = () => {\n backButton.style.filter = 'brightness(0.9)';\n }\n backButton.onmouseleave = () => {\n backButton.style.filter = 'brightness(1)';\n }\n backButton.onclick = () => {\n emit('popup_clicked', surveyId, {action: 'back'});\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (formInstance as any)?.back?.();\n };\n\n // Boton start survyes, solo aprece cuando la encuesta empieza con mensaje de inicio\n // Width de 100% para que ocupe todo el espacio disponible\n const startButton = document.createElement('button');\n startButton.id = 'dd-start';\n startButton.className = 'dd-nav-btn';\n startButton.textContent = actionLabels.start;\n startButton.style.cssText = `\n background: #1E293B;\n color: #fff;\n border: none;\n min-height: 44px;\n padding: 12px 24px;\n border-radius: 999px; /* píldora, como el Button de Material3 en KMP */\n cursor: pointer;\n font-size: 15px;\n font-weight: 600;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: filter .15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n `;\n startButton.onclick = () => {\n emit('popup_clicked', surveyId, {action: 'start_survey'});\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (formInstance as any)?.startForm?.();\n onStartPage = false;\n updateProgress({progress: formInstance?.progress, total: formInstance?.total});\n };\n\n // Botón cerrar popup, solo aparece al terminar la encuesta\n // Width de 100% para que ocupe todo el espacio disponible\n const closeButton = document.createElement('button');\n closeButton.id = 'dd-complete';\n closeButton.className = 'dd-nav-btn';\n closeButton.textContent = actionLabels.complete;\n closeButton.style.cssText = `\n background: #1E293B;\n color: #fff;\n border: none;\n min-height: 44px;\n padding: 12px 24px;\n border-radius: 999px; /* píldora, como el Button de Material3 en KMP */\n cursor: pointer;\n font-size: 15px;\n font-weight: 600;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: filter .15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n `;\n closeButton.onmouseenter = () => {\n closeButton.style.filter = 'brightness(0.9)';\n }\n closeButton.onmouseleave = () => {\n closeButton.style.filter = 'brightness(1)';\n }\n closeButton.onclick = () => {\n emit('popup_clicked', surveyId, {action: 'complete'});\n onClose();\n };\n\n // Botón send, si es primera pagina ocupara el espacio completo pero si no estara al lado derecho\n const submitButton = document.createElement('button');\n submitButton.id = 'dd-submit';\n submitButton.className = 'dd-nav-btn';\n submitButton.textContent = actionLabels.accept;\n submitButton.style.cssText = `\n background: #1E293B;\n color: #fff;\n border: none;\n min-height: 44px;\n padding: 12px 24px;\n border-radius: 999px; /* píldora, como el Button de Material3 en KMP */\n cursor: pointer;\n font-size: 15px;\n font-weight: 600;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: filter .15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n `;\n submitButton.onclick = () => {\n if (!surveyCompletedEmitted) {\n emit('popup_clicked', surveyId, {action: 'manual_send'});\n // Dispara envío nativo si existe\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (formInstance as any)?.send?.();\n }\n };\n\n /**\n * Re-resuelve todos los textos del chrome para `lang`. Se llama cuando el survey ya cargó\n * y se conoce su idioma. Las etiquetas que configure la plataforma por popup\n * (`actions.*.label`) siguen ganando: `resolveActionLabels` solo rellena las que falten.\n * El contador de progreso no se toca aquí porque `updateProgress` lo repinta justo después\n * leyendo `labels`.\n */\n function applyLabels(lang: string): void {\n labels = getLabels(lang);\n actionLabels = resolveActionLabels(actions, lang);\n backButton.textContent = actionLabels.back;\n startButton.textContent = actionLabels.start;\n closeButton.textContent = actionLabels.complete;\n submitButton.textContent = actionLabels.accept;\n closeBtn.setAttribute('aria-label', labels.closeAria);\n spinnerEl.setAttribute('aria-label', labels.loadingAria);\n progressFollowUp.textContent = labels.followUp;\n // El survey ya se ha volteado a sí mismo: el chrome tiene que acompañarlo.\n direction = directionFor(lang);\n popup.setAttribute('dir', direction);\n }\n\n backButton.style.display = 'none';\n startButton.style.display = 'none';\n submitButton.style.display = 'none';\n closeButton.style.display = 'none';\n\n // Insertar botones dentro del footer en orden visual (row-reverse deja primario a la derecha)\n footer.appendChild(submitButton);\n footer.appendChild(backButton);\n footer.appendChild(closeButton);\n footer.appendChild(startButton);\n\n // Añadir footer al main y main al containerContent\n containerContent.appendChild(main);\n // El footer va fuera del área scrollable, como hermano de `main`.\n footer.style.flex = '0 0 auto';\n containerContent.appendChild(footer);\n\n // Helper para controlar visibilidad de botones según estado\n type ViewState = 'loading' | 'start' | 'in_progress_first' | 'in_progress_next' | 'completed' | 'error';\n\n function updateButtons(state: ViewState) {\n // Por defecto, ocultar todos\n backButton.style.display = 'none';\n startButton.style.display = 'none';\n submitButton.style.display = 'none';\n closeButton.style.display = 'none';\n // Reset widths por estado\n backButton.style.width = '';\n startButton.style.width = '';\n submitButton.style.width = '';\n closeButton.style.width = '';\n\n switch (state) {\n case 'loading':\n // Footer se ocultará desde setLoading\n break;\n case 'start':\n // Solo botón Start a ancho completo\n startButton.style.display = 'inline-flex';\n startButton.style.width = '100%';\n break;\n case 'in_progress_first':\n // Solo botón Send (lado derecho), ancho auto\n submitButton.style.display = 'inline-flex';\n submitButton.style.width = '';\n setLoading(false);\n break;\n case 'in_progress_next':\n // Mostrar Back (izquierda) + Send (derecha)\n backButton.style.display = 'inline-flex';\n submitButton.style.display = 'inline-flex';\n setLoading(false);\n break;\n case 'completed':\n // Mostrar Close/Complete a ancho completo como acción principal\n closeButton.style.display = 'inline-flex';\n closeButton.style.width = '100%';\n setLoading(false);\n break;\n case 'error':\n // En error, permitir cerrar (ancho auto)\n // closeButton.style.display = 'inline-flex';\n setLoading(false);\n break;\n }\n }\n\n /** Estado de navegación según la profundidad recorrida (no según `total`). */\n function updateNavButtons() {\n updateButtons(pageDepth > 0 ? 'in_progress_next' : 'in_progress_first');\n }\n\n /**\n * Pantalla final con el `successMessage` configurado en la plataforma. La pinta el popup y\n * no `@magicfeedback/native`, cuyo `renderSuccess` usa `textContent` (el mensaje es HTML del\n * editor: imagen + texto) y cuyo fallback es un literal que ignora el estilo del survey.\n * Va por innerHTML igual que `renderStartMessage` con el mensaje de inicio.\n */\n let successMessageHtml = '';\n function showSuccessScreen() {\n progressEl.style.display = 'none';\n formWrapper.style.display = 'none';\n let done = main.querySelector('.deepdots-success') as HTMLElement | null;\n if (!done) {\n done = document.createElement('div');\n done.className = 'deepdots-success';\n done.style.cssText = `width:100%; text-align:center; padding:24px 0; color:${theme.textPrimary};`;\n main.insertBefore(done, errorHint);\n }\n done.innerHTML = successMessageHtml || '<p>Thank you for your feedback!</p>';\n done.querySelectorAll('img').forEach((img) => {\n img.style.cssText = 'max-width:100%; height:auto; margin:0 auto 16px auto; display:block;';\n });\n }\n\n // Ensamblar popup\n popup.appendChild(header);\n popup.appendChild(progressEl);\n popup.appendChild(containerContent);\n\n container.innerHTML = '';\n container.appendChild(popup);\n container.style.display = 'flex';\n // Apertura diferida: el contenedor entra en el layout (el survey necesita medidas reales\n // para pintarse) pero invisible y sin capturar clics, hasta que `reveal()` lo enseñe.\n container.style.visibility = 'hidden';\n container.style.pointerEvents = 'none';\n container.style.justifyContent = pos.justifyContent;\n container.style.alignItems = pos.alignItems;\n container.style.background = pos.background;\n if (pos.padding) container.style.padding = pos.padding;\n\n // ── Apertura diferida ────────────────────────────────────────────────────────────────\n // El spinner de apertura es lo que el cliente reporta como mala sensación: medido, está\n // ~310 ms en pantalla esperando el JSON del survey. En vez de enseñar la espera, se espera\n // en silencio y se abre el popup ya montado.\n let revealed = false;\n let revealTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reveal() {\n if (revealed) return;\n revealed = true;\n if (revealTimer !== null) {\n clearTimeout(revealTimer);\n revealTimer = null;\n }\n // Si el popup se cerró durante la espera, este contenedor ya no es suyo: `hide()` lo ha\n // vaciado y puede estar sirviendo a otra apertura. Revelarlo aquí la sacaría antes de\n // tiempo.\n if (!container.contains(popup)) return;\n container.style.visibility = 'visible';\n container.style.pointerEvents = '';\n }\n\n /**\n * Revela en cuanto el popup está pintado del todo. Se mira el popup entero, no solo el\n * formulario, porque el logo del survey se inserta sobre la barra de progreso y es el que más\n * desplaza el contenido al entrar.\n */\n function revealWhenPainted() {\n if (revealed) return;\n revealWhenImagesLoaded(popup, reveal);\n }\n\n revealTimer = setTimeout(reveal, REVEAL_TIMEOUT_MS);\n\n // Gestión dinámica de loading\n function setLoading(isLoading: boolean) {\n spinnerEl.style.display = isLoading ? 'flex' : 'none';\n // El spinner no tapa el survey (28px en posición absoluta), así que sin esto el usuario\n // puede seguir cambiando de opción mientras se envía la página — y ese cambio ya no\n // viaja con ella. Se bloquea el wrapper, que es el elemento estable: el div de dentro lo\n // sustituye `@magicfeedback/native` por su propio contenedor al generar el survey.\n setSurveyBusy(formWrapper, isLoading);\n if (!isLoading) {\n formHost.style.visibility = 'visible';\n // Fin de la carga, sea por survey cargado o por error: el popup ya puede verse.\n // Entre páginas del survey es no-op, porque `reveal` solo actúa la primera vez.\n revealWhenPainted();\n }\n // Ocultar totalmente los botones cuando está cargando\n footer.style.display = isLoading ? 'none' : 'flex';\n // Ajustar estados de los botones por si se muestran\n backButton.disabled = isLoading;\n startButton.disabled = isLoading;\n closeButton.disabled = isLoading;\n submitButton.disabled = isLoading;\n submitButton.style.opacity = isLoading ? '0.6' : '1';\n submitButton.style.cursor = isLoading ? 'not-allowed' : 'pointer';\n // No sobrescribir el estado de botones al finalizar la carga.\n if (isLoading) {\n updateButtons('loading');\n }\n }\n\n // Estado inicial\n setLoading(true);\n\n // Entorno navegador\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n // Referencia instancia formulario\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let formInstance: any = null;\n\n try {\n if (!magicfeedback || typeof magicfeedback.form !== 'function') {\n sdkWarn('[MagicFeedback] form() no disponible. Fallback manual.');\n setLoading(false);\n return;\n }\n magicfeedback.init({\n debug: true,\n env: env === 'production' ? 'prod' : 'dev'}\n );\n // Contrato Fase 1 §5: profile = identidad del usuario; metadata = contexto de tracking (session_id + user_id)\n const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);\n formInstance = magicfeedback.form(surveyId, productId, profile, metadata);\n\n interface TypedGenerateOptions {\n addButton: boolean;\n getMetaData: boolean;\n addSuccessScreen: boolean;\n onLoadedEvent?: (args: {\n formData: FormData,\n progress?: number, total?: number\n }) => void;\n beforeSubmitEvent?: () => void;\n afterSubmitEvent?: (args: { error?: string, completed: boolean, progress: number, total: number, followup?: boolean }) => void;\n onBackEvent?: (args: { error?: string, progress: number, total: number, followup: boolean }) => void;\n }\n\n const generateOptions: TypedGenerateOptions = {\n addButton: false,\n getMetaData: true,\n // La pantalla final la pinta el popup: `renderSuccess` de @magicfeedback/native usa\n // textContent, así que el mensaje de la plataforma (HTML con imagen) no se vería, y\n // su fallback es un literal genérico que ignora `style.successMessage`.\n addSuccessScreen: false,\n };\n generateOptions.onLoadedEvent = ({formData}) => {\n // Idioma del survey: el que la plataforma configura en la integración y con el que\n // `@magicfeedback/native` localiza preguntas y placeholders. Manda sobre el del init\n // para que el chrome no se quede en inglés delante de un survey traducido.\n const surveyLang = Array.isArray(formData?.lang)\n ? formData.lang.find((l) => typeof l === 'string' && l.trim().length > 0)\n : undefined;\n if (surveyLang) applyLabels(surveyLang);\n // Calcular altura disponible y aplicarla al main (restando header + footer + paddings)\n try {\n /*\n const headerHeight = header.getBoundingClientRect().height;\n const footerHeight = footer.getBoundingClientRect().height;\n const paddingY = 48; // 24px top + 24px bottom\n const viewportLimit = window.innerHeight * 0.8; // coincide con max-height del popup\n const available = viewportLimit - headerHeight - footerHeight - paddingY;\n if (available > 120) { // asegurar un mínimo razonable\n main.style.maxHeight = available + 'px';\n } */\n } catch {\n // silencioso\n }\n // Personalización del popup basada en formData.style\n const s = formData?.style;\n if (s && !stylesInjected) {\n stylesInjected = true;\n if (s.successMessage) successMessageHtml = s.successMessage;\n // La barra de progreso la decide el host (init) y, si no se pronuncia, la plataforma.\n if (options?.showProgressBar === undefined) progressEnabled = s.showProgressBar === true;\n if (s.showProgressUnit !== undefined) progressShowUnit = s.showProgressUnit !== false;\n if (s.progressUnit) progressUnit = s.progressUnit;\n if (s.loadingBarColor) {\n progressBar.style.background = s.loadingBarColor;\n progressFollowUp.style.background = s.loadingBarColor;\n }\n // Fondo del contenedor popup\n if (s.boxBackgroundColor) {\n popup.style.background = s.boxBackgroundColor;\n }\n // Alineación del contenido principal\n if (s.contentAlign) {\n // 'top' => start, 'center' => center\n main.style.justifyContent = s.contentAlign === 'center' ? 'center' : 'flex-start';\n }\n // Botón primario (submit, start)\n // El survey pinta sus controles con `--mf-primary`, que sale de `primaryColor`;\n // si la integración solo configura el del botón, el popup mezclaría el color de\n // la marca con el gris azulado por defecto del paquete.\n applySurveyPrimaryColor(popup, s);\n if (s.buttonPrimaryColor) {\n submitButton.style.background = s.buttonPrimaryColor;\n submitButton.style.border = 'none';\n submitButton.style.color = '#fff';\n\n startButton.style.background = s.buttonPrimaryColor;\n startButton.style.border = 'none';\n startButton.style.color = '#fff';\n\n closeButton.style.background = s.buttonPrimaryColor;\n closeButton.style.border = 'none';\n closeButton.style.color = '#fff';\n }\n // Botón secundario (back) — outlined: fondo blanco, letra y borde del color secundario\n if (s.buttonSecondaryColor) {\n // Botón de texto: solo tiñe la etiqueta, sin recuperar fondo ni borde.\n backButton.style.background = 'transparent';\n backButton.style.color = s.buttonSecondaryColor;\n backButton.style.border = 'none';\n }\n // El logo va encima de la barra de progreso, como hermano del header.\n insertPopupLogo(popup, progressEl, s, 'deepdots-popup-logo');\n\n if (s.startMessage && s.startMessage !== '') {\n // Si hay mensaje de inicio, mostrar botón Start inicialmente\n sdkLog(s.startMessage);\n onStartPage = true;\n updateButtons('start');\n } else {\n // Si no hay mensaje de inicio, mostrar estado de primera página (solo Send)\n updateButtons('in_progress_first');\n }\n } else {\n // Sin estilos, asumir primera página normal\n updateButtons('in_progress_first');\n }\n\n // El total solo se conoce con el form ya montado.\n updateProgress({progress: formInstance.progress, total: formInstance.total});\n emit('popup_clicked', surveyId, {action: 'loaded'});\n setLoading(false); // hace visible el formulario y oculta el spinner\n };\n generateOptions.beforeSubmitEvent = () => {\n setLoading(true);\n emit('popup_clicked', surveyId, {action: 'before_submit'});\n };\n generateOptions.afterSubmitEvent = ({error, completed, total, progress, followup}) => {\n // No cambiar estado de loading aquí; lo gestiona cada transición\n // Normalizar el error a texto seguro\n const errText = error ? (typeof error === 'string' ? error : ((error as unknown as {message?: string}).message ?? String(error))) : '';\n if (errText) {\n // Desactivar loading para que se vean los botones\n setLoading(false);\n // Caso específico: error de pregunta obligatoria\n if (errText.toLowerCase().includes('no response')) {\n errorHint.textContent = labels.errorRequired;\n errorHint.style.display = 'block';\n emit('popup_clicked', surveyId, {action: 'validation_error_required'});\n // La página no ha cambiado: el estado de navegación se queda como estaba.\n updateNavButtons();\n return;\n }\n // Otros errores: mostrar mensaje genérico y permitir cerrar\n errorHint.textContent = labels.errorSubmit;\n errorHint.style.display = 'block';\n emit('popup_clicked', surveyId, {action: 'submit_error', error: errText});\n // updateButtons('error');\n return;\n }\n // Limpiar hint si no hay error\n errorHint.style.display = 'none';\n setLoading(false);\n if (completed) {\n emit('survey_completed', surveyId, {action: 'completed'});\n surveyCompletedEmitted = true;\n showSuccessScreen();\n updateButtons('completed');\n updateProgress({progress, total, completed: true});\n return;\n }\n emit('popup_clicked', surveyId, {action: 'partial'});\n pageDepth++;\n updateNavButtons();\n updateProgress({progress, total, followup});\n };\n generateOptions.onBackEvent = ({progress, error, followup}) => {\n emit('popup_clicked', surveyId, {action: 'back'});\n // Con error (\"No page found\") no hubo navegación: la profundidad no se toca.\n if (!error) pageDepth = Math.max(0, pageDepth - 1);\n updateNavButtons();\n // onBackEvent no trae `total`: se lee del form, que es su dueño.\n updateProgress({progress, total: formInstance.total, followup});\n };\n\n // Ejecutar generación con opciones tipadas\n formInstance.generate(formDivId, generateOptions)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .catch((err: any) => {\n sdkError('[MagicFeedback] Error generating form:', err);\n setLoading(false);\n });\n } catch (e) {\n sdkError('[MagicFeedback] Exception initializing form:', e);\n setLoading(false);\n }\n}\n","/**\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 // El Modal se abre en cuanto hay popup, y es el HTML del WebView el que se revela solo\n // cuando el survey está pintado (apertura diferida): con el Modal transparente, hasta ese\n // momento no se ve nada, ni velo ni spinner. No se difiere el Modal con `onReady` porque\n // React Native no monta los hijos de un Modal cerrado: el WebView no llegaría a cargar y\n // el aviso no llegaría nunca. El host que quiera diferir su contenedor debe montar el\n // WebView invisible (ver el ejemplo de `ReactNativePopupRenderer`).\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\n/**\n * A session of the analytics channel as the Deepdots API sees it. Every session becomes ONE\n * feedback, and `sessionId` is what the API stores as `sdkSessionId` on it: the value to look\n * that feedback up with (`GET /feedbacks?filter={\"where\":{\"sdkSessionId\":\"<sessionId>\"}}`).\n * A session that closes again keeps that one feedback: the API updates it.\n * - `open`: the API acknowledged the session's first batch; its events keep accumulating.\n * - `closed`: the API acknowledged the session's closing batch (`completed: true`); the\n * feedback is created from it within seconds.\n */\nexport interface FeedbackSession {\n sessionId: string;\n status: 'open' | 'closed';\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: nothing is sent, and the payload is only\n * logged when `debug` is true.\n */\n analytics?: AnalyticsKeys;\n /**\n * Called when the analytics channel opens or closes a session (needs `analytics`), with the\n * id the Deepdots API stores as `sdkSessionId` on the feedback the session becomes: send it\n * to your backend to attach data to that feedback later. A session whose only batch is the\n * closing one reports just `closed`. `closed` does not come when the page unloads before the\n * closing request returns, for a session dropped after 30 min hidden, nor when the closing\n * request fails (the API closes those itself, later): their id already came with `open`. Key\n * by `sessionId`, not by order: the next session's `open` can arrive before the previous\n * one's `closed`. Same value as `getFeedbackSessionId()` while the session is open.\n */\n onFeedbackSession?: (session: FeedbackSession) => void;\n /**\n * IP geolocation (country/city) added to analytics. Default `true`. The lookup calls\n * third-party services (ipapi.co, then ipwho.is, then ipinfo.io as fallbacks, 3 s timeout\n * each) and only runs when `analytics` is configured, tracking is enabled and the 30-day\n * cache is missing or expired. Set `false` to never call them nor attach country/city.\n */\n geolocation?: boolean;\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 * Whether the SDK renders the popup \"modal\" (card + backdrop + positioning) around the survey.\n * Default `true`. Only affects React Native (the survey WebView HTML); the web DOM popup is\n * unaffected. With `false` the survey is delivered transparent and edge-to-edge so the host\n * mounts its own Modal/container and controls the styling — avoids the \"double modal\" when the\n * host already wraps the WebView. The survey stays fully functional (bridge, form, nav buttons,\n * close). Ver `ReactNativePopupRenderer`.\n */\n renderChrome?: boolean;\n /**\n * Barra de progreso (\"Question X of Y\" + barra) en la cabecera del popup. Si se omite,\n * manda el `showProgressBar` que la plataforma configura en el estilo del survey; `true` o\n * `false` lo fuerzan desde el host. Solo se pinta con más de una página, fuera de la\n * pantalla de inicio y antes de completar, igual que en la web de surveys.\n */\n showProgressBar?: boolean;\n /**\n * CSS propio del host, inyectado como último bloque de estilos del popup: gana en cascada\n * sobre el del SDK y sobre el de `@magicfeedback/native` sin tener que tocar ninguno. Es la\n * vía para reestilar el área de preguntas (enunciados, opciones, escalas) por integración,\n * en vez de cambiar los defaults que comparten todos los clientes.\n */\n surveyCss?: string;\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 * - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta (web). La sesión\n * anterior se cierra y se abre otra, como hace GA con su timeout de sesión.\n */\nexport type SessionEndReason =\n | 'page_hide'\n | 'background'\n | 'user_change'\n | 'tracking_disabled'\n | 'manual'\n | 'idle_timeout';\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 /**\n * App routes where it must NOT be shown. Wins over `path`: a route listed here\n * blocks the popup even if `path` matches it (so `path: ['/']` +\n * `excludedPaths: ['/cart']` = everywhere but the cart).\n * Same matching rules as `path`: an absolute URL matches the full href, an entry\n * starting with `/` matches any href containing it (this is what covers hash\n * routes like `/#/cart`), anything else matches the pathname exactly.\n */\n excludedPaths?: string[];\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 /**\n * Idiomas configurados en la integración del survey. `@magicfeedback/native` localiza\n * preguntas y placeholders con el primero, y el popup traduce su chrome con el mismo.\n */\n lang?: 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\n/** `TriggerConfig.value` también acepta string (id/nombre para click/event); para time/scroll se necesita un número. */\nfunction toNumericValue(value: number | string | undefined, fallback: number): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : fallback;\n}\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 = toNumericValue(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 = toNumericValue(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 {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 { DeepdotsEventType, PopupActions, PopupStyle } from '../types';\nimport type { PopupRenderer, PopupRenderOptions } from './renderer';\nimport { buildSurveyIdentity } from '../tracking/tracking-manager';\nimport { buildSurveyHtml } from '../ui/surveyHtml';\nimport { REVEAL_TIMEOUT_MS } from '../ui/reveal';\nimport { sdkWarn } from '../util/logger';\n\n/**\n * Margen que el aviso nativo de \"listo\" deja por detrás del techo que ya lleva el HTML. El\n * WebView se revela solo a `REVEAL_TIMEOUT_MS`; este respaldo solo debe entrar cuando el WebView\n * no llega ni a ejecutar el HTML (motor que no arranca, documento que no carga), que es el único\n * caso en el que el host se quedaría esperando para siempre.\n */\nexport const NATIVE_READY_GRACE_MS = 500;\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 * Se llama cuando el survey ya está pintado dentro del WebView, o pasado el techo de espera.\n *\n * Sirve para no enseñar un WebView en blanco: monta el WebView en cuanto llegue `onShow` (fuera\n * de pantalla o transparente) y ábrelo al recibir `onReady`. El HTML se revela solo de todas\n * formas, así que el host que no lo use sigue funcionando igual.\n */\n onReady?: (payload: { surveyId: string }) => 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: monta el WebView con `onShow` y enséñalo con `onReady`, para que el usuario no vea el\n * WebView arrancando en blanco (motor + bundle del CDN + fetch del survey).\n * ```tsx\n * const renderer = new ReactNativePopupRenderer({\n * onShow: (p) => setSurvey({ ...p, ready: false }),\n * onReady: () => setSurvey((s) => (s ? { ...s, ready: true } : s)),\n * onHide: () => setSurvey(null),\n * });\n * sdk.setRenderer(renderer);\n * // …\n * // ⚠️ Con `<Modal visible={survey.ready}>` esto NO funciona: React Native no monta los hijos\n * // de un Modal cerrado, así que el WebView no empezaría a cargar y `ready` no llegaría nunca.\n * // El WebView tiene que estar montado y solo invisible.\n * {survey ? (\n * <View style={[StyleSheet.absoluteFill, { opacity: survey.ready ? 1 : 0 }]}\n * pointerEvents={survey.ready ? 'auto' : 'none'}>\n * <WebView source={{ html: survey.html }}\n * onMessage={(e) => renderer.handleMessage(e.nativeEvent.data)} />\n * </View>\n * ) : null}\n * ```\n *\n * Quien prefiera no complicarse puede ignorar `onReady` y abrir su Modal en `onShow`: el HTML se\n * revela solo igualmente, así que el usuario verá el contenedor del host unos cientos de ms antes\n * que la tarjeta, pero nunca el spinner.\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 private readyEmitted = false;\n private readyTimer: ReturnType<typeof setTimeout> | null = null;\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 renderChrome?: boolean,\n options?: PopupRenderOptions,\n ): void {\n this.emitFn = emit;\n this.onCloseFn = onClose;\n this.currentSurveyId = surveyId;\n this.partialEmitted = false;\n this.readyEmitted = false;\n this.armReadyFallback(surveyId);\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 // renderChrome:false (init) → HTML sin tarjeta/overlay: el host lo envuelve en su Modal.\n chrome: renderChrome,\n title: options?.title,\n showProgressBar: options?.showProgressBar,\n surveyCss: options?.surveyCss,\n language: options?.language,\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.clearReadyFallback();\n this.options.onHide?.();\n this.currentSurveyId = null;\n }\n\n /** Respaldo del aviso de listo por si el WebView no llega a ejecutar el HTML. */\n private armReadyFallback(surveyId: string): void {\n this.clearReadyFallback();\n if (!this.options.onReady) return;\n this.readyTimer = setTimeout(() => {\n this.readyTimer = null;\n this.emitReady(surveyId);\n }, REVEAL_TIMEOUT_MS + NATIVE_READY_GRACE_MS);\n }\n\n private clearReadyFallback(): void {\n if (this.readyTimer !== null) {\n clearTimeout(this.readyTimer);\n this.readyTimer = null;\n }\n }\n\n /** Idempotente: el WebView puede avisar a la vez que vence el respaldo. */\n private emitReady(surveyId: string): void {\n if (this.readyEmitted) return;\n this.readyEmitted = true;\n this.clearReadyFallback();\n this.options.onReady?.({ surveyId });\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 'ready':\n // El survey ya está pintado dentro del WebView. NO es interacción del usuario: no debe\n // marcar el popup como PARTIAL.\n this.emitReady(surveyId);\n break;\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 // Solo reporta el estado COMPLETED. NO cierra: el WebView acaba de pintar la pantalla\n // final del survey y cerrar aquí la hacía invisible. El cierre llega después con\n // `popup_close`, cuando el usuario pulsa el botón de completar.\n this.emitFn('survey_completed', surveyId);\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 type { IdentityAnswer } from '../tracking/tracking-manager';\nimport { buildFontFaceCss, buildFontFamilyValue } from './font';\nimport type { PopupActions, PopupFont } from '../types';\nimport { LABELS, RESOLVE_LOCALE_JS, RTL_LOCALES, directionFor, getLabels, resolveActionLabels } from '../i18n/labels';\nimport { REVEAL_TIMEOUT_MS } from './reveal';\nimport { PRIMARY_COLOR_JS } from './surveyPalette';\nimport { BUSY_JS } from './busy';\nimport magicfeedbackCss from '../assets/style.css';\n\n/**\n * Apertura diferida, versión que corre DENTRO del WebView: espejo ES5 de `revealWhenPainted`\n * (`reveal.ts`). El popup se monta invisible y se enseña cuando el survey está pintado y sus\n * imágenes han llegado, en vez de enseñar el spinner girando; `timeoutMs` es el techo para lo\n * que no llegue nunca. En RN esto pesa más que en web, porque a la espera del survey se le suma\n * el arranque del WebView y la descarga del bundle del CDN.\n *\n * Va como texto (y no como import) porque el WebView no comparte módulos con el bundle del SDK.\n * `surveyHtml.reveal.test.ts` lo evalúa para probar el comportamiento, no solo el string.\n */\nexport const REVEAL_JS = `\nfunction ddCreateReveal(popup, onReveal, timeoutMs){\n var revealed=false, timer=null;\n function reveal(){\n if(revealed){ return; }\n revealed=true;\n if(timer){ clearTimeout(timer); timer=null; }\n onReveal();\n }\n function revealWhenPainted(){\n if(revealed){ return; }\n var imgs=popup.querySelectorAll('img'), pending=[];\n for(var i=0;i<imgs.length;i++){ if(!imgs[i].complete){ pending.push(imgs[i]); } }\n if(!pending.length){ reveal(); return; }\n var left=pending.length;\n function onSettled(){ left--; if(left<=0){ reveal(); } }\n for(var j=0;j<pending.length;j++){\n pending[j].addEventListener('load', onSettled);\n pending[j].addEventListener('error', onSettled);\n }\n }\n timer=setTimeout(reveal, timeoutMs);\n return { reveal: reveal, whenPainted: revealWhenPainted };\n}`;\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 * Renderiza el \"modal\" del popup (tarjeta con fondo/sombra/bordes + overlay + posicionamiento).\n * Default `true`. Con `false` el HTML es transparente y llena el contenedor: el host monta su\n * propio Modal/tarjeta y controla el estilo. El survey sigue siendo funcional (bridge, form,\n * botones y botón de cerrar). Pensado para React Native cuando el host gestiona el contenedor.\n */\n chrome?: boolean;\n /**\n * Título de la cabecera, a la izquierda del botón de cerrar. Es el `title` de la definición\n * del popup (`GET /sdk/{publicKey}/popups`), así que cambia de un popup a otro. Vacío es un\n * valor legítimo: la cabecera se queda solo con la X. NO cae al título del survey.\n */\n title?: string;\n /**\n * Barra de progreso (\"Question X of Y\" + barra) bajo la cabecera. `undefined` respeta el\n * `showProgressBar` que configure la plataforma en el estilo del survey; `true`/`false` lo\n * fuerzan desde el host. Solo se pinta con más de una página y fuera de la pantalla de inicio.\n */\n showProgressBar?: boolean;\n /**\n * CSS del host, inyectado como último bloque de estilos: gana en cascada sobre el del SDK\n * y sobre el de `@magicfeedback/native` sin tener que tocar ninguno de los dos. Es la vía\n * para reestilar el área de preguntas (enunciados, opciones, escalas) por integración.\n */\n surveyCss?: string;\n /**\n * Idioma con el que arranca el chrome (botones, progreso, aria-labels, errores). Es el\n * idioma resuelto en `init()`. Dentro del WebView lo pisa el idioma del survey\n * (`formData.lang[0]`), y ambos los pisa `actions.*.label` de la API.\n */\n language?: string;\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\n/**\n * Serializa un valor para incrustarlo dentro de un `<script>`. `JSON.stringify` no escapa `<`,\n * así que un `</script>` dentro de un texto de la API (título, etiqueta de botón, metadata)\n * cerraría el bloque y el resto se interpretaría como HTML. Escapa también los separadores de\n * línea U+2028/U+2029, que son JSON válido pero rompen un literal de JavaScript.\n */\nfunction jsonForScript(value: unknown): string {\n return JSON.stringify(value ?? null)\n .replace(/</g, '\\\\u003C')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\n/** Devuelve el label de la API solo si trae contenido; en blanco cuenta como \"no configurado\". */\nfunction trimmedOrNull(value: string | undefined): string | null {\n const trimmed = typeof value === 'string' ? value.trim() : '';\n return trimmed ? value as string : null;\n}\n\nexport function buildSurveyHtml(opts: BuildSurveyHtmlOptions): string {\n const env = opts.env === 'development' ? 'dev' : 'prod';\n // Misma versión que la dep npm del bundle web, para que las dos rutas pinten igual.\n const version = opts.version ?? '2.2.22';\n const cdn = `https://cdn.jsdelivr.net/npm/@magicfeedback/native@${version}/dist/magicfeedback-sdk.browser.js`;\n const sid = jsonForScript(opts.surveyId);\n const pid = jsonForScript(opts.productId);\n const profileJson = jsonForScript(opts.profile ?? []);\n const metaJson = jsonForScript(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 textMuted = isDark ? '#9ca3af' : '#6b7280';\n const trackBg = isDark ? '#3f3f46' : '#e5e7eb';\n const pos = POSITION_MAP[opts.position ?? 'center'] ?? POSITION_MAP.center;\n\n // chrome:false → el host envuelve el survey en su propio Modal/tarjeta. El HTML se vuelve\n // transparente y llena el contenedor (sin fondo/sombra/borde/overlay/posicionamiento propios);\n // así se evita el \"doble modal\" cuando el host ya monta el suyo. Survey funcional intacto.\n const chrome = opts.chrome !== false;\n const bodyBg = chrome ? pos.background : 'transparent';\n const bodyPadding = chrome ? pos.padding : '0';\n const bodyDisplay = chrome ? 'flex' : 'block';\n const cardBg = chrome ? popupBg : 'transparent';\n const cardShadow = chrome ? '0 4px 6px rgba(0,0,0,0.1)' : 'none';\n const cardRadius = chrome ? '8px' : '0';\n const cardMaxWidth = chrome ? '600px' : 'none';\n const cardWidth = chrome ? '90%' : '100%';\n const cardMinHeight = chrome ? '200px' : '0';\n // Con chrome el alto lo marca el contenido (hasta 90vh); sin chrome llena el contenedor del\n // host. En ambos casos hace falta un alto acotado para que el footer pueda quedar fijo abajo.\n const cardHeight = chrome ? 'auto' : '100%';\n const cardMaxHeight = chrome ? '90vh' : '100%';\n // En móvil la tarjeta se estrecha y se separa de los bordes de la ventana... salvo con\n // chrome:false, donde el contenedor es del host: ahí llena el ancho y NO añade los insets\n // de safe-area (el host ya los aplica; sumarlos otra vez dejaba un hueco muerto bajo el footer).\n const mobileCard = chrome\n ? `.deepdots-popup{width:calc(100% - 24px);max-width:calc(100% - 24px);border-radius:12px;padding:calc(16px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom)) 16px}`\n : `.deepdots-popup{width:100%;max-width:100%;border-radius:0;padding:16px}`;\n\n // El título viaja como literal JSON y se aplica con textContent: viene de la API y no debe\n // interpolarse en el HTML (mismo criterio anti-inyección que `font.family`).\n const titleJson = jsonForScript(opts.title ?? '');\n // CSS del host: va el último para ganar en cascada. Se corta en `</style>` porque el bloque\n // se cierra ahí y todo lo que siguiera pasaría a interpretarse como HTML.\n const customCss = (opts.surveyCss ?? '').replace(/<\\/\\s*style/gi, '<\\\\/style');\n // `null` = decide la plataforma (style.showProgressBar); true/false = lo fuerza el host.\n const progressPref = opts.showProgressBar === undefined ? 'null' : String(opts.showProgressBar);\n\n // Textos del chrome. Arrancan con el idioma del init; dentro del WebView se re-resuelven con\n // el idioma del survey (`formData.lang[0]`), que es el que la plataforma configura en la\n // integración y con el que `@magicfeedback/native` localiza preguntas y placeholders.\n const initLabels = getLabels(opts.language);\n const initActionLabels = resolveActionLabels(opts.actions, opts.language);\n const backLabel = jsonForScript(initActionLabels.back);\n const startLabel = jsonForScript(initActionLabels.start);\n const completeLabel = jsonForScript(initActionLabels.complete);\n const submitLabel = jsonForScript(initActionLabels.accept);\n // La tabla entera viaja con el HTML: el idioma del survey no se conoce hasta que carga.\n const labelsTableJson = jsonForScript(LABELS);\n // Los labels de la API van aparte para que sigan ganando después de re-resolver.\n const actionOverridesJson = jsonForScript({\n accept: trimmedOrNull(opts.actions?.accept?.label),\n start: trimmedOrNull(opts.actions?.start?.label),\n complete: trimmedOrNull(opts.actions?.complete?.label),\n back: trimmedOrNull(opts.actions?.back?.label),\n });\n const initLangJson = jsonForScript(opts.language ?? '');\n // Dirección del chrome. El survey se voltea solo desde 2.2.22 (`dir=\"rtl\"` en su contenedor);\n // el header/footer/progreso son nuestros y tienen que acompañarlo. Siempre explícito.\n const initDirection = directionFor(opts.language);\n const rtlLocalesJson = jsonForScript(RTL_LOCALES);\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}}\n/* Apertura diferida: ni la tarjeta ni el velo se pintan hasta que el survey está listo\n (\\`dd-ready\\`). Si el fondo entrara antes, en RN se vería el velo oscuro varios cientos de ms\n antes que el contenido, que es la misma mala sensación que el spinner. */\nbody{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:transparent;padding:${bodyPadding};box-sizing:border-box}\nbody.dd-ready{background:${bodyBg}}\n#dd-popup{visibility:hidden}\nbody.dd-ready #dd-popup{visibility:visible}\n.deepdots-popup{position:relative;display:flex;flex-direction:column;justify-content:flex-start;background:${cardBg};color-scheme:${colorScheme};border-radius:${cardRadius};padding:24px;box-shadow:${cardShadow};max-width:${cardMaxWidth};width:${cardWidth};min-height:${cardMinHeight};height:${cardHeight};max-height:${cardMaxHeight};box-sizing:border-box}\n.deepdots-popup-header{display:flex;justify-content:space-between;align-items:center;gap:12px;width:100%;flex:0 0 auto}\n/* Neutraliza la regla \\`.deepdots-popup h2\\` del CSS del survey (uppercase, centrado y\n margin-bottom 40px), pensada para los enunciados y no para el título de la cabecera. */\n#dd-title{margin:0;font-family:var(--deepdots-font,'Montserrat',inherit);font-size:17px;font-weight:600;line-height:1.3;color:${textPrimary};text-transform:none;text-align:start;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\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;flex:0 0 auto;margin-inline-start:auto}\n/* Progreso alineado con la cabecera: sin padding horizontal propio, lo marca la tarjeta.\n El vertical es el mismo arriba y abajo, y del mismo valor que el padding de la tarjeta. */\n.deepdots-progress{display:none;flex-direction:column;gap:8px;width:100%;flex:0 0 auto;padding:16px 0;box-sizing:border-box}\n.deepdots-progress-head{display:flex;flex-direction:row;justify-content:space-between;align-items:center;width:100%;gap:8px}\n#dd-progress-label{font-size:13px;line-height:1.2}\n/* \"Question 1\" en negrita y \"of 3\" en regular gris, como el mockup. */\n#dd-progress-current{font-weight:700;color:${textPrimary}}\n#dd-progress-total{font-weight:400;color:${textMuted}}\n#dd-progress-followup{display:none;font-size:12px;font-weight:600;color:#fff;background:rgba(59,130,246,0.44);border-radius:999px;padding:2px 10px}\n.deepdots-progress-track{width:100%;height:4px;border-radius:999px;background:${trackBg};overflow:hidden}\n#dd-progress-bar{height:100%;width:0%;background:#22C55E;border-radius:999px;transition:width 450ms ease}\n/* El popup es una columna: solo el bloque de preguntas hace scroll, para que el footer\n con las acciones quede siempre visible en el borde inferior (antes vivía dentro del\n área scrollable y había que bajar hasta el final para ver \"Send\"). */\n/* Sin sangrado extra: el contenido se alinea con el título y con la barra de progreso (antes\n sumaba 20px a los lados sobre el padding de la tarjeta y quedaba más adentro que la cabecera). */\n/* Sin barra de progreso, este padding-top es el que separa la cabecera de la pregunta. Con\n barra, el JS lo pone a 0 y el hueco lo aporta el padding inferior del bloque de progreso,\n de modo que el espacio por arriba y por abajo de la barra es idéntico. */\n.deepdots-popup-container-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:16px 0 0 0;overflow:hidden;box-sizing:border-box}\n/* El enunciado trae margin-top:20px del CSS de surveys, que sumaba al hueco de la barra y\n descompensaba la simetría. Solo se anula en el primero: entre preguntas sigue separando. */\n.deepdots-popup .magicfeedback-questions .magicfeedback-div:first-child .magicfeedback-label{margin-top:0}\n.deepdots-popup-main{display:flex;flex-direction:column;width:100%;flex:1 1 auto;min-height:0;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/* El margen lateral lo pone la tarjeta: el CSS del survey lo añade otra vez en el contenedor, en\n el formulario y en el bloque de cada pregunta, y eso deja el enunciado y las opciones más\n adentro que el logo, el título y la barra de progreso. Solo el lateral; el vertical separa las\n preguntas. Mismas reglas que en el popup DOM (renderPopup.ts). */\n.deepdots-popup .magicfeedback-container,\n.deepdots-popup .magicfeedback-form,\n.deepdots-popup .magicfeedback-div{padding-left:0;padding-right:0}\n#dd-logo{max-height:40px;max-width:100%;object-fit:contain;display:block;margin:12px 0 0 0}\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/* Pantalla final: el HTML del editor de la plataforma (imagen + texto), centrado. */\n.deepdots-success{display:none;width:100%;text-align:center;padding:24px 0;color:${textPrimary}}\n.deepdots-success img{max-width:100%;height:auto;margin:0 auto 16px auto;display:block}\n.deepdots-success p{margin:0;font-size:16px;font-weight:600;line-height:1.4}\n.deepdots-popup-footer{display:flex;flex-direction:row-reverse;justify-content:space-between;align-items:center;gap:4px;margin-top:auto;width:100%;padding-top:12px}\n.dd-nav-btn{display:none;border:none;min-height:44px;padding:12px 24px;border-radius:999px;cursor:pointer;font-size:15px;font-weight:600;align-items:center;justify-content:center;text-align:center}\n/* Secundario como botón de texto: sin borde ni fondo, para que el primario sea la única CTA. */\n#dd-back{background:transparent;color:${textMuted};border:none}\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 ${mobileCard}\n /* Apilados: la acción principal (Send/Start/Complete) arriba y Back debajo. El orden del\n DOM ya es ese, así que \\`column\\` (no \\`column-reverse\\`) es lo que lo respeta. */\n .deepdots-popup-footer{flex-direction:column;gap:4px}\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${customCss}\n</style>\n</head><body>\n<div class=\"${popupClass}\" id=\"dd-popup\" dir=\"${initDirection}\">\n <div class=\"deepdots-popup-header\">\n <h2 id=\"dd-title\" class=\"deepdots-popup-title\" hidden></h2>\n <button type=\"button\" id=\"dd-close\" aria-label=\"${initLabels.closeAria}\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M5 5L19 19M5 19L19 5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"butt\"/>\n </svg>\n </button>\n </div>\n <div class=\"deepdots-progress\" id=\"dd-progress\">\n <div class=\"deepdots-progress-head\">\n <span id=\"dd-progress-label\"><span id=\"dd-progress-current\"></span><span id=\"dd-progress-total\"></span></span>\n <span id=\"dd-progress-followup\">${initLabels.followUp}</span>\n </div>\n <div class=\"deepdots-progress-track\"><div id=\"dd-progress-bar\"></div></div>\n </div>\n <div class=\"deepdots-popup-container-content\" id=\"dd-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=\"${initLabels.loadingAria}\"><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>\n <div class=\"deepdots-popup-footer\" id=\"dd-footer\">\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-submit\"></button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-back\"></button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-complete\"></button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-start\"></button>\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 var chromeOn=${chrome ? 'true' : 'false'};\n\n var popup=document.getElementById('dd-popup');\n var main=document.getElementById('dd-main');\n var content=document.getElementById('dd-content');\n var footer=document.getElementById('dd-footer');\n var spinner=document.getElementById('dd-spinner');\n var formHost=document.getElementById('mf');\n var formWrapper=document.getElementById('dd-form-wrapper');\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 var titleEl=document.getElementById('dd-title');\n var progressEl=document.getElementById('dd-progress');\n var progressLabel=document.getElementById('dd-progress-label');\n var progressCurrent=document.getElementById('dd-progress-current');\n var progressTotal=document.getElementById('dd-progress-total');\n var progressFollowUp=document.getElementById('dd-progress-followup');\n var progressBar=document.getElementById('dd-progress-bar');\n var closeEl=document.getElementById('dd-close');\n\n // --- i18n del chrome -------------------------------------------------------------------\n // La tabla completa viaja embebida porque el idioma del survey (\\`formData.lang[0]\\`) solo se\n // conoce ya dentro del WebView. \\`ddResolveLocale\\` es el espejo del \\`resolveLocale\\` de TS\n // (paridad cubierta por test).\n var DD_LABELS=${labelsTableJson};\n var DD_ACTION_OVERRIDES=${actionOverridesJson};\n ${RESOLVE_LOCALE_JS}\n var DD_LABELS_ACTIVE=DD_LABELS[ddResolveLocale(${initLangJson})];\n var DD_RTL=${rtlLocalesJson};\n function ddIsRtl(lang){ return DD_RTL.indexOf(ddResolveLocale(lang))!==-1; }\n function ddApplyLabels(lang){\n var next=DD_LABELS[ddResolveLocale(lang)];\n if(!next) return;\n DD_LABELS_ACTIVE=next;\n // El survey ya se ha volteado a sí mismo: el chrome tiene que acompañarlo.\n popup.setAttribute('dir', ddIsRtl(lang) ? 'rtl' : 'ltr');\n backBtn.textContent=DD_ACTION_OVERRIDES.back||next.back;\n startBtn.textContent=DD_ACTION_OVERRIDES.start||next.start;\n completeBtn.textContent=DD_ACTION_OVERRIDES.complete||next.complete;\n submitBtn.textContent=DD_ACTION_OVERRIDES.accept||next.accept;\n if(closeEl){ closeEl.setAttribute('aria-label',next.closeAria); }\n if(spinner){ spinner.setAttribute('aria-label',next.loadingAria); }\n progressFollowUp.textContent=next.followUp;\n }\n\n function setTitle(t){\n if(!t) return;\n titleEl.textContent=t;\n titleEl.hidden=false;\n }\n setTitle(${titleJson});\n\n // Estado de la barra de progreso. \\`progressPref\\` null = manda la plataforma (style).\n var progressPref=${progressPref};\n var progressEnabled=progressPref===true;\n var progressShowUnit=true;\n var progressUnit='fraction';\n var onStartPage=false;\n\n function updateProgress(p){\n var total=p&&typeof p.total==='number'?p.total:0;\n var progress=p&&typeof p.progress==='number'?p.progress:0;\n // Solo tiene sentido con más de una página, fuera de la pantalla de inicio y sin completar.\n if(!progressEnabled||onStartPage||p&&p.completed||total<=1){\n progressEl.style.display='none';\n // Sin barra, el hueco cabecera→pregunta lo pone el contenido.\n content.style.paddingTop='16px';\n return;\n }\n progressEl.style.display='flex';\n // Con barra, el hueco inferior ya lo pone el bloque de progreso: no duplicarlo.\n content.style.paddingTop='0';\n // Espejo de LineProgressQuestion (MagicSurvey): la barra usa el valor real (las follow-up\n // suman +0.5 y avanzan media casilla) y la etiqueta redondea hacia abajo, porque una\n // follow-up es un paso DENTRO de la misma pregunta, no la siguiente.\n var current=Math.min(total,Math.max(1,progress+1));\n var pct=Math.min(100,Math.max(0,(current/total)*100));\n progressBar.style.width=pct+'%';\n var label=Math.min(total,Math.max(1,Math.floor(progress)+1));\n progressLabel.style.display=progressShowUnit?'block':'none';\n if(progressUnit==='percentage'){\n progressCurrent.textContent=Math.round(pct)+'%';\n progressTotal.textContent='';\n } else {\n progressCurrent.textContent=DD_LABELS_ACTIVE.question+' '+label;\n progressTotal.textContent=' '+DD_LABELS_ACTIVE.of+' '+total;\n }\n progressFollowUp.style.display=p&&p.followup?'inline-block':'none';\n }\n\n backBtn.textContent=${backLabel};\n startBtn.textContent=${startLabel};\n completeBtn.textContent=${completeLabel};\n submitBtn.textContent=${submitLabel};\n\n closeEl.onclick=function(){ emitJSON('popup_close'); };\n backBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.back){ window.DeepdotsForm.back(); } };\n startBtn.onclick=function(){\n if(window.DeepdotsForm && window.DeepdotsForm.startForm){\n window.DeepdotsForm.startForm();\n onStartPage=false;\n updateProgress({ progress:window.DeepdotsForm.progress, total:window.DeepdotsForm.total, completed:false, followup:false });\n }\n };\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 // Mensaje final configurado en la plataforma (\\`style.successMessage\\`). Se guarda al cargar\n // y se pinta al completar. Va por innerHTML porque es HTML del editor de la plataforma\n // (imagen + texto), igual que ya hace \\`renderStartMessage\\` con el mensaje de inicio.\n var successMessageHtml='';\n function showSuccessScreen(){\n progressEl.style.display='none';\n var wrapper=document.getElementById('dd-form-wrapper');\n if(wrapper){ wrapper.style.display='none'; }\n var done=document.getElementById('dd-success');\n if(!done){\n done=document.createElement('div');\n done.id='dd-success';\n done.className='deepdots-success';\n main.insertBefore(done, errorHint);\n }\n done.innerHTML=successMessageHtml || '<p>Thank you for your feedback!</p>';\n done.style.display='block';\n }\n\n // Profundidad de navegación DENTRO del survey: +1 por página avanzada, -1 al volver.\n // Sustituye a la condición \\`total>1 && progress>0 && progress<total\\`, que escondía el\n // Back siempre que la siguiente pantalla era una follow-up dinámica: las follow-up no\n // entran en el grafo (suman +0.5 al progress y no tocan el total), así que un survey de\n // una pregunta con follow-up tiene total=1 y nunca cumplía \\`total>1\\`.\n var pageDepth=0;\n\n function updateNavButtons(){\n updateButtons(pageDepth>0?'in_progress_next':'in_progress_first');\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${PRIMARY_COLOR_JS}\n${BUSY_JS}\n${REVEAL_JS}\n // El popup se enseña cuando el survey está pintado; \\`ready\\` deja al host abrir su propio\n // Modal en ese momento en vez de montar un WebView en blanco.\n var ddReveal=ddCreateReveal(popup, function(){\n document.body.classList.add('dd-ready');\n emitJSON('ready');\n }, ${REVEAL_TIMEOUT_MS});\n\n function setLoading(isLoading){\n spinner.style.display=isLoading?'flex':'none';\n // El spinner no tapa el survey (28px en posicion absoluta), asi que sin esto el usuario\n // puede seguir cambiando de opcion mientras se envia la pagina, y ese cambio ya no viaja\n // con ella. Se bloquea el wrapper, que es el elemento estable: el div de dentro lo\n // sustituye @magicfeedback/native por su propio contenedor al generar el survey.\n ddSetSurveyBusy(formWrapper, isLoading);\n if(!isLoading){\n formHost.style.visibility='visible';\n // Fin de la carga, sea por survey cargado o por error. Entre páginas es no-op: la\n // revelación solo actúa la primera vez.\n ddReveal.whenPainted();\n }\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 // La pantalla final la pinta el popup: renderSuccess de @magicfeedback/native usa\n // textContent, así que el mensaje de la plataforma (HTML con imagen) no se vería, y su\n // fallback es un literal genérico que ignora style.successMessage.\n addSuccessScreen:false,\n onLoadedEvent:function(args){\n // Idioma del survey: manda sobre el del init para que el chrome no se quede en\n // inglés delante de un survey traducido.\n var langs=args && args.formData ? args.formData.lang : null;\n if(langs && langs.length){\n for(var li=0;li<langs.length;li++){\n if(typeof langs[li]==='string' && langs[li].trim()){ ddApplyLabels(langs[li]); break; }\n }\n }\n var s=args && args.formData ? args.formData.style : null;\n if(s && !stylesInjected){\n stylesInjected=true;\n if(s.successMessage){ successMessageHtml=s.successMessage; }\n // La barra de progreso la decide el host (init) y, si no se pronuncia, la plataforma.\n if(progressPref===null){ progressEnabled=s.showProgressBar===true; }\n if(s.showProgressUnit!==undefined){ progressShowUnit=s.showProgressUnit!==false; }\n if(s.progressUnit){ progressUnit=s.progressUnit; }\n if(s.loadingBarColor){ progressBar.style.background=s.loadingBarColor; progressFollowUp.style.background=s.loadingBarColor; }\n if(chromeOn && s.boxBackgroundColor){ popup.style.background=s.boxBackgroundColor; }\n if(s.contentAlign){ main.style.justifyContent = s.contentAlign==='center' ? 'center' : 'flex-start'; }\n // El survey pinta sus controles con --mf-primary, que sale de primaryColor; si la\n // integración solo configura el del botón, el popup mezclaría el color de la marca\n // con el gris azulado por defecto del paquete.\n ddApplySurveyPrimaryColor(popup, s);\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 // Botón de texto: solo tiñe la etiqueta, sin recuperar fondo ni borde.\n backBtn.style.background='transparent'; backBtn.style.color=s.buttonSecondaryColor; backBtn.style.border='none';\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 // Márgenes lógicos: con un survey RTL el popup va con dir=\"rtl\" y el logo\n // tiene que acompañar al chrome. En LTR el resultado es el mismo de antes.\n if(s.logoPosition==='left'){ logoImg.style.margin='12px 0 0 0'; logoImg.style.setProperty('margin-inline-start','0'); logoImg.style.setProperty('margin-inline-end','16px'); }\n else if(s.logoPosition==='right'){ logoImg.style.margin='12px 0 0 0'; logoImg.style.setProperty('margin-inline-start','auto'); logoImg.style.setProperty('margin-inline-end','0'); }\n else if(s.logoPosition==='center'){ logoImg.style.margin='12px auto 0 auto'; }\n // Encima de la barra de progreso, como hermano del header (espejo de ui/logo.ts en\n // web). Fuera de #dd-main, así el logo no se va con el scroll del formulario.\n popup.insertBefore(logoImg, progressEl);\n }\n if(s.startMessage && s.startMessage!==''){ onStartPage=true; updateButtons('start'); }\n else { updateButtons('in_progress_first'); }\n } else {\n updateButtons('in_progress_first');\n }\n // El total solo se conoce una vez montado el form; sin él no hay barra que pintar.\n updateProgress({ progress:form.progress, total:form.total, completed:false, followup:false });\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 // La página no ha cambiado: el estado de navegación se queda como estaba.\n errorHint.textContent=DD_LABELS_ACTIVE.errorRequired;\n errorHint.style.display='block';\n updateNavButtons();\n return;\n }\n errorHint.textContent=DD_LABELS_ACTIVE.errorSubmit;\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 showSuccessScreen();\n updateButtons('completed');\n updateProgress(p);\n emitJSON('survey_completed', p);\n return;\n }\n pageDepth++;\n updateNavButtons();\n updateProgress(p);\n emitJSON('after_submit', p);\n },\n onBackEvent:function(p){\n // Con error (\"No page found\") no hubo navegación: la profundidad no se toca.\n if(!(p&&p.error)){ pageDepth=Math.max(0,pageDepth-1); }\n updateNavButtons();\n // onBackEvent no trae \\`total\\`: se lee del form, que es su dueño.\n updateProgress({ progress:p&&p.progress, total:form.total, completed:false, followup:p&&p.followup });\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","/**\n * Precalentado del chunk de `renderPopup`.\n *\n * `renderPopup` se carga por dynamic import para que importar el SDK sea seguro en React Native\n * y en SSR, pero eso deja su descarga (DOM del popup + `@magicfeedback/native` + el CSS\n * vendorizado, ~238 KB sin comprimir) dentro del camino crítico: empieza cuando el trigger ya ha\n * disparado y el usuario está mirando. Traerlo en idle en cuanto se sabe que hay algún popup que\n * puede abrirse deja el import de `show()` resuelto al instante.\n *\n * Es oportunista por diseño: si falla, no rompe nada porque `show()` hace su propio import.\n */\n\ntype Loader = () => Promise<unknown>;\n\nconst defaultLoader: Loader = () => import('./renderPopup');\n\n/** Precalentado en curso o ya resuelto. Null tras un fallo, para permitir reintento. */\nlet pending: Promise<void> | null = null;\n\n/**\n * Descarga el chunk una sola vez por sesión. Nunca rechaza: un precalentado que falla es un\n * no-evento, la carga de verdad la hará `show()`.\n */\nexport function preloadRenderPopup(loader: Loader = defaultLoader): Promise<void> {\n if (pending) return pending;\n try {\n // El loader se invoca ya, no en un microtask: el import debe empezar en este tick.\n pending = loader().then(() => undefined).catch(() => { pending = null; });\n } catch {\n pending = null;\n return Promise.resolve();\n }\n return pending;\n}\n\n/**\n * Agenda el precalentado para cuando el navegador esté ocioso, para no competir con el render\n * de la página del host. `requestIdleCallback` no existe en Safari, ahí basta con salir del\n * tick actual.\n */\nexport function schedulePreloadRenderPopup(loader?: Loader): void {\n const run = () => { void preloadRenderPopup(loader); };\n const idle = (globalThis as { requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => unknown }).requestIdleCallback;\n if (typeof idle === 'function') {\n idle(run, { timeout: 2000 });\n return;\n }\n setTimeout(run, 0);\n}\n","import {DeepdotsEventType, PopupActions, PopupStyle} from '../types';\nimport { isReactNativeEnv, createReactNativeRenderer } from './react-native-renderer';\nimport { schedulePreloadRenderPopup } from '../ui/preload';\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\n/**\n * Ajustes de presentación que no vienen del estilo del popup. Van agrupados para no seguir\n * alargando la lista posicional de `show`, que ya arrastra doce parámetros.\n */\nexport interface PopupRenderOptions {\n /**\n * Título de la cabecera: el `title` de la definición del popup, tal cual llega de la API.\n * Varía por popup y vacío es un valor válido (la cabecera se queda solo con la X).\n */\n title?: string;\n /** Barra de progreso: `undefined` deja decidir a la plataforma, `true`/`false` la fuerzan. */\n showProgressBar?: boolean;\n /** CSS del host, inyectado el último: gana sobre el del SDK y el de `@magicfeedback/native`. */\n surveyCss?: string;\n /**\n * Idioma con el que arranca el chrome del popup (botones, progreso, aria-labels, errores).\n * Es el idioma resuelto en `init()` (host > navigator > Intl). Lo pisa el idioma del survey\n * (`formData.lang[0]`) en cuanto este carga, y ambos los pisa `actions.*.label` de la API.\n */\n language?: string;\n}\n\nexport interface PopupRenderer {\n /** Preparar recursos si aplica */\n init?(): void;\n /**\n * Traer por adelantado lo que la apertura necesitará, en idle. Opcional: solo lo implementan\n * los renderers cuyo coste de apertura se paga delante del usuario.\n */\n preload?(): 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 renderChrome?: boolean,\n options?: PopupRenderOptions,\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 /**\n * Descarga el chunk de `renderPopup` antes de que ningún trigger dispare, para que abrir el\n * popup no empiece por esperar ~238 KB de JS.\n */\n preload(): void {\n schedulePreloadRenderPopup();\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 _renderChrome?: boolean, // solo aplica a RN (WebView); el popup DOM web siempre lleva su chrome\n options?: PopupRenderOptions,\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, options);\n });\n }\n\n hide(): void {\n this.visible = false;\n if (this.container) {\n this.container.style.display = 'none';\n // `renderPopup` abre el contenedor invisible y lo revela al cargar el survey; si se cierra\n // antes de esa revelación, el estado oculto no puede quedarse pegado al contenedor.\n this.container.style.visibility = '';\n this.container.style.pointerEvents = '';\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","import {\n DeepdotsConfig,\n TriggerConfig,\n DeepdotsEvent,\n DeepdotsEventType,\n EventListener,\n DeepdotsInitParams,\n DeepdotsLogger,\n FeedbackSession,\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 { clearPopupState, readPopupState, writePopupState } from './popup-state-store';\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/**\n * Latido: con la pestaña visible (o la app en foreground), el flush periódico manda el\n * engagement acumulado al menos cada ANALYTICS_HEARTBEAT_MS. Sin él, alguien leyendo una\n * sola página no enviaba nada — el page_view sale al SALIR de la pantalla y el engagement\n * al ocultar/cerrar — y el backend daba la sesión por abandonada con solo su session_start.\n */\nconst ANALYTICS_HEARTBEAT_MS = 5 * 60_000;\n/** Una pestaña que vuelve a verse tras este tiempo oculta empieza sesión nueva (timeout de sesión, como GA). */\nconst ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60_000;\n/**\n * Inactividad tras la que el backend cierra una sesión por su cuenta (Run_Jobs\n * `incomplete-surveys`, `IDLE_MINUTES`, 60 min; el barrido corre cada 20). Mantener en sincronía.\n */\nconst ANALYTICS_SERVER_IDLE_MS = 60 * 60_000;\n/**\n * Pasado este tiempo oculta, la sesión se da por cerrada en el backend: reenviarle el cierre\n * la volvería a montar entera en un segundo feedback, así que se descarta sin enviar nada.\n * Margen de 10 min bajo la ventana del backend: el último lote enviado puede ser algo\n * anterior al momento en que se ocultó la pestaña.\n */\nconst ANALYTICS_SESSION_DISCARD_MS = ANALYTICS_SERVER_IDLE_MS - 10 * 60_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 // Estado que alimenta las reglas de redisplay. Copia de trabajo en memoria; la fuente\n // duradera es el storage (`popup-state-store`), rehidratada en `init()` — sin eso, una\n // recarga reiniciaba el cooldown y el popup volvía a salir. `answeredSurveys` se deriva\n // de las entradas COMPLETED de `surveyProgress` (los dos los escribe `markSurveyAnswered`).\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 /** Si el SDK pinta el \"modal\" del popup (RN). Default true; false = el host lo envuelve. */\n private renderChrome = true;\n /** Barra de progreso. undefined = la decide la plataforma (style.showProgressBar). */\n private showProgressBar: boolean | undefined = undefined;\n /** CSS del host inyectado al final de los estilos del popup. */\n private surveyCss: string | undefined = undefined;\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 /** `init({ onFeedbackSession })`: avisa al host al abrir y cerrar cada registro. */\n private onFeedbackSession: DeepdotsInitParams['onFeedbackSession'] = 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 /** Último `user_engagement` emitido (ms epoch): marca el latido. */\n private lastEngagementAt = 0;\n /** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */\n private hiddenAt: number | null = null;\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 /** `geolocation` del init: si es false no se usa ni la caché ni el lookup. */\n private geoEnabled = true;\n /** Solo se consulta a terceros si el país/ciudad se va a enviar (analytics configurado). */\n private geoWanted = false;\n /** Evita más de un lookup por instancia (el consentimiento puede ir y venir). */\n private geoLookupStarted = false;\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 this.renderChrome = config.renderChrome ?? true;\n this.showProgressBar = config.showProgressBar;\n this.surveyCss = config.surveyCss;\n this.onFeedbackSession = config.onFeedbackSession;\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.loadPopupState(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: no envía nada y el payload\n // solo se pinta con `debug`, para no ensuciar la consola de la web del host en producción.\n const analyticsSink = config.analytics\n ? createFeedbackSink({\n baseUrl: this.baseUrl,\n keys: config.analytics,\n log: (...a) => this.log(...a),\n onSessionId: (id) => {\n this.analyticsFeedbackSessionId = id;\n this.notifyFeedbackSession(id, 'open');\n },\n onSessionClosed: (id) => this.notifyFeedbackSession(id, 'closed'),\n onSessionReset: () => { this.analyticsFeedbackSessionId = undefined; },\n })\n : createDryRunSink((...a) => { if (this.config?.debug) 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: ver `resolveGeo()` para cuándo se consulta a terceros.\n this.geoEnabled = config.geolocation ?? true;\n this.geoWanted = this.geoEnabled && !!config.analytics;\n this.resolveGeo();\n // Fase 2: navegación → eventos page_view por el canal de analytics.\n this.navObserver = new NavigationObserver();\n this.navObserver.onVisit((v) =>\n this.track('deepdots_page_view', { screen: v.screen, path: v.path, duration_seconds: v.durationSeconds })\n );\n this.navObserver.install();\n // Engagement time (#8): cuenta tiempo activo en primer plano.\n // No arranca aquí: lo hace `openSession`, y solo con la pestaña visible.\n this.engagement = new EngagementTracker();\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 // Con al menos un popup que pueda abrirse, el renderer se trae ya lo que costará la\n // apertura (en navegador, el chunk de renderPopup). Un host sin popups no descarga\n // nada.\n if (this.popupDefinitions.length) this.renderer.preload?.();\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 /**\n * Id del registro de analytics abierto: el `sessionId` que devolvió `POST /sdk/feedback`,\n * que la API guarda como `sdkSessionId` en el Feedback en que se convierte la sesión. No es\n * `getSessionId()`. Null hasta que el backend acepta el primer lote y de nuevo al cerrar la\n * sesión: para saber también qué registro se cerró, usa `init({ onFeedbackSession })`.\n */\n getFeedbackSessionId(): string | null {\n return this.analyticsFeedbackSessionId ?? null;\n }\n\n /** Avisa al host del registro abierto o cerrado; un fallo del host no corta el envío. */\n private notifyFeedbackSession(sessionId: string, status: FeedbackSession['status']): void {\n try {\n this.onFeedbackSession?.({ sessionId, status });\n } catch (error) {\n this.log('analytics · onFeedbackSession threw:', error);\n }\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 // El lookup de geo se aplaza hasta que haya consentimiento.\n if (enabled) this.resolveGeo();\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 * Product Effectiveness: una interacción significativa del usuario (`interaction_type` es la\n * dimensión de agrupación, p. ej. `get_help`, `homepage`, `contact_support`).\n *\n * Existe como evento RESERVADO para que la convención no la invente cada host: un `track()`\n * custom acaba como `deepdots_event_…` y no lo cuenta la analítica de effectiveness.\n * Un `interactionType` vacío se descarta con warning (no aporta dato agrupable).\n */\n trackMeaningfulInteraction(interactionType: string, params?: Record<string, unknown>): void {\n const type = typeof interactionType === 'string' ? interactionType.trim() : '';\n if (!type) {\n // Siempre visible (como en `trackMessage`): es un error de integración del host.\n console.warn('[DeepdotsPopups] trackMeaningfulInteraction discarded: empty interaction_type');\n return;\n }\n this.track('deepdots_meaningful_interaction', { interaction_type: type, ...(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 // El historial de redisplay era del usuario anterior: el nuevo empieza de cero.\n this.lastShown.clear();\n this.surveyProgress.clear();\n this.answeredSurveys.clear();\n clearPopupState(storage);\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 // Parado hasta la sesión siguiente. Si siguiera en marcha, lo que llega después del\n // cierre (en Chromium el `visibilitychange` va DESPUÉS de `pagehide`) emitía un\n // `user_engagement` sin sessionId que abría en la API un registro que nunca se cierra.\n this.engagement?.pause();\n this.track('deepdots_session_end', { reason });\n // `final` solo cuando el documento se está muriendo (el transporte no espera respuestas).\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.lastEngagementAt = Date.now();\n this.track('deepdots_session_start', {});\n // El cierre anterior dejó sin pantalla al observador: sin esto, la pantalla en la que\n // está el usuario al reabrir (cambio de usuario, vuelta de la bfcache) no contaba.\n this.navObserver?.restart();\n // Una pestaña abierta en segundo plano no ha sido vista: no cuenta hasta que se muestre.\n if (this.isDocumentVisible()) this.engagement?.resume();\n }\n\n /** Web: si la pestaña se ve. Sin `document` (RN) manda AppState vía onForeground/onBackground. */\n private isDocumentVisible(): boolean {\n return typeof document === 'undefined' || document.visibilityState !== 'hidden';\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): el lote no espera al primer POST en vuelo, porque\n * el documento se muere; 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) {\n this.track('deepdots_user_engagement', { engagement_time_msec: ms });\n this.lastEngagementAt = Date.now();\n }\n }\n\n /** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */\n private onAnalyticsFlushTick(): void {\n if (\n this.sessionOpen &&\n this.isDocumentVisible() &&\n Date.now() - this.lastEngagementAt >= ANALYTICS_HEARTBEAT_MS\n ) {\n this.flushEngagement();\n }\n this.flushAnalytics();\n }\n\n /**\n * La pestaña vuelve a verse. Tras más de ANALYTICS_SESSION_TIMEOUT_MS oculta, la sesión\n * anterior termina y empieza otra: seguir con el mismo sessionId mezclaba dos visitas y,\n * si el backend ya la había cerrado por inactividad, la partía en dos feedbacks.\n * - Oculta menos de ANALYTICS_SESSION_DISCARD_MS: el backend aún la tiene abierta → se\n * cierra con normalidad (page_view de la pantalla, session_end `idle_timeout`, completed).\n * - Más: ya la cerró el backend → se descarta sin enviar nada.\n */\n private onDocumentVisible(): void {\n const hiddenFor = this.hiddenAt === null ? 0 : Date.now() - this.hiddenAt;\n this.hiddenAt = null;\n if (!this.sessionOpen) return; // sin sesión abierta no hay dónde contar ese tiempo\n if (hiddenFor < ANALYTICS_SESSION_TIMEOUT_MS) {\n this.engagement?.resume();\n return;\n }\n if (hiddenFor < ANALYTICS_SESSION_DISCARD_MS) {\n this.closeSession('idle_timeout');\n } else {\n this.discardSession();\n }\n this.openSession();\n }\n\n /** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */\n private discardSession(): void {\n this.sessionOpen = false;\n this.navObserver?.discard();\n this.analytics?.discardSession(this.analyticsIdentity());\n this.engagement?.consume(); // tiramos lo acumulado: era de la sesión descartada\n this.engagement?.pause();\n this.analyticsFeedbackSessionId = undefined;\n this.tracking?.setSessionId(null);\n this.log('tracking · session discarded after', ANALYTICS_SESSION_DISCARD_MS / 60_000, 'min hidden');\n }\n\n /**\n * Geolocalización por IP (país/ciudad para analytics). La caché persistente es local y se\n * aplica siempre que `geolocation` no esté desactivado. El lookup a terceros (ipapi.co →\n * ipwho.is → ipinfo.io, fallback con timeout) solo se hace si el dato se va a ENVIAR:\n * analytics configurado + tracking activo + caché ausente o caducada. Una vez por instancia.\n */\n private resolveGeo(): void {\n const storage = this.storage;\n if (!this.geoEnabled || !storage || !this.analytics) return;\n const cachedGeo = readCachedGeo(storage, Date.now());\n if (cachedGeo) {\n this.analytics.updateDevice(cachedGeo);\n return;\n }\n if (!this.geoWanted || this.geoLookupStarted) return;\n if (!this.tracking?.isTrackingEnabled()) return;\n this.geoLookupStarted = true;\n fetchGeo()\n .then((geo) => {\n if (geo) {\n this.analytics?.updateDevice(geo);\n writeCachedGeo(storage, geo, Date.now());\n }\n })\n .catch(() => {});\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 // Flush periódico + latido en todos los entornos. En RN no había ninguno: una app en\n // foreground que no llegaba a `maxBatchSize` no enviaba nada hasta ir a background.\n this.startAnalyticsFlushTimer();\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\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.\n window.addEventListener('pagehide', () => {\n clearInterval(this.analyticsFlushTimer);\n this.closeSession('page_hide');\n });\n // Vuelta desde la bfcache (botón atrás): la página no se recarga, así que `init()` no\n // vuelve a correr. El `pagehide` cerró la sesión y paró el flush; sin reabrir ambos, la\n // pestaña no mandaba nada más hasta una recarga.\n window.addEventListener('pageshow', (event) => {\n if (!(event as PageTransitionEvent).persisted) return;\n this.startAnalyticsFlushTimer();\n this.openSession();\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 this.hiddenAt = Date.now();\n } else if (document.visibilityState === 'visible') {\n this.onDocumentVisible();\n }\n });\n }\n\n private startAnalyticsFlushTimer(): void {\n clearInterval(this.analyticsFlushTimer);\n this.analyticsFlushTimer = setInterval(() => this.onAnalyticsFlushTick(), ANALYTICS_FLUSH_INTERVAL_MS);\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.persistPopupState();\n this.renderPopup(def.surveyId, def.productId, def.actions, def.style, def.title);\n this.emitEvent('popup_shown', def.surveyId, { popupId: def.id });\n }\n\n private shouldShow(def: NormalizedPopupDefinition, pathUrl?: string, skipPathCheck = false): boolean {\n // Otra pestaña puede haber enseñado o contestado este popup desde nuestro `init()`.\n if (this.storage) this.loadPopupState(this.storage);\n // La exclusión se evalúa SIEMPRE, también cuando `skipPathCheck` levanta la\n // restricción de \"dónde PUEDE mostrarse\" (popup de exit ya encolado): decir\n // \"no en /cart\" es una regla sobre la pantalla en la que se va a pintar.\n if (this.isPathExcluded(def, pathUrl)) {\n return false;\n }\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 /** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */\n private matchesSegmentsPath(def: PopupDefinition, pathUrl?: string): boolean {\n const paths = def.segments?.path;\n if (!Array.isArray(paths) || paths.length === 0) return true;\n\n const location = this.resolveCurrentLocation(pathUrl);\n if (!location) {\n this.debug('No window.location available for path comparison', { popupId: def.id, paths });\n return true;\n }\n\n const matches = paths.some((rawCandidate) => {\n const match = this.matchesPathCandidate(rawCandidate, location);\n this.debug('Path comparison', {\n popupId: def.id,\n candidate: rawCandidate,\n currentPath: location.path,\n currentHref: location.href,\n match,\n });\n return match;\n });\n\n if (!matches) {\n this.debug('No path match for popup', { popupId: def.id, paths, currentPath: location.path });\n }\n\n return matches;\n }\n\n /**\n * `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre\n * `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el\n * carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).\n */\n private isPathExcluded(def: PopupDefinition, pathUrl?: string): boolean {\n const excluded = def.segments?.excludedPaths;\n if (!Array.isArray(excluded) || excluded.length === 0) return false;\n\n const location = this.resolveCurrentLocation(pathUrl);\n if (!location) {\n // Sin `window.location` (RN/SSR) no se puede evaluar: no bloqueamos, igual\n // que hace `matchesSegmentsPath` con la lista de inclusión.\n this.debug('No window.location available for excluded path comparison', { popupId: def.id, excluded });\n return false;\n }\n\n const hit = excluded.find((rawCandidate) => this.matchesPathCandidate(rawCandidate, location));\n if (hit !== undefined) {\n this.debug('Popup excluded by path', {\n popupId: def.id,\n candidate: hit,\n currentPath: location.path,\n currentHref: location.href,\n });\n return true;\n }\n return false;\n }\n\n /**\n * Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta\n * actual. Mismas reglas para incluir y para excluir:\n * - URL absoluta → href completo exacto\n * - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)\n * - resto → pathname exacto\n * Ignora entradas que no sean string: el backend puede colar un `null` en la lista\n * y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con\n * `segments.lang`).\n */\n private matchesPathCandidate(rawCandidate: unknown, location: { href: string; path: string }): boolean {\n if (typeof rawCandidate !== 'string') return false;\n const candidate = this.normalizeUrl(rawCandidate);\n if (!candidate) return false;\n if (candidate.startsWith('http://') || candidate.startsWith('https://')) {\n return location.href === candidate;\n }\n if (candidate.startsWith('/')) {\n return location.href.includes(candidate);\n }\n return location.path === candidate;\n }\n\n /** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */\n private resolveCurrentLocation(pathUrl?: string): { href: string; path: string } | null {\n if (typeof window === 'undefined' || !window.location) return null;\n const href = this.normalizeUrl(pathUrl || window.location.href || '');\n const parsed = this.safeParseUrl(href);\n return { href, path: this.normalizeUrl(parsed?.pathname || window.location.pathname || '') };\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 // El backend aplica las reglas de redisplay AQUI: si este userId ya tiene un\n // SHOWED/PARTIAL/COMPLETED dentro del cooldown, no devuelve ese popup. Para que\n // cuadre, hay que preguntar por el MISMO id con el que se reporta el estado en\n // `POST /sdk/popups` — el del tracking (host si lo hay, si no el anonimo\n // persistente). Con `config.userId` a secas, la integracion que no pasa userId\n // (el caso por defecto) preguntaba sin filtro y el backend devolvia todo.\n const userId = this.tracking?.getUserId() ?? 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, title?: string): 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 this.renderChrome,\n { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language },\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 { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language },\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 this.persistPopupState();\n }\n\n /**\n * Fusiona el estado de redisplay del storage con el de memoria: por cada popup/survey gana\n * la entrada MÁS RECIENTE. Fusionar y no sustituir es lo que hace convivir a varias\n * pestañas sobre el mismo `localStorage`: se llama en el `init()`, antes de cada decisión\n * de mostrar y antes de cada escritura. `answeredSurveys` se reconstruye desde las\n * entradas COMPLETED: es exactamente lo que persiste `markSurveyAnswered`.\n */\n private loadPopupState(storage: KeyValueStorage): void {\n const state = readPopupState(storage);\n Object.entries(state.lastShown).forEach(([popupId, timestamp]) => {\n if (timestamp > (this.lastShown.get(popupId) ?? -Infinity)) this.lastShown.set(popupId, timestamp);\n });\n Object.entries(state.progress).forEach(([surveyId, entry]) => {\n const current = this.surveyProgress.get(surveyId);\n if (current && current.timestamp >= entry.timestamp) return;\n this.surveyProgress.set(surveyId, { status: entry.status, timestamp: entry.timestamp });\n if (entry.status === 'COMPLETED') this.answeredSurveys.add(surveyId);\n });\n }\n\n /**\n * Vuelca el estado de redisplay al storage. Sin storage (o bloqueado) degrada a memoria.\n * Fusiona antes de escribir: escribir solo la foto en memoria borraba lo que otra pestaña\n * hubiese guardado desde nuestro `init()`.\n */\n private persistPopupState(): void {\n if (!this.storage) return;\n this.loadPopupState(this.storage);\n writePopupState(this.storage, {\n lastShown: Object.fromEntries(this.lastShown),\n progress: Object.fromEntries(this.surveyProgress),\n });\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 * 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 (`fetch` con `keepalive`) y no puede esperar\n * 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 * `true` para olvidar la sesión SIN enviar nada (el payload llega vacío): el backend ya la\n * cerró por inactividad. El sink descarta su `sessionId` para que el lote siguiente abra un\n * registro nuevo, igual que tras `sessionEnd`, pero sin POST.\n */\n resetSession?: 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, meta) => {\n if (meta?.resetSession) return;\n log(\n '[DeepdotsAnalytics] (dry-run · NOT sent · no init.analytics) POST /sdk/feedback →',\n JSON.stringify(payload, null, 2),\n );\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 la sesión en curso SIN emitir nada: vacía el buffer y los mini-services activos\n * (sin `mini_service_exit`). Para una sesión que el backend ya cerró por inactividad: lo\n * que quedara pendiente pertenecía a ella y no debe llegar con la sesión nueva.\n */\n discardSession(identity: AnalyticsIdentity): void {\n this.events = [];\n this.activeMiniServices.clear();\n try {\n void this.sink({ ...this.buildPayload(identity), events: [] }, { resetSession: true });\n } catch {\n /* el sink no debe romper el ciclo de vida */\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 * @deprecated Se ignora. El flush final salía por `sendBeacon`, que va SIEMPRE con\n * credenciales; contra la API (`Access-Control-Allow-Origin: *` + `Allow-Credentials:\n * true`) el preflight fallaba y el lote de cierre, el único con `completed:true`, no\n * llegaba nunca. Ahora sale por `fetch` con `keepalive`. Se mantiene en el tipo para no\n * romper a quien lo pase.\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 * Callback invocado cuando el backend acepta el lote de cierre, con el sessionId del\n * registro cerrado: el que llevaba el lote o, en una sesión de un solo lote, el que\n * devuelve la respuesta. Ese registro se convierte en Feedback en segundos.\n */\n onSessionClosed?: (id: string) => void;\n}\n\n/** Límite práctico de `fetch({keepalive:true})` (~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 ni al cerrar la\n * página, también en el flush final (sin `sendBeacon`: ver `sendBeaconImpl`);\n * - sin credenciales, que es lo único que pasa el CORS de la API (`Allow-Origin: *`);\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 * Sube cada vez que se cierra u olvida una sesión. Una respuesta que llega con otra\n * generación es de una sesión ya cerrada (p. ej. el primer POST seguía en vuelo en el\n * `page_hide`) y no puede volver a cachear su sessionId ni avisarlo como abierto.\n */\n let generation = 0;\n\n const url = `${options.baseUrl}/sdk/feedback`;\n\n const post = async (body: AnalyticsFeedbackBody, final: boolean, sessionEnd = false): Promise<void> => {\n const postGeneration = generation;\n const json = JSON.stringify(body);\n const small = json.length <= KEEPALIVE_MAX_BYTES;\n\n // Sin `sendBeacon` a propósito, también en el cierre de página (ver `sendBeaconImpl`):\n // `keepalive` sobrevive igual al unload y, sin credenciales, pasa el preflight de la API.\n if (final) options.log?.('[DeepdotsAnalytics] final flush via fetch keepalive');\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 // La API autentica por `publicKey` en el body y responde `Allow-Origin: *`: una\n // petición con credenciales fallaría el CORS. Explícito para no depender del default.\n credentials: 'omit',\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 // pero se avisa por onSessionClosed, porque ese registro ya va a ser un Feedback.\n if (sessionEnd) {\n let closedId = body.sessionId;\n try {\n const data = (await res.json()) as { sessionId?: string };\n closedId = data?.sessionId ?? closedId;\n } catch {\n /* respuesta sin JSON: vale el sessionId que llevaba el lote */\n }\n try {\n if (closedId) options.onSessionClosed?.(closedId);\n } catch {\n // Un fallo del callback no puede convertir el lote entregado en uno fallido:\n // el manager lo re-encolaría y el cierre se enviaría dos veces.\n }\n return;\n }\n\n try {\n const data = (await res.json()) as { sessionId?: string };\n if (postGeneration !== generation) {\n options.log?.('[DeepdotsAnalytics] sessionId of a closed session ignored:', data?.sessionId);\n return;\n }\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 if (meta?.resetSession) {\n // Sesión cerrada por el backend: no se envía nada, solo se olvida el registro.\n feedbackSessionId = undefined;\n firstPostInFlight = null;\n generation++;\n options.onSessionReset?.();\n return;\n }\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 generation++;\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 /** Pantalla normalizada (IDs → `:id`): agrupa las visitas a la misma pantalla. */\n screen: string;\n /**\n * La ruta real, sin query: `/insights/requests/2f1c…` donde `screen` dice\n * `/insights/requests/:id`. Es lo que permite enlazar la visita al elemento concreto.\n */\n path: 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/** La ruta de una URL: path (+hash route), sin query. Sin normalizar los IDs. */\nexport function screenPath(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 return pathAndHash.replace(/\\?[^#]*/g, '') || '/';\n}\n\n/** Normaliza una URL a un nombre de pantalla: path (+hash route), sin query, IDs → :id. */\nexport function normalizeScreen(href: string): string {\n const pathAndHash = screenPath(href);\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 currentPath: 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.currentPath = screenPath(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 nextPath = screenPath(href);\n // Misma ruta (solo cambia la query) → no es una visita nueva. Se compara la ruta y no\n // la pantalla normalizada: pasar de una research a otra (`/insights/requests/A` → `/B`)\n // sí es otra visita, aunque las dos sean `/insights/requests/:id`.\n if (this.currentScreen != null && nextPath === this.currentPath) return;\n this.closeCurrent();\n this.currentScreen = normalizeScreen(href);\n this.currentPath = nextPath;\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 this.currentPath = null;\n }\n\n /**\n * Olvida la pantalla actual SIN emitir su visita. Para una sesión que el backend ya dio\n * por cerrada: su visita no tiene dónde ir, y mandarla con la sesión nueva la contaría\n * donde no ocurrió. Después, `restart()` vuelve a empezar en la pantalla actual.\n */\n discard(): void {\n this.currentScreen = null;\n this.currentPath = null;\n }\n\n /**\n * Retoma el seguimiento en la pantalla actual tras un `stop()`, cuando se abre una sesión\n * nueva sin navegar (cambio de usuario, vuelta de la bfcache). No-op si nunca se instaló\n * (navegación manual por `setScreen`, RN) o si ya hay pantalla en curso.\n */\n restart(): void {\n if (!this.installed || this.currentScreen != null || typeof window === 'undefined') return;\n this.begin(window.location.href);\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 path: this.currentPath ?? 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 * Persistencia del estado que alimenta las reglas de redisplay (cooldown) de los popups.\n *\n * Hasta la 1.7.0 `lastShown`/`surveyProgress` vivían SOLO en memoria, así que cualquier\n * recarga de página reiniciaba el cooldown y el popup volvía a salir aunque su regla\n * dijese \"no antes de N días\". El SDK nativo (KMP) sí lo persistía (`getLastShown`/\n * `setLastShown` contra el `KeyValueStorage`); esto cierra esa divergencia en Web.\n *\n * ⚠️ El alcance es POR NAVEGADOR: otro dispositivo, otro perfil o un borrado de datos\n * vuelven a empezar. El cooldown por usuario de verdad exige que el backend filtre el\n * `GET /sdk/{publicKey}/popups` con el historial que ya recibe en `POST /sdk/popups`.\n */\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\nimport { POPUP_TRIGGER_CONDITION_STATUSES } from '../types';\nimport type { PopupTriggerConditionStatus } from '../types';\n\n/** Clave única en el namespace `deepdots.*` (mismo que `STORAGE_KEYS`). */\nexport const POPUP_STATE_STORAGE_KEY = 'deepdots.popups.state';\n\n/**\n * Techo de entradas por mapa, con desalojo del más ANTIGUO (mismo trade-off explícito\n * que `MAX_TRACKED_MESSAGES` en `MessageGuard`). Una cuenta real tiene unos pocos popups;\n * el techo solo evita que el registro crezca sin límite si se rotan definiciones.\n * No se poda por antigüedad a propósito: un `cooldownDays` largo debe seguir valiendo.\n */\nexport const MAX_TRACKED_POPUP_ENTRIES = 200;\n\nexport interface PersistedProgressEntry {\n status: PopupTriggerConditionStatus;\n timestamp: number;\n}\n\nexport interface PersistedPopupState {\n /** popupId → timestamp de la última vez que se mostró. */\n lastShown: Record<string, number>;\n /** surveyId → último estado conocido (PARTIAL/COMPLETED) y cuándo. */\n progress: Record<string, PersistedProgressEntry>;\n}\n\nfunction emptyState(): PersistedPopupState {\n return { lastShown: {}, progress: {} };\n}\n\nfunction isValidTimestamp(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\n/** Se queda con las `MAX_TRACKED_POPUP_ENTRIES` entradas de timestamp más reciente. */\nfunction capEntries<T>(entries: Array<[string, T]>, timestampOf: (value: T) => number): Array<[string, T]> {\n if (entries.length <= MAX_TRACKED_POPUP_ENTRIES) return entries;\n return [...entries]\n .sort((a, b) => timestampOf(b[1]) - timestampOf(a[1]))\n .slice(0, MAX_TRACKED_POPUP_ENTRIES);\n}\n\n/**\n * Lee el estado persistido. Tolerante a todo: sin clave, JSON corrupto, entradas con\n * forma inválida o un storage que lanza (Safari en privado, cookies bloqueadas) →\n * estado vacío o entradas buenas, nunca una excepción.\n */\nexport function readPopupState(storage: KeyValueStorage): PersistedPopupState {\n let raw: string | null = null;\n try {\n raw = storage.getItem(POPUP_STATE_STORAGE_KEY);\n } catch {\n return emptyState();\n }\n if (!raw) return emptyState();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return emptyState();\n }\n if (typeof parsed !== 'object' || parsed === null) return emptyState();\n\n const source = parsed as Partial<Record<keyof PersistedPopupState, unknown>>;\n const state = emptyState();\n\n if (typeof source.lastShown === 'object' && source.lastShown !== null) {\n const valid = Object.entries(source.lastShown as Record<string, unknown>)\n .filter((entry): entry is [string, number] => isValidTimestamp(entry[1]));\n capEntries(valid, (timestamp) => timestamp).forEach(([popupId, timestamp]) => {\n state.lastShown[popupId] = timestamp;\n });\n }\n\n if (typeof source.progress === 'object' && source.progress !== null) {\n const valid = Object.entries(source.progress as Record<string, unknown>)\n .filter((entry): entry is [string, PersistedProgressEntry] => {\n const value = entry[1];\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<PersistedProgressEntry>;\n return (\n POPUP_TRIGGER_CONDITION_STATUSES.includes(candidate.status as PopupTriggerConditionStatus)\n && isValidTimestamp(candidate.timestamp)\n );\n });\n capEntries(valid, (entry) => entry.timestamp).forEach(([surveyId, entry]) => {\n state.progress[surveyId] = { status: entry.status, timestamp: entry.timestamp };\n });\n }\n\n return state;\n}\n\n/** Escribe el estado (acotado). Un storage que lanza se ignora en silencio. */\nexport function writePopupState(storage: KeyValueStorage, state: PersistedPopupState): void {\n const capped: PersistedPopupState = emptyState();\n capEntries(Object.entries(state.lastShown), (timestamp) => timestamp).forEach(([popupId, timestamp]) => {\n capped.lastShown[popupId] = timestamp;\n });\n capEntries(Object.entries(state.progress), (entry) => entry.timestamp).forEach(([surveyId, entry]) => {\n capped.progress[surveyId] = entry;\n });\n\n try {\n storage.setItem(POPUP_STATE_STORAGE_KEY, JSON.stringify(capped));\n } catch {\n // Storage lleno o bloqueado por el navegador: el cooldown degrada a solo-memoria.\n }\n}\n\n/** Borra el registro (cambio de usuario: el historial era del anterior). */\nexport function clearPopupState(storage: KeyValueStorage): void {\n try {\n storage.removeItem(POPUP_STATE_STORAGE_KEY);\n } catch {\n // idem\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","/**\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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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["clean","pending","revealWhenPainted","magicfeedback","renderPopup","pending","renderPopup","sdk"]}
|
|
1
|
+
{"version":3,"sources":["../src/tracking/tracking-manager.ts","../src/ui/font.ts","../src/i18n/labels.ts","../src/ui/reveal.ts","../src/ui/surveyPalette.ts","../src/ui/busy.ts","../src/assets/style.css","../src/util/logger.ts","../src/ui/logo.ts","../src/ui/renderPopup.ts","../src/react-native/index.tsx","../src/types/index.ts","../src/triggers/index.ts","../src/config/env.ts","../src/platform/react-native-renderer.ts","../src/ui/surveyHtml.ts","../src/ui/preload.ts","../src/platform/renderer.ts","../src/core/deepdots-popups.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/core/popup-state-store.ts","../src/analytics/messaging.ts","../src/react-native/setup.ts"],"sourcesContent":["/**\n * Identidad + sesión para el tracking de navegación (Fase 1).\n * Implementa el \"Contrato común Web+KMP\".\n *\n * Modelo: el SDK gestiona el `user_id` (persistente). El `session_id` es propiedad\n * del BACKEND: llega en la respuesta de `POST /sdk/popups` y se cachea; el backend\n * cose sesiones por `user_id` + ventana. El SDK NO genera ni expira sesiones.\n *\n * Sin dependencias de DOM: recibe el storage y (opcionalmente) el reloj y el\n * generador de uuid por inyección. El binding a `localStorage` vive en `createDefaultStorage`.\n */\n\n/** Store clave/valor mínimo (espejo conceptual del `KeyValueStorage` de KMP). */\nexport interface KeyValueStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/** Impl en memoria — usada en tests y como fallback sin `localStorage`. */\nexport class InMemoryStorage implements KeyValueStorage {\n private map = new Map<string, string>();\n getItem(key: string): string | null {\n return this.map.has(key) ? (this.map.get(key) as string) : null;\n }\n setItem(key: string, value: string): void {\n this.map.set(key, value);\n }\n removeItem(key: string): void {\n this.map.delete(key);\n }\n}\n\n/** Storage por defecto en Web: `localStorage` si está disponible, si no in-memory. */\nexport function createDefaultStorage(): KeyValueStorage {\n try {\n if (typeof window !== 'undefined' && window.localStorage) {\n const ls = window.localStorage;\n const probe = '__deepdots_probe__';\n ls.setItem(probe, '1');\n ls.removeItem(probe);\n return {\n getItem: (k) => ls.getItem(k),\n setItem: (k, v) => ls.setItem(k, v),\n removeItem: (k) => ls.removeItem(k),\n };\n }\n } catch {\n // cae a in-memory\n }\n return new InMemoryStorage();\n}\n\n/** Claves de storage — namespace `deepdots.*`. Solo identidad (la sesión no se persiste). */\nexport const STORAGE_KEYS = {\n userId: 'deepdots.user_id',\n firstSeen: 'deepdots.user.first_seen',\n contactAttributes: 'deepdots.contact.attributes',\n} as const;\n\n/** Par clave/valor con el formato `NativeAnswer` que espera `@magicfeedback/native`. */\nexport interface IdentityAnswer {\n key: string;\n value: string[];\n}\n\n/**\n * Construye el `profile` y el `metadata` de identidad para inyectar en el survey\n * (`magicfeedback.form(appId, publicKey, profile, metadata)`), según el contrato §5.\n * `session_id` solo se incluye si el backend ya lo proveyó.\n */\nexport function buildSurveyIdentity(\n userId: string | null,\n sessionId: string | null,\n miniService: string | null = null,\n analyticsFeedbackSessionId: string | null = null,\n): { profile: IdentityAnswer[]; metadata: IdentityAnswer[] } {\n const profile: IdentityAnswer[] = userId ? [{ key: 'external-user-id', value: [userId] }] : [];\n const metadata: IdentityAnswer[] = [];\n if (sessionId) metadata.push({ key: 'session_id', value: [sessionId] });\n if (userId) metadata.push({ key: 'user_id', value: [userId] });\n if (miniService) metadata.push({ key: 'mini_service', value: [miniService] });\n if (analyticsFeedbackSessionId) metadata.push({ key: 'deepdots_analytics_feedback_session_id', value: [analyticsFeedbackSessionId] });\n return { profile, metadata };\n}\n\nexport interface TrackingManagerOptions {\n storage: KeyValueStorage;\n /** Id provisto por el cliente; si existe se usa y NO se persiste. */\n clientUserId?: string;\n /** Reloj inyectable (default `Date.now`). */\n now?: () => number;\n /** Generador de uuid inyectable (default uuid v4). */\n uuid?: () => string;\n /** Estado inicial del tracking (default `true`). */\n enabled?: boolean;\n}\n\nfunction defaultUuid(): string {\n const g = (typeof globalThis !== 'undefined' ? globalThis : {}) as {\n crypto?: { randomUUID?: () => string; getRandomValues?: (a: Uint8Array) => Uint8Array };\n };\n if (g.crypto?.randomUUID) return g.crypto.randomUUID();\n const bytes = new Uint8Array(16);\n if (g.crypto?.getRandomValues) {\n g.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0'));\n return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex\n .slice(6, 8)\n .join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;\n}\n\nexport class TrackingManager {\n private storage: KeyValueStorage;\n private clientUserId?: string;\n private now: () => number;\n private uuid: () => string;\n private enabled: boolean;\n\n /** session_id cacheado, provisto por el backend (no se genera ni se persiste). */\n private sessionId: string | null = null;\n private userWasJustCreated = false;\n\n constructor(options: TrackingManagerOptions) {\n this.storage = options.storage;\n this.clientUserId = options.clientUserId;\n this.now = options.now ?? (() => Date.now());\n this.uuid = options.uuid ?? defaultUuid;\n this.enabled = options.enabled ?? true;\n }\n\n isTrackingEnabled(): boolean {\n return this.enabled;\n }\n\n setTrackingEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /** Resuelve el user_id según la regla: cliente > persistido > generar+persistir. */\n getUserId(): string | null {\n if (!this.enabled) return null;\n if (this.clientUserId) return this.clientUserId;\n\n const persisted = this.storage.getItem(STORAGE_KEYS.userId);\n if (persisted) return persisted;\n\n const id = this.uuid();\n this.storage.setItem(STORAGE_KEYS.userId, id);\n this.storage.setItem(STORAGE_KEYS.firstSeen, String(this.now()));\n this.userWasJustCreated = true;\n return id;\n }\n\n /** `true` solo en la primera sesión tras crear el user_id propio del SDK. */\n isNewUser(): boolean {\n return this.userWasJustCreated;\n }\n\n /** session_id actual provisto por el backend, o null si aún no llegó. */\n getSessionId(): string | null {\n if (!this.enabled) return null;\n return this.sessionId;\n }\n\n /** Cachea el session_id devuelto por el backend (respuesta de POST /sdk/popups). */\n setSessionId(sessionId: string | null): void {\n if (!this.enabled) return;\n this.sessionId = sessionId;\n }\n}\n","/**\n * Helpers puros (sin DOM) para la fuente personalizable del popup/survey.\n * Espejo exacto en KMP: `ui/Font.kt`. Cualquier cambio aquí se replica allí.\n */\n\nconst FORMAT_BY_EXT: Record<string, string> = {\n woff2: 'woff2',\n woff: 'woff',\n ttf: 'truetype',\n otf: 'opentype',\n};\n\n/** Deriva el `format()` del `@font-face` a partir de la extensión de la URL. */\nexport function fontFormatFromUrl(url: string): string | undefined {\n const clean = url.split('?')[0].split('#')[0];\n const ext = clean.slice(clean.lastIndexOf('.') + 1).toLowerCase();\n return FORMAT_BY_EXT[ext];\n}\n\n// family/url provienen de la API y se interpolan en CSS y en el <style> del HTML del WebView.\n// Saneamos para evitar romper el string/rule (CSS injection) o cerrar </style> (script injection).\nfunction sanitizeFamily(family: string): string {\n return family.replace(/[^A-Za-z0-9 ._-]/g, '').trim();\n}\n\nfunction isSafeFontUrl(url: string): boolean {\n // Solo http(s)/data y sin caracteres que rompan url(\"...\") o el <style>:\n // comillas, <>, backslash, y cualquier whitespace o carácter de control.\n if (!/^(https?:|data:)/i.test(url)) return false;\n // eslint-disable-next-line no-control-regex\n return !/[\\x00-\\x20\"<>\\\\]/.test(url);\n}\n\n/** Valor a aplicar en `font-family`: nombre custom + fallback de sistema. */\nexport function buildFontFamilyValue(family: string): string {\n const safe = sanitizeFamily(family);\n return `\"${safe}\", -apple-system, system-ui, sans-serif`;\n}\n\n/** CSS del `@font-face`. Devuelve \"\" si no hay url (o no es segura). */\nexport function buildFontFaceCss(family: string, url?: string): string {\n if (!url || !isSafeFontUrl(url)) return '';\n const safe = sanitizeFamily(family);\n const fmt = fontFormatFromUrl(url);\n const src = fmt ? `url(\"${url}\") format(\"${fmt}\")` : `url(\"${url}\")`;\n return `@font-face{font-family:\"${safe}\";src:${src};font-display:swap;}`;\n}\n","import type { PopupActions } from '../types';\n\n/**\n * Textos que pinta el SDK alrededor del survey (botones del footer, barra de progreso,\n * aria-labels y avisos de error). NO son textos del survey: las preguntas, placeholders y\n * opciones las localiza `@magicfeedback/native` con `formData.lang[0]`, el idioma configurado\n * en la integración. Este módulo existe para que el chrome del popup hable ese mismo idioma\n * en lugar de quedarse siempre en inglés.\n *\n * Espejo del `i18n/DefaultLabels.kt` del SDK nativo (KMP): mismos locales y mismas reglas de\n * resolución, para que las tres rutas (popup DOM web, WebView de RN y Compose) digan lo mismo.\n */\n\n/** Locales con traducción propia. Cualquier otro idioma cae a `en`. */\nexport type PopupLocale =\n | 'en' | 'es' | 'da' | 'no' | 'sv' | 'fi' | 'de' | 'fr' | 'pt' | 'ar' | 'bn' | 'zh-CN';\n\nexport interface PopupLabels {\n /** Acción principal: enviar la respuesta. */\n accept: string;\n /** Acción de descarte. */\n decline: string;\n /** Botón de la pantalla de bienvenida. */\n start: string;\n /** Botón de la pantalla final. */\n complete: string;\n /** Volver a la pregunta anterior. */\n back: string;\n /** Sustantivo del contador de progreso (\"Question\" en `Question 2 of 5`). */\n question: string;\n /** Separador del contador de progreso (\"of\" en `Question 2 of 5`). */\n of: string;\n /** Distintivo de las preguntas de seguimiento dinámicas. */\n followUp: string;\n /** aria-label del botón de cerrar. */\n closeAria: string;\n /** aria-label del spinner mientras carga el survey. */\n loadingAria: string;\n /** Aviso al intentar avanzar con una pregunta obligatoria sin responder. */\n errorRequired: string;\n /** Aviso cuando el envío falla. */\n errorSubmit: string;\n}\n\nconst EN: PopupLabels = {\n accept: 'Send',\n decline: 'Cancel',\n start: 'Start survey',\n complete: 'Complete survey',\n back: 'Back',\n question: 'Question',\n of: 'of',\n followUp: 'Follow-up',\n closeAria: 'Close popup',\n loadingAria: 'Loading survey',\n errorRequired: 'Please answer the required question to continue.',\n errorSubmit: 'An error occurred while submitting. Please try again or close the popup.',\n};\n\nconst ES: PopupLabels = {\n accept: 'Enviar',\n decline: 'Cancelar',\n start: 'Empezar encuesta',\n complete: 'Completar encuesta',\n back: 'Atrás',\n question: 'Pregunta',\n of: 'de',\n followUp: 'Seguimiento',\n closeAria: 'Cerrar ventana emergente',\n loadingAria: 'Cargando encuesta',\n errorRequired: 'Responde la pregunta obligatoria para continuar.',\n errorSubmit: 'Se produjo un error al enviar. Inténtalo de nuevo o cierra la ventana.',\n};\n\nconst DA: PopupLabels = {\n accept: 'Send',\n decline: 'Annuller',\n start: 'Start undersøgelse',\n complete: 'Afslut undersøgelse',\n back: 'Tilbage',\n question: 'Spørgsmål',\n of: 'af',\n followUp: 'Opfølgning',\n closeAria: 'Luk pop op',\n loadingAria: 'Indlæser undersøgelse',\n errorRequired: 'Besvar venligst det obligatoriske spørgsmål for at fortsætte.',\n errorSubmit: 'Der opstod en fejl under afsendelsen. Prøv igen, eller luk pop op-vinduet.',\n};\n\nconst NO: PopupLabels = {\n accept: 'Send',\n decline: 'Avbryt',\n start: 'Start undersøkelse',\n complete: 'Fullfør undersøkelse',\n back: 'Tilbake',\n question: 'Spørsmål',\n of: 'av',\n followUp: 'Oppfølging',\n closeAria: 'Lukk popup',\n loadingAria: 'Laster undersøkelse',\n errorRequired: 'Svar på det obligatoriske spørsmålet for å fortsette.',\n errorSubmit: 'Det oppstod en feil under innsendingen. Prøv igjen, eller lukk popupen.',\n};\n\nconst SV: PopupLabels = {\n accept: 'Skicka',\n decline: 'Avbryt',\n start: 'Starta undersökning',\n complete: 'Slutför undersökning',\n back: 'Tillbaka',\n question: 'Fråga',\n of: 'av',\n followUp: 'Uppföljning',\n closeAria: 'Stäng popup',\n loadingAria: 'Laddar undersökning',\n errorRequired: 'Svara på den obligatoriska frågan för att fortsätta.',\n errorSubmit: 'Ett fel uppstod vid inskickningen. Försök igen eller stäng popupen.',\n};\n\nconst FI: PopupLabels = {\n accept: 'Lähetä',\n decline: 'Peruuta',\n start: 'Aloita kysely',\n complete: 'Viimeistele kysely',\n back: 'Takaisin',\n question: 'Kysymys',\n of: '/',\n followUp: 'Jatkokysymys',\n closeAria: 'Sulje ponnahdusikkuna',\n loadingAria: 'Ladataan kyselyä',\n errorRequired: 'Vastaa pakolliseen kysymykseen jatkaaksesi.',\n errorSubmit: 'Lähetyksessä tapahtui virhe. Yritä uudelleen tai sulje ponnahdusikkuna.',\n};\n\nconst DE: PopupLabels = {\n accept: 'Senden',\n decline: 'Abbrechen',\n start: 'Umfrage starten',\n complete: 'Umfrage abschließen',\n back: 'Zurück',\n question: 'Frage',\n of: 'von',\n followUp: 'Folgefrage',\n closeAria: 'Popup schließen',\n loadingAria: 'Umfrage wird geladen',\n errorRequired: 'Bitte beantworte die Pflichtfrage, um fortzufahren.',\n errorSubmit: 'Beim Senden ist ein Fehler aufgetreten. Versuche es erneut oder schließe das Popup.',\n};\n\nconst FR: PopupLabels = {\n accept: 'Envoyer',\n decline: 'Annuler',\n start: \"Commencer l'enquête\",\n complete: \"Terminer l'enquête\",\n back: 'Retour',\n question: 'Question',\n of: 'sur',\n followUp: 'Question complémentaire',\n closeAria: 'Fermer la fenêtre',\n loadingAria: \"Chargement de l'enquête\",\n errorRequired: 'Veuillez répondre à la question obligatoire pour continuer.',\n errorSubmit: \"Une erreur s'est produite lors de l'envoi. Réessayez ou fermez la fenêtre.\",\n};\n\nconst PT: PopupLabels = {\n accept: 'Enviar',\n decline: 'Cancelar',\n start: 'Iniciar questionário',\n complete: 'Concluir questionário',\n back: 'Voltar',\n question: 'Pergunta',\n of: 'de',\n followUp: 'Seguimento',\n closeAria: 'Fechar janela',\n loadingAria: 'A carregar questionário',\n errorRequired: 'Responda à pergunta obrigatória para continuar.',\n errorSubmit: 'Ocorreu um erro ao enviar. Tente novamente ou feche a janela.',\n};\n\n// ⚠️ Árabe: el texto es RTL pero el chrome del popup sigue maquetado LTR (ver nota en el\n// README). Traducir ya es mejor que dejarlo en inglés; la dirección es una decisión aparte.\nconst AR: PopupLabels = {\n accept: 'إرسال',\n decline: 'إلغاء',\n start: 'ابدأ الاستبيان',\n complete: 'إنهاء الاستبيان',\n back: 'رجوع',\n question: 'سؤال',\n of: 'من',\n followUp: 'سؤال متابعة',\n closeAria: 'إغلاق النافذة المنبثقة',\n loadingAria: 'جارٍ تحميل الاستبيان',\n errorRequired: 'يرجى الإجابة على السؤال المطلوب للمتابعة.',\n errorSubmit: 'حدث خطأ أثناء الإرسال. يرجى المحاولة مرة أخرى أو إغلاق النافذة.',\n};\n\nconst BN: PopupLabels = {\n accept: 'পাঠান',\n decline: 'বাতিল',\n start: 'জরিপ শুরু করুন',\n complete: 'জরিপ সম্পূর্ণ করুন',\n back: 'পিছনে',\n question: 'প্রশ্ন',\n of: '/',\n followUp: 'ফলো-আপ',\n closeAria: 'পপআপ বন্ধ করুন',\n loadingAria: 'জরিপ লোড হচ্ছে',\n errorRequired: 'চালিয়ে যেতে আবশ্যক প্রশ্নের উত্তর দিন।',\n errorSubmit: 'জমা দেওয়ার সময় একটি ত্রুটি ঘটেছে। আবার চেষ্টা করুন বা পপআপ বন্ধ করুন।',\n};\n\nconst ZH_CN: PopupLabels = {\n accept: '发送',\n decline: '取消',\n start: '开始问卷',\n complete: '完成问卷',\n back: '返回',\n question: '问题',\n of: '/',\n followUp: '追问',\n closeAria: '关闭弹窗',\n loadingAria: '正在加载问卷',\n errorRequired: '请回答必答题后继续。',\n errorSubmit: '提交时出错。请重试或关闭弹窗。',\n};\n\n/** Tabla completa de traducciones, indexada por locale. */\nexport const LABELS: Record<PopupLocale, PopupLabels> = {\n en: EN,\n es: ES,\n da: DA,\n no: NO,\n sv: SV,\n fi: FI,\n de: DE,\n fr: FR,\n pt: PT,\n ar: AR,\n bn: BN,\n 'zh-CN': ZH_CN,\n};\n\n/** Locales con traducción propia del SDK. */\nexport const SUPPORTED_LANGUAGES: PopupLocale[] = [\n // Los 11 idiomas que la plataforma ofrece para un survey, más zh-CN (que el SDK nativo ya\n // traía). Si la plataforma añade uno nuevo, hay que añadirlo aquí o el chrome saldrá en\n // inglés bajo un survey traducido.\n 'en', 'es', 'da', 'no', 'sv', 'fi', 'de', 'fr', 'pt', 'ar', 'bn', 'zh-CN',\n];\n\n/**\n * Normaliza un tag BCP-47 al locale con traducción. Reglas (idénticas a KMP):\n * - el prefijo de idioma manda, sin importar región ni caja (`da`, `da-DK`, `da_DK` → `da`);\n * - las variantes noruegas (`nb`, `nn`) colapsan en `no`;\n * - las variantes chinas (`zh`, `zh-Hans`, `zh-CN`, `zh-TW`) colapsan en `zh-CN`;\n * - `pt-BR` y `pt-PT` comparten juego, igual que `de-AT`/`de-CH` o `fr-CA`;\n * - cualquier otra cosa, incluido un valor no-string del backend, cae a `en`.\n */\nexport function resolveLocale(lang?: string | null): PopupLocale {\n if (typeof lang !== 'string') return 'en';\n const normalized = lang.trim().toLowerCase().replace(/_/g, '-');\n if (!normalized) return 'en';\n if (normalized === 'zh' || normalized.startsWith('zh-')) return 'zh-CN';\n const primary = normalized.split('-')[0];\n switch (primary) {\n case 'en': return 'en';\n case 'es': return 'es';\n case 'da': return 'da';\n case 'no':\n case 'nb':\n case 'nn': return 'no';\n case 'sv': return 'sv';\n case 'fi': return 'fi';\n case 'de': return 'de';\n case 'fr': return 'fr';\n case 'pt': return 'pt';\n case 'ar': return 'ar';\n case 'bn': return 'bn';\n default: return 'en';\n }\n}\n\n/**\n * Locales que se escriben de derecha a izquierda. Espejo de `RTL_LANGUAGES` de\n * `@magicfeedback/native` (2.2.22), que estampa `dir=\"rtl\"` en el contenedor del survey para\n * esos idiomas: el chrome del popup tiene que voltear con él o media tarjeta queda mirando a\n * un lado y media al otro.\n */\nexport const RTL_LOCALES: PopupLocale[] = ['ar'];\n\n/** Si el chrome debe pintarse de derecha a izquierda para `lang`. */\nexport function isRtlLanguage(lang?: string | null): boolean {\n return RTL_LOCALES.includes(resolveLocale(lang));\n}\n\n/** Valor del atributo `dir` del chrome para `lang`. Siempre explícito: nunca se hereda del host. */\nexport function directionFor(lang?: string | null): 'rtl' | 'ltr' {\n return isRtlLanguage(lang) ? 'rtl' : 'ltr';\n}\n\n/** Textos del chrome del popup para `lang`. Cae a inglés si el idioma no está soportado. */\nexport function getLabels(lang?: string | null): PopupLabels {\n return LABELS[resolveLocale(lang)];\n}\n\n/** Etiquetas de los cuatro botones del footer, ya resueltas. */\nexport interface ResolvedActionLabels {\n accept: string;\n decline: string;\n start: string;\n complete: string;\n back: string;\n}\n\n/**\n * Resuelve las etiquetas de los botones con la prioridad acordada: lo que configure la\n * plataforma por popup (`actions.*.label`) gana siempre; si falta o viene en blanco, se usa\n * la traducción del SDK para `lang`.\n */\nexport function resolveActionLabels(actions: PopupActions | undefined, lang?: string | null): ResolvedActionLabels {\n const labels = getLabels(lang);\n const pick = (value: string | undefined, fallback: string): string => {\n const trimmed = typeof value === 'string' ? value.trim() : '';\n return trimmed ? value as string : fallback;\n };\n return {\n accept: pick(actions?.accept?.label, labels.accept),\n decline: pick(actions?.decline?.label, labels.decline),\n start: pick(actions?.start?.label, labels.start),\n complete: pick(actions?.complete?.label, labels.complete),\n back: pick(actions?.back?.label, labels.back),\n };\n}\n\n/**\n * Fuente JavaScript del resolutor de locale, para incrustar en el HTML del WebView (React\n * Native): allí el idioma del survey solo se conoce ya dentro del WebView, así que la\n * normalización tiene que viajar con el HTML. Es un espejo literal de `resolveLocale`, y\n * `labels.test.ts` compara las dos implementaciones contra la misma batería de tags para que\n * no se separen en silencio.\n */\nexport const RESOLVE_LOCALE_JS = `function ddResolveLocale(lang){\n if(typeof lang!=='string') return 'en';\n var n=lang.trim().toLowerCase().replace(/_/g,'-');\n if(!n) return 'en';\n if(n==='zh'||n.indexOf('zh-')===0) return 'zh-CN';\n var p=n.split('-')[0];\n if(p==='en') return 'en';\n if(p==='es') return 'es';\n if(p==='da') return 'da';\n if(p==='no'||p==='nb'||p==='nn') return 'no';\n if(p==='sv') return 'sv';\n if(p==='fi') return 'fi';\n if(p==='de') return 'de';\n if(p==='fr') return 'fr';\n if(p==='pt') return 'pt';\n if(p==='ar') return 'ar';\n if(p==='bn') return 'bn';\n return 'en';\n}`;\n","/**\n * Apertura diferida del popup: el chrome se monta invisible y se enseña cuando el survey ya\n * está pintado, en vez de enseñar un spinner girando mientras se espera.\n *\n * Vive en su propio módulo porque lo comparten las dos rutas de render y `renderPopup` arrastra\n * `@magicfeedback/native`: importar la constante desde allí metería el survey entero en el bundle\n * de React Native. La ruta del WebView (`surveyHtml`) hornea el valor en el HTML y lleva su\n * propio espejo del algoritmo en ES5, porque ese código corre dentro del WebView.\n */\n\n/**\n * Techo de la espera. Pasado este tiempo el popup se abre igualmente (con spinner, el\n * comportamiento de siempre), para que una red mala retrase la apertura pero nunca la impida.\n *\n * 1200 ms sale de medir contra producción: el `GET .../info` del survey tarda unos 250 ms de\n * mediana desde Europa, pero con el servicio frío se han visto 775 ms. El techo tiene que quedar\n * por encima de esa cola: si cayera justo encima, el popup se abriría con el spinner para\n * quitarlo 30 ms después, que es peor que cualquiera de las dos opciones.\n */\nexport const REVEAL_TIMEOUT_MS = 1200;\n\n/**\n * Llama a `reveal` cuando no queden imágenes por llegar dentro de `root`, o ya mismo si no hay\n * ninguna pendiente. Las imágenes (los emojis del rating son SVG, y el logo del survey) llegan\n * DESPUÉS de que el formulario esté montado: sin esperarlas, el popup aparece y se rellena\n * delante del usuario. Quien llama debe mantener su propio techo de tiempo para las que no\n * lleguen nunca.\n */\nexport function revealWhenPainted(root: ParentNode, reveal: () => void): void {\n const pending = Array.from(root.querySelectorAll('img')).filter((img) => !img.complete);\n if (!pending.length) {\n reveal();\n return;\n }\n let left = pending.length;\n const onSettled = () => {\n left -= 1;\n if (left <= 0) reveal();\n };\n pending.forEach((img) => {\n img.addEventListener('load', onSettled, { once: true });\n img.addEventListener('error', onSettled, { once: true });\n });\n}\n","/**\n * Color de marca dentro del survey.\n *\n * El survey pinta sus controles (chips del rating, bordes, foco, selección) con `--mf-primary`,\n * que `@magicfeedback/native` rellena desde `formData.style.primaryColor`. Las integraciones\n * configuran `buttonPrimaryColor` para el botón, pero muchas dejan `primaryColor` vacío, así que\n * el popup acababa mezclando un botón con el color de la marca y unos controles con el `#5d7bad`\n * por defecto del paquete.\n *\n * Aquí se rellena ese hueco: sin `primaryColor`, se usa `buttonPrimaryColor`. Cuando la\n * integración sí define `primaryColor`, ese manda y el SDK no interviene.\n */\n\n/** Estilo del survey, tal y como llega en `formData.style`. */\nexport interface SurveyPaletteStyle {\n primaryColor?: string;\n buttonPrimaryColor?: string;\n}\n\n/**\n * Solo hex. El estilo viene de la API y se interpola en CSS, así que cualquier otra cosa\n * (`red; background: url(...)`, `var(--x)`, …) se descarta en vez de colarse en la hoja.\n */\nconst HEX_COLOR = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n/** Color que debe usar el survey, o null para dejar el del paquete. */\nexport function resolveSurveyPrimaryColor(style?: SurveyPaletteStyle | null): string | null {\n for (const candidate of [style?.primaryColor, style?.buttonPrimaryColor]) {\n const value = (candidate ?? '').trim();\n if (HEX_COLOR.test(value)) return value;\n }\n return null;\n}\n\n/**\n * Las cinco variables que `applyPrimaryColor` del Surveys SDK escribe en el contenedor. Las\n * derivadas hay que redeclararlas y no solo cambiar `--mf-primary`: un `color-mix` escrito en\n * `:root` ya se resolvió allí contra el color por defecto, y heredaría ese valor ya calculado.\n */\nexport function surveyPrimaryVars(color: string): Array<[string, string]> {\n return [\n ['--mf-primary', color],\n ['--mf-primary-hover', `color-mix(in srgb, ${color} 85%, black)`],\n ['--mf-primary-light', `color-mix(in srgb, ${color} 15%, white)`],\n ['--mf-primary-border', `color-mix(in srgb, ${color} 35%, transparent)`],\n ['--mf-border-focus', color],\n ];\n}\n\n/** Pinta el color de marca del survey en `el`. No-op si no hay color utilizable. */\nexport function applySurveyPrimaryColor(el: HTMLElement, style?: SurveyPaletteStyle | null): void {\n const color = resolveSurveyPrimaryColor(style);\n if (!color) return;\n for (const [name, value] of surveyPrimaryVars(color)) {\n el.style.setProperty(name, value);\n }\n}\n\n/**\n * Espejo ES5 de lo anterior, para el JS que corre DENTRO del WebView (React Native y KMP), que no\n * comparte módulos con el bundle. `surveyPalette.test.ts` evalúa este texto y compara el\n * resultado con el del módulo, para que las dos implementaciones no se separen.\n */\nexport const PRIMARY_COLOR_JS = `\nfunction ddResolveSurveyPrimaryColor(style){\n var hex=/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n var candidates=[style&&style.primaryColor, style&&style.buttonPrimaryColor];\n for(var i=0;i<candidates.length;i++){\n var value=(candidates[i]||'').trim();\n if(hex.test(value)){ return value; }\n }\n return null;\n}\nfunction ddApplySurveyPrimaryColor(el, style){\n var color=ddResolveSurveyPrimaryColor(style);\n if(!color||!el){ return; }\n el.style.setProperty('--mf-primary', color);\n el.style.setProperty('--mf-primary-hover', 'color-mix(in srgb, '+color+' 85%, black)');\n el.style.setProperty('--mf-primary-light', 'color-mix(in srgb, '+color+' 15%, white)');\n el.style.setProperty('--mf-primary-border', 'color-mix(in srgb, '+color+' 35%, transparent)');\n el.style.setProperty('--mf-border-focus', color);\n}`;\n","/**\n * Bloqueo del survey mientras carga.\n *\n * El spinner de las transiciones entre páginas no tapa nada (es un círculo de 28px en posición\n * absoluta) y `setLoading(true)` solo ocultaba el footer y deshabilitaba SUS botones, así que el\n * contenido del survey seguía respondiendo: el usuario podía cambiar de opción mientras se\n * enviaba la página. Ese cambio ya no viajaba con ella, porque la respuesta se había mandado, de\n * modo que creía haber corregido su respuesta y no era así.\n *\n * Se bloquea el contenedor entero en vez de deshabilitar campo por campo: el survey lo pinta\n * `@magicfeedback/native` y su marcado cambia con cada tipo de pregunta.\n */\n\n/** Marca (o suelta) el contenedor del survey como ocupado. No-op sin elemento. */\nexport function setSurveyBusy(el: HTMLElement | null | undefined, busy: boolean): void {\n if (!el) return;\n // Cadena vacía al soltar, no 'auto': así el CSS del host sigue mandando sobre el elemento.\n el.style.pointerEvents = busy ? 'none' : '';\n el.setAttribute('aria-busy', busy ? 'true' : 'false');\n // `inert` lo saca además del tab order y del lector de pantalla. No está en WebViews\n // antiguos, donde `pointer-events` ya cubre el caso del vídeo del cliente.\n if ('inert' in el) {\n (el as HTMLElement & { inert?: boolean }).inert = busy;\n }\n}\n\n/**\n * Espejo ES5 para el JS que corre DENTRO del WebView (React Native y KMP), que no comparte\n * módulos con el bundle. `busy.test.ts` lo evalúa y compara su efecto con el del módulo, para\n * que las dos implementaciones no se separen.\n */\nexport const BUSY_JS = `\nfunction ddSetSurveyBusy(el, busy){\n if(!el){ return; }\n el.style.pointerEvents = busy ? 'none' : '';\n el.setAttribute('aria-busy', busy ? 'true' : 'false');\n if('inert' in el){ el.inert = busy; }\n}`;\n","/**\n * NO EDITAR A MANO — generado por scripts/vendor-survey-css.mjs\n *\n * Copia de @magicfeedback/native@2.2.31 → dist/styles/magicfeedback-default.css\n * (la hoja canónica del paquete, la que apunta su campo \"style\"), con estos cambios locales:\n *\n * 1. Sin el @import de Google Fonts (Nunito): no queremos una petición externa desde la\n * página del host, y la tipografía la decide el popup.\n * 2. .magicfeedback-container hereda la fuente del popup (var(--deepdots-font, inherit)).\n * 3. Los selectores desnudos (h2, p, input, textarea, select, ::placeholder, .error) van\n * acotados a .deepdots-popup: este CSS se inyecta en el <head> del host y sin acotar\n * repintaría su web.\n * 4. El grupo .magicfeedback-radio se cualifica como div.magicfeedback-radio (la clase acaba\n * también en el <input> del boolean).\n * 5. Todo :hover va dentro de @media (hover: hover): en táctil WebKit deja el hover pegado\n * en el último punto tocado y la opción de la página siguiente parecía seleccionada.\n * 6. Al final, un bloque de reglas propias del popup (fuente en los controles y padding del\n * radio nativo).\n *\n * Las variables --mf-* se dejan en :root a propósito: el modal del priority-list se portea a\n * <body>, fuera de .deepdots-popup, y ahí las necesita.\n *\n * Para subir de versión: npm i @magicfeedback/native@<v> && node scripts/vendor-survey-css.mjs\n */\n\n/**\n * @magicfeedback/native - Default Styles\n * Clean, minimal, and elegant survey rendering\n */\n\n/* @import de Google Fonts (Nunito) eliminado al vendorizar: la fuente la pone el popup. */\n\n/* ========================================\n CSS VARIABLES - Design SYSTEM\n ======================================== */\n:root {\n /* Colors - Minimal Pastel */\n /* --mf-primary is the one source of truth for brand/accent color —\n every token below derives from it via color-mix() (or references\n it directly) so changing this single value, here or via a\n per-integration override, keeps hover/light/border states in\n sync automatically instead of going stale independently. Scoped\n to genuinely interactive/accent surfaces (buttons, selected\n chips, focus rings) — general body text stays on its own neutral\n scale below, not tied to the brand color. */\n --mf-primary: #5d7bad;\n --mf-primary-hover: color-mix(in srgb, var(--mf-primary) 85%, black);\n --mf-primary-light: color-mix(in srgb, var(--mf-primary) 15%, white);\n /* a translucent tint — mixed with transparent (opacity), not white,\n so it reads as a soft shadow of the brand color rather than a\n pastel of it. */\n --mf-primary-border: color-mix(in srgb, var(--mf-primary) 35%, transparent);\n\n --mf-text-primary: #1E293B;\n --mf-text-secondary: #475569;\n --mf-text-muted: #94A3B8;\n\n --mf-bg-primary: #F8FAFC;\n --mf-bg-secondary: #FFFFFF;\n --mf-bg-hover: #EEF2F6;\n\n --mf-border: #E2E8F0;\n --mf-border-focus: var(--mf-primary);\n\n --mf-success: #16A34A;\n --mf-error: #EF4444;\n --mf-warning: #F59E0B;\n\n --mf-surface: #FFFFFF;\n --mf-surface-alt: #F1F5F9;\n --mf-accent: #38BDF8;\n\n /* Spacing */\n --mf-space-xs: 0.25rem;\n --mf-space-sm: 0.5rem;\n --mf-space-md: 0.75rem;\n --mf-space-lg: 1rem;\n --mf-space-xl: 1.5rem;\n\n /* Border Radius */\n --mf-radius-sm: 0.5rem;\n --mf-radius-md: 0.75rem;\n --mf-radius-lg: 1.25rem;\n --mf-radius-full: 9999px;\n\n /* Shadows */\n --mf-shadow-sm: 0 1px 2px 0 rgba(15, 23, 42, 0.06);\n --mf-shadow-md: 0 8px 20px rgba(15, 23, 42, 0.08);\n --mf-shadow-lg: 0 16px 30px rgba(15, 23, 42, 0.12);\n --mf-shadow-focus: 0 0 0 3px rgba(30, 41, 59, 0.15);\n --mf-shadow-card: 0 18px 40px rgba(15, 23, 42, 0.08);\n\n /* Typography */\n --mf-font-sans: \"Nunito\", \"Quicksand\", \"Avenir Next\", \"Trebuchet MS\", sans-serif;\n --mf-font-size-sm: 0.875rem;\n --mf-font-size-base: 1rem;\n --mf-font-size-lg: 1.125rem;\n --mf-font-size-xl: 1.25rem;\n --mf-line-height: 1.6;\n\n /* Font Weights */\n --mf-font-weight-normal: 400;\n --mf-font-weight-medium: 500;\n --mf-font-weight-semibold: 600;\n --mf-font-weight-bold: 700;\n --mf-font-weight-extrabold: 800;\n\n /* Transitions */\n --mf-transition: all 0.2s ease;\n --mf-transition-fast: all 0.15s ease;\n}\n\n/* ========================================\n ANIMATIONS\n ======================================== */\n@keyframes mf-fade-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes mf-slide-in {\n from {\n opacity: 0;\n transform: translateX(-16px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n@keyframes mf-scale-in {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes mf-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.8;\n }\n}\n\n/* ========================================\n BASE CONTAINER\n ======================================== */\n.magicfeedback-container {\n /* Local: hereda la fuente del popup; --deepdots-font viene de PopupStyle.font. */\n font-family: var(--deepdots-font, inherit);\n color: var(--mf-text-primary);\n line-height: var(--mf-line-height);\n animation: mf-fade-in 0.4s ease-out;\n border-radius: var(--mf-radius-lg);\n}\n\n/* ========================================\n MESSAGES\n ======================================== */\n.magicfeedback-success-message,\n.magicfeedback-info-message,\n.magicfeedback-start-message {\n padding: var(--mf-space-lg);\n margin: var(--mf-space-lg) 0;\n animation: mf-scale-in 0.3s ease-out;\n}\n\n/* .magicfeedback-info-message/-start-message are set via innerHTML (see\n renderInfoPage.ts / questions.service.ts's renderStartMessage) — whatever\n rich text a survey author writes lands here as real HTML, not just the\n <p>/<img> pair this used to style. Covering the rest of what a rich-text\n editor commonly outputs (headings, lists, links, emphasis, quotes, code,\n rules) keeps that content on the same type scale and palette as the rest\n of the widget instead of falling back to unstyled browser defaults. */\n.magicfeedback-success-message > :first-child,\n.magicfeedback-info-message > :first-child,\n.magicfeedback-start-message > :first-child {\n margin-top: 0;\n}\n\n.magicfeedback-success-message > :last-child,\n.magicfeedback-info-message > :last-child,\n.magicfeedback-start-message > :last-child {\n margin-bottom: 0;\n}\n\n.magicfeedback-success-message p,\n.magicfeedback-info-message p,\n.magicfeedback-start-message p {\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-secondary);\n margin: 0 0 var(--mf-space-md);\n line-height: var(--mf-line-height);\n}\n\n.magicfeedback-info-message h1,\n.magicfeedback-info-message h2,\n.magicfeedback-info-message h3,\n.magicfeedback-info-message h4,\n.magicfeedback-info-message h5,\n.magicfeedback-info-message h6,\n.magicfeedback-start-message h1,\n.magicfeedback-start-message h2,\n.magicfeedback-start-message h3,\n.magicfeedback-start-message h4,\n.magicfeedback-start-message h5,\n.magicfeedback-start-message h6 {\n color: var(--mf-text-primary);\n font-weight: var(--mf-font-weight-bold);\n line-height: 1.3;\n margin: var(--mf-space-lg) 0 var(--mf-space-sm);\n text-align: start;\n}\n\n.magicfeedback-info-message h1, .magicfeedback-start-message h1 { font-size: var(--mf-font-size-xl); }\n.magicfeedback-info-message h2, .magicfeedback-start-message h2 { font-size: var(--mf-font-size-lg); }\n.magicfeedback-info-message h3,\n.magicfeedback-info-message h4,\n.magicfeedback-info-message h5,\n.magicfeedback-info-message h6,\n.magicfeedback-start-message h3,\n.magicfeedback-start-message h4,\n.magicfeedback-start-message h5,\n.magicfeedback-start-message h6 { font-size: var(--mf-font-size-base); }\n\n.magicfeedback-info-message strong,\n.magicfeedback-info-message b,\n.magicfeedback-start-message strong,\n.magicfeedback-start-message b {\n font-weight: var(--mf-font-weight-bold);\n color: var(--mf-text-primary);\n}\n\n.magicfeedback-info-message a,\n.magicfeedback-start-message a {\n color: var(--mf-primary);\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n\n@media (hover: hover) {\n .magicfeedback-info-message a:hover,\n .magicfeedback-start-message a:hover {\n color: var(--mf-primary-hover);\n }\n}\n\n.magicfeedback-info-message ul,\n.magicfeedback-info-message ol,\n.magicfeedback-start-message ul,\n.magicfeedback-start-message ol {\n margin: 0 0 var(--mf-space-md);\n padding-inline-start: var(--mf-space-xl);\n color: var(--mf-text-secondary);\n line-height: var(--mf-line-height);\n}\n\n.magicfeedback-info-message li + li,\n.magicfeedback-start-message li + li {\n margin-top: var(--mf-space-xs);\n}\n\n.magicfeedback-info-message blockquote,\n.magicfeedback-start-message blockquote {\n margin: 0 0 var(--mf-space-md);\n padding: var(--mf-space-sm) var(--mf-space-md);\n border-inline-start: 3px solid var(--mf-primary-border);\n color: var(--mf-text-secondary);\n font-style: italic;\n}\n\n.magicfeedback-info-message code,\n.magicfeedback-start-message code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 0.9em;\n background: var(--mf-bg-hover);\n padding: 0.15em 0.4em;\n border-radius: var(--mf-radius-sm);\n}\n\n.magicfeedback-info-message pre,\n.magicfeedback-start-message pre {\n margin: 0 0 var(--mf-space-md);\n padding: var(--mf-space-md);\n background: var(--mf-bg-hover);\n border-radius: var(--mf-radius-md);\n overflow-x: auto;\n}\n\n.magicfeedback-info-message pre code,\n.magicfeedback-start-message pre code {\n background: none;\n padding: 0;\n}\n\n.magicfeedback-info-message hr,\n.magicfeedback-start-message hr {\n border: none;\n border-top: 1px solid var(--mf-border);\n margin: var(--mf-space-lg) 0;\n}\n\n.magicfeedback-success-message img,\n.magicfeedback-info-message img,\n.magicfeedback-start-message img {\n max-width: 100%;\n height: auto;\n margin: var(--mf-space-lg) auto;\n display: block;\n border-radius: var(--mf-radius-md);\n}\n\n.magicfeedback-image {\n max-height: 400px;\n height: auto;\n max-width: 100%;\n margin: var(--mf-space-lg) auto;\n display: block;\n border-radius: var(--mf-radius-md);\n}\n\n/* ========================================\n FORM STRUCTURE\n ======================================== */\n.magicfeedback-form {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-lg);\n padding: clamp(16px, 2.5vw, 28px);\n}\n\n.magicfeedback-questions {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-xl);\n width: 100%;\n}\n\n.magicfeedback-div {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-sm);\n animation: mf-slide-in 0.3s ease-out;\n background: var(--mf-bg-secondary);\n border-radius: var(--mf-radius-md);\n}\n\n/* ========================================\n LABELS & TYPOGRAPHY\n ======================================== */\n.magicfeedback-label {\n font-size: var(--mf-font-size-lg);\n font-weight: var(--mf-font-weight-bold);\n color: var(--mf-text-primary);\n line-height: var(--mf-line-height);\n text-align: start;\n letter-spacing: -0.01em;\n}\n\n.magicfeedback-sublabel {\n font-size: var(--mf-font-size-base);\n font-weight: var(--mf-font-weight-normal);\n color: var(--mf-text-secondary);\n margin-bottom: var(--mf-space-sm);\n}\n\n.deepdots-popup h2 {\n font-size: var(--mf-font-size-xl);\n font-weight: 600;\n color: var(--mf-text-primary);\n margin: 0 0 var(--mf-space-xl) 0;\n text-align: center;\n letter-spacing: -0.01em;\n}\n\n.deepdots-popup p {\n margin: 0;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-secondary);\n}\n\n/* ========================================\n FORM INPUTS\n ======================================== */\n.deepdots-popup input[type=\"date\"],\n.deepdots-popup input[type=\"text\"],\n.deepdots-popup input[type=\"number\"],\n.deepdots-popup input[type=\"email\"],\n.deepdots-popup input[type=\"password\"],\n.deepdots-popup textarea,\n.deepdots-popup select {\n padding: 0.75rem 0.9rem;\n font-size: var(--mf-font-size-base);\n font-family: inherit;\n color: var(--mf-text-primary);\n background-color: var(--mf-surface);\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n outline: none;\n transition: var(--mf-transition);\n -webkit-appearance: none;\n appearance: none;\n box-shadow: var(--mf-shadow-sm);\n}\n\n.deepdots-popup textarea {\n min-height: 100px;\n resize: vertical;\n}\n\n.deepdots-popup input::placeholder,\n.deepdots-popup textarea::placeholder {\n color: var(--mf-text-muted);\n}\n\n/* Remove number input spinners */\n.deepdots-popup input::-webkit-outer-spin-button,\n.deepdots-popup input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.deepdots-popup input[type=\"number\"] {\n /* -moz-appearance: textfield; removed for lint compatibility */\n}\n\n/* Custom Select Arrow */\n.deepdots-popup select {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right var(--mf-space-md) center;\n padding-inline-end: var(--mf-space-xl);\n}\n\n/* Error State */\n.deepdots-popup input.error,\n.deepdots-popup select.error,\n.deepdots-popup textarea.error {\n border-color: var(--mf-error);\n}\n\n.magicfeedback-error {\n margin-top: var(--mf-space-xs);\n font-size: var(--mf-font-size-sm);\n color: var(--mf-error);\n}\n\n/* ========================================\n CHECKBOX & RADIO OPTIONS\n ======================================== */\n.magicfeedback-checkbox,\ndiv.magicfeedback-radio {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-sm);\n width: 100%;\n}\n\n.magicfeedback-checkbox-container,\n.magicfeedback-radio-container {\n display: flex;\n align-items: center;\n gap: var(--mf-space-sm);\n padding: var(--mf-space-md);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-checkbox-container:hover,\n .magicfeedback-radio-container:hover {\n background-color: var(--mf-bg-hover);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-checkbox-container:has(input:checked),\n.magicfeedback-radio-container:has(input:checked) {\n background-color: var(--mf-primary-light);\n border-color: var(--mf-primary);\n}\n\n.magicfeedback-checkbox input[type=\"checkbox\"],\n.magicfeedback-radio-container input[type=\"radio\"] {\n width: 18px;\n height: 18px;\n margin: 0;\n cursor: pointer;\n flex-shrink: 0;\n accent-color: var(--mf-primary);\n}\n\n.magicfeedback-checkbox label,\n.magicfeedback-radio-container label {\n font-size: var(--mf-font-size-base);\n text-align: start;\n color: var(--mf-text-primary);\n cursor: pointer;\n margin: 0;\n flex: 1;\n}\n\n/* ========================================\n BOOLEAN OPTIONS\n ======================================== */\n.magicfeedback-boolean-container {\n display: flex;\n gap: var(--mf-space-md);\n width: 100%;\n}\n\n.magicfeedback-boolean-option {\n flex: 1;\n position: relative;\n}\n\n.magicfeedback-boolean-option input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.magicfeedback-boolean-option label {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--mf-space-md);\n font-size: var(--mf-font-size-base);\n font-weight: 500;\n color: var(--mf-text-secondary);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n margin: 0;\n width: -webkit-fill-available;\n height: inherit;\n}\n\n@media (hover: hover) {\n .magicfeedback-boolean-option label:hover {\n background-color: var(--mf-bg-hover);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-boolean-option input[type=\"radio\"]:checked + label {\n color: var(--mf-primary);\n background-color: var(--mf-primary-light);\n border-color: var(--mf-primary);\n font-weight: 600;\n}\n\n/* ========================================\n RATING SYSTEMS\n ======================================== */\n.magicfeedback-rating,\n.magicfeedback-rating-number,\n.magicfeedback-rating-star {\n width: 100%;\n}\n\n.magicfeedback-rating-container,\n.magicfeedback-rating-number-container,\n.magicfeedback-rating-star-container {\n display: flex;\n flex-wrap: wrap;\n gap: var(--mf-space-sm);\n justify-content: center;\n align-items: center;\n padding: var(--mf-space-md) 0;\n list-style: none;\n margin: 0;\n}\n\n/* Caption line below the row — min on one side, max on the other (swapped\n for order: \"rtl\") — same simple, non-responsive pattern as the\n rating-number row caption. Used by rating-star and rating-emoji. */\n.magicfeedback-rating-placeholder {\n display: flex;\n justify-content: space-between;\n gap: var(--mf-space-md);\n width: 100%;\n margin-top: var(--mf-space-sm);\n font-size: var(--mf-font-size-sm);\n font-weight: var(--mf-font-weight-semibold);\n color: var(--mf-text-secondary);\n}\n\n.magicfeedback-rating-placeholder-value {\n overflow-wrap: anywhere;\n}\n\n.magicfeedback-rating-placeholder-value:last-child {\n text-align: end;\n}\n\n/* Rating with Images */\n.magicfeedback-rating-option-label-container input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.magicfeedback-rating-option-label-container img {\n width: 56px;\n height: 56px;\n cursor: pointer;\n transition: var(--mf-transition-fast);\n border-radius: var(--mf-radius-full);\n border: 2px solid transparent;\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-option-label-container img:hover {\n transform: scale(1.1);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-rating-option-label-container input[type=\"radio\"]:checked + img {\n border-color: var(--mf-primary);\n box-shadow: 0 0 0 3px var(--mf-primary-light);\n}\n\n.magicfeedback-rating-option-label-container label {\n display: block;\n text-align: center;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-secondary);\n margin-top: var(--mf-space-xs);\n cursor: pointer;\n}\n\n/* Rating Numbers — each option is its own outlined chip in the survey's\n primary color, with a gap between them. Row and column use the same\n chip; column additionally has room to show each option's own text\n plainly next to its number. */\n\n/* Every size here is width:100%/flex:1 combined with the chip's own\n padding and border. Without border-box, the browser's default\n content-box adds that padding/border on TOP of the declared size, so\n the chip quietly overflows past its flex slot — reading as \"not quite\n full width\" or, worse, pushing into the next chip. */\n.magicfeedback-rating-number-container,\n.magicfeedback-rating-number-option,\n.magicfeedback-rating-number-option-label-container,\n.magicfeedback-rating-number-value {\n box-sizing: border-box;\n}\n\n/* The shared rating-container rule (above, reused by star rating) sets\n align-items:center for its row-of-chips layout — in a stacked column\n that still nudges a 100%-wide chip inward instead of a true 0 offset.\n Force stretch so every chip fills the exact same width, flush on\n both sides. */\n.magicfeedback-rating-number-container-column,\n.magicfeedback-rating-number-container-column-reverse {\n align-items: stretch;\n}\n\n/* Caption line below the row — the single place min/max labels live\n for row mode, shared by mobile and desktop. */\n.magicfeedback-rating-number-scale-labels {\n display: flex;\n justify-content: space-between;\n gap: var(--mf-space-md);\n margin-top: var(--mf-space-sm);\n font-size: var(--mf-font-size-sm);\n font-weight: var(--mf-font-weight-semibold);\n color: var(--mf-text-secondary);\n}\n\n.magicfeedback-rating-number-scale-label {\n overflow-wrap: anywhere;\n}\n\n.magicfeedback-rating-number-scale-label:last-child {\n text-align: end;\n}\n\n/* Column, no per-option text: each row is its own separated chip, so\n the min/max labels bookend the stack from outside it — one line\n above the first chip, one below the last. Margin goes on whichever\n side actually faces the list — the \"before\" label (always the first\n child) gets space below it, the \"after\" one (always right after the\n container, extra option row or not) gets space above it — so both\n sit the same distance from the chips regardless of which one it is. */\n.magicfeedback-rating-number-scale-label-block {\n font-size: var(--mf-font-size-sm);\n font-weight: var(--mf-font-weight-semibold);\n color: var(--mf-text-secondary);\n text-align: center;\n}\n\n.magicfeedback-rating-number-scale-label-block:first-child {\n margin-bottom: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-container + .magicfeedback-rating-number-scale-label-block {\n margin-top: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-option {\n display: flex;\n}\n\n.magicfeedback-rating-number-option-label-container {\n position: relative;\n width: 100%;\n display: flex;\n cursor: pointer;\n}\n\n.magicfeedback-rating-number-option-label-container input[type=\"radio\"] {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.magicfeedback-rating-number-value {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n transition: var(--mf-transition-fast);\n color: var(--mf-text-secondary);\n font-weight: var(--mf-font-weight-semibold);\n}\n\n/* Row: individual outlined chips in the survey's own primary color —\n var(--mf-primary) so whatever brand color a given survey sets carries\n through automatically, no color hardcoded here. */\n.magicfeedback-rating-number-container-row,\n.magicfeedback-rating-number-container-row-reverse {\n flex-wrap: nowrap;\n gap: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-option-row,\n.magicfeedback-rating-number-option-row-reverse {\n flex: 1 1 0;\n min-width: 0;\n}\n\n.magicfeedback-rating-number-option-row .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-value {\n min-height: 56px;\n padding: var(--mf-space-sm);\n border: 2px solid var(--mf-primary-border);\n border-radius: var(--mf-radius-md);\n background: var(--mf-bg-secondary);\n color: var(--mf-primary);\n font-size: var(--mf-font-size-lg);\n font-weight: var(--mf-font-weight-bold);\n}\n\n/* More than 6 options (e.g. a 0-10 NPS scale): still one row, never\n wraps — but scaled down on purpose, so it reads as a compact chip\n rather than the same 56px button stretched thin. */\n.magicfeedback-rating-number-container-row--dense .magicfeedback-rating-number-option-row .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-container-row--dense .magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-value {\n min-height: 36px;\n padding: var(--mf-space-xs);\n border-radius: var(--mf-radius-sm);\n font-size: 0.8rem;\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-number-option-row .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value,\n .magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {\n background: var(--mf-primary-light);\n }\n}\n\n.magicfeedback-rating-number-option-row .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value {\n color: #fff;\n background-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-md);\n}\n\n/* Extra option: always its own row below the scale, and styled like every\n other chip now — \"doesn't apply\" is still not a point on the 1..N\n continuum, but it no longer needs a different border style to say so. */\n.magicfeedback-rating-number-extra-row {\n display: flex;\n justify-content: center;\n margin-top: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-option--extra {\n flex: 0 1 auto;\n}\n\n.magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container {\n width: auto;\n}\n\n.magicfeedback-rating-number-option--extra .magicfeedback-rating-number-value {\n min-height: 36px;\n padding: 0 var(--mf-space-md);\n border: 2px solid var(--mf-primary-border);\n border-radius: var(--mf-radius-md);\n background: var(--mf-bg-secondary);\n color: var(--mf-primary);\n font-size: var(--mf-font-size-sm);\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {\n background: var(--mf-primary-light);\n }\n}\n\n.magicfeedback-rating-number-option--extra .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value {\n color: #fff;\n background-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-md);\n}\n\n/* Column: the same separated chips as row, just stacked — each option is\n its own outlined chip with a gap between them, not one shared box. */\n.magicfeedback-rating-number-container-column,\n.magicfeedback-rating-number-container-column-reverse {\n gap: var(--mf-space-sm);\n}\n\n.magicfeedback-rating-number-option-column,\n.magicfeedback-rating-number-option-column-reverse {\n width: 100%;\n}\n\n.magicfeedback-rating-number-option-column .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-value {\n justify-content: flex-start;\n gap: var(--mf-space-sm);\n min-height: 48px;\n padding: var(--mf-space-sm) var(--mf-space-md);\n border: 2px solid var(--mf-primary-border);\n border-radius: var(--mf-radius-md);\n background: var(--mf-bg-secondary);\n color: var(--mf-primary);\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value,\n .magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-option-label-container:hover .magicfeedback-rating-number-value {\n background: var(--mf-primary-light);\n }\n}\n\n.magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value {\n color: #fff;\n background-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-md);\n}\n\n.magicfeedback-rating-number-value-num {\n font-weight: var(--mf-font-weight-bold);\n}\n\n/* Column with per-option text: a min-width so \"1\"..\"9\" and \"10\" line up,\n with the label text starting at the same x. Skipped entirely when the\n column has no per-option text at all (--bare below) — with nothing to\n align against, the same left-shift bug from row mode would resurface. */\n.magicfeedback-rating-number-container-column:not(.magicfeedback-rating-number-container-column--bare) .magicfeedback-rating-number-value-num,\n.magicfeedback-rating-number-container-column-reverse:not(.magicfeedback-rating-number-container-column--bare) .magicfeedback-rating-number-value-num {\n flex: none;\n min-width: 1.4em;\n}\n\n/* Column, no per-option text at all (only min/max, or nothing): a bare\n numbered list has nothing to left-align against, so center each\n number instead of stranding it on the left with blank space beside it. */\n.magicfeedback-rating-number-container-column--bare .magicfeedback-rating-number-value {\n justify-content: center;\n}\n\n.magicfeedback-rating-number-value-label {\n font-weight: var(--mf-font-weight-medium);\n}\n\n.magicfeedback-rating-number-option-column .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value,\n.magicfeedback-rating-number-option-column-reverse .magicfeedback-rating-number-option-label-container input[type=\"radio\"]:checked + .magicfeedback-rating-number-value {\n color: #fff;\n background-color: var(--mf-primary);\n}\n\n/* Star Rating */\n.magicfeedback-rating-star-container {\n justify-content: center;\n}\n\n.magicfeedback-rating-star-option:not(:checked) > .rating__star {\n font-size: 40px;\n color: var(--mf-border);\n cursor: pointer;\n margin: 0 var(--mf-space-xs);\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-rating-star-option:not(:checked) > .rating__star:hover,\n .magicfeedback-rating-star-option:not(:checked) > .rating__star:hover ~ span {\n color: #fbbf24;\n transform: scale(1.1);\n }\n}\n\n.magicfeedback-rating-star-selected {\n color: #fbbf24 !important;\n}\n\n/* ========================================\n MULTIPLE CHOICE WITH IMAGES\n ======================================== */\n.magicfeedback-multiple-choice-image-option {\n display: flex;\n border-radius: var(--mf-radius-md);\n overflow: hidden;\n transition: var(--mf-transition-fast);\n}\n\n.magicfeedback-image-option-label-container {\n cursor: pointer;\n border-radius: var(--mf-radius-md);\n overflow: hidden;\n transition: var(--mf-transition-fast);\n}\n\n.magicfeedback-image-option-label-container input[type=\"radio\"],\n.magicfeedback-image-option-label-container input[type=\"checkbox\"] {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.magicfeedback-image-option-label-container img {\n width: 100%;\n height: auto;\n display: block;\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-image-option-label-container:hover img {\n border-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-md);\n }\n}\n\n.magicfeedback-image-option-label-container input:checked + img {\n border-color: var(--mf-primary);\n box-shadow: 0 0 0 3px var(--mf-primary-light);\n}\n\n/* ========================================\n MATRIX QUESTIONS\n ======================================== */\n.magicfeedback-multi-question-matrix-container {\n width: 100%;\n overflow-x: auto;\n border-radius: var(--mf-radius-md);\n /* the layout follows the space the survey gets (a pop-in, a sidebar),\n not the size of the window */\n container-type: inline-size;\n}\n\n.magicfeedback-multi-question-matrix-table {\n width: 100%;\n border-collapse: separate;\n border-spacing: 0 var(--mf-space-xs);\n}\n\n.magicfeedback-multi-question-matrix-table th {\n padding: var(--mf-space-md);\n text-align: center;\n font-size: var(--mf-font-size-sm);\n font-weight: 500;\n color: var(--mf-text-secondary);\n}\n\n.magicfeedback-multi-question-matrix-row-tr {\n background-color: var(--mf-bg-primary);\n border-radius: var(--mf-radius-md);\n transition: var(--mf-transition-fast);\n}\n\n.magicfeedback-multi-question-matrix-row-tr:nth-child(even) {\n background-color: var(--mf-bg-secondary);\n}\n\n@media (hover: hover) {\n .magicfeedback-multi-question-matrix-row-tr:hover {\n background-color: var(--mf-bg-hover);\n box-shadow: var(--mf-shadow-sm);\n }\n}\n\n.magicfeedback-multi-question-matrix-table td {\n padding: var(--mf-space-md);\n text-align: center;\n}\n\n.magicfeedback-multi-question-matrix-label {\n text-align: start;\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-primary);\n}\n\n.magicfeedback-multi-question-matrix-table input[type=\"radio\"] {\n width: 20px;\n height: 20px;\n cursor: pointer;\n accent-color: var(--mf-primary);\n}\n\n.magicfeedback-multi-question-matrix-question {\n min-width: 200px;\n padding: 10px;\n}\n\n/* the column header names the option in a table; the per-cell label is for\n screen readers until the rows stack */\n.magicfeedback-multi-question-matrix-option-label {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n}\n\n/* narrow container: every row becomes its own question with the options\n stacked underneath, instead of a table cut off at the side */\n@container (max-width: 599px) {\n .magicfeedback-multi-question-matrix-table,\n .magicfeedback-multi-question-matrix-table tbody,\n .magicfeedback-multi-question-matrix-table tr,\n .magicfeedback-multi-question-matrix-table td {\n display: block;\n width: 100%;\n }\n\n .magicfeedback-multi-question-matrix-table thead {\n display: none;\n }\n\n .magicfeedback-multi-question-matrix-row-tr,\n .magicfeedback-multi-question-matrix-row-tr:nth-child(even) {\n margin-bottom: var(--mf-space-lg);\n background: transparent;\n box-shadow: none;\n }\n\n @media (hover: hover) {\n .magicfeedback-multi-question-matrix-row-tr:hover {\n margin-bottom: var(--mf-space-lg);\n background: transparent;\n box-shadow: none;\n }\n }\n\n .magicfeedback-multi-question-matrix-table td.magicfeedback-multi-question-matrix-question {\n min-width: 0;\n padding: 0 0 var(--mf-space-sm);\n text-align: start;\n }\n\n .magicfeedback-multi-question-matrix-question .magicfeedback-multi-question-matrix-label {\n font-weight: 600;\n }\n\n .magicfeedback-multi-question-matrix-table td.magicfeedback-multi-question-matrix-option {\n display: flex;\n align-items: center;\n gap: var(--mf-space-sm);\n margin-bottom: var(--mf-space-sm);\n padding: 0 var(--mf-space-md);\n text-align: start;\n background: var(--mf-bg-secondary);\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n }\n\n @media (hover: hover) {\n .magicfeedback-multi-question-matrix-option:hover {\n border-color: var(--mf-primary-border);\n }\n }\n\n .magicfeedback-multi-question-matrix-option:has(input:checked) {\n border-color: var(--mf-primary);\n background: var(--mf-primary-light);\n }\n\n .magicfeedback-multi-question-matrix-table .magicfeedback-multi-question-matrix-option input[type=\"radio\"] {\n flex-shrink: 0;\n }\n\n /* the whole card is the click target */\n .magicfeedback-multi-question-matrix-option-label {\n position: static;\n flex: 1;\n width: auto;\n height: auto;\n padding: var(--mf-space-md) 0;\n overflow: visible;\n clip: auto;\n white-space: normal;\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-primary);\n cursor: pointer;\n }\n}\n\n/* ========================================\n MAXDIFF (BEST–WORST)\n ======================================== */\n.magicfeedback-max-diff {\n /* \"least\" gets its own hue, apart from the brand color \"most\" uses; dark\n enough to read as header text and to hold the white center dot */\n --mf-max-diff-worst: #b45309;\n width: 100%;\n}\n\n.magicfeedback-max-diff-container {\n width: 100%;\n /* the layout follows the space the survey gets (a pop-in, a sidebar),\n not the size of the window */\n container-type: inline-size;\n}\n\n.magicfeedback-max-diff-progress {\n display: flex;\n align-items: center;\n gap: var(--mf-space-md);\n margin-bottom: var(--mf-space-lg);\n}\n\n.magicfeedback-max-diff-progress-label {\n font-size: 0.8125rem;\n font-weight: var(--mf-font-weight-bold);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n white-space: nowrap;\n color: var(--mf-text-secondary);\n}\n\n.magicfeedback-max-diff-progress-track {\n flex: 1;\n height: 6px;\n overflow: hidden;\n border-radius: var(--mf-radius-full);\n background: var(--mf-border);\n}\n\n.magicfeedback-max-diff-progress-fill {\n display: block;\n height: 100%;\n border-radius: var(--mf-radius-full);\n background: var(--mf-primary);\n}\n\n.magicfeedback-max-diff-table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.magicfeedback-max-diff-head,\n.magicfeedback-max-diff-head-spacer {\n border-bottom: 1px solid var(--mf-border);\n}\n\n.magicfeedback-max-diff-head {\n width: 11rem;\n padding: 0 var(--mf-space-sm) var(--mf-space-sm);\n font-size: 0.8125rem;\n font-weight: var(--mf-font-weight-extrabold);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n text-align: center;\n vertical-align: bottom;\n}\n\n.magicfeedback-max-diff-head-best {\n color: var(--mf-primary);\n}\n\n.magicfeedback-max-diff-head-worst {\n color: var(--mf-max-diff-worst);\n}\n\n.magicfeedback-max-diff-head-label {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.magicfeedback-max-diff-row {\n transition: background-color 0.15s ease;\n}\n\n.magicfeedback-max-diff-row > * {\n border-bottom: 1px solid var(--mf-border);\n}\n\n.magicfeedback-max-diff-row:has(.magicfeedback-max-diff-best:checked) {\n background-color: color-mix(in srgb, var(--mf-primary) 8%, transparent);\n}\n\n.magicfeedback-max-diff-row:has(.magicfeedback-max-diff-worst:checked) {\n background-color: color-mix(in srgb, var(--mf-max-diff-worst) 8%, transparent);\n}\n\n.magicfeedback-max-diff-item {\n padding: var(--mf-space-md) var(--mf-space-sm);\n font-size: 1.0625rem;\n font-weight: var(--mf-font-weight-semibold);\n line-height: 1.4;\n text-align: center;\n color: var(--mf-text-primary);\n}\n\n.magicfeedback-max-diff-row:has(input:checked) .magicfeedback-max-diff-item {\n font-weight: var(--mf-font-weight-extrabold);\n}\n\n.magicfeedback-max-diff-cell {\n padding: 0;\n}\n\n/* the whole cell is the click target */\n.magicfeedback-max-diff-pick {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 64px;\n cursor: pointer;\n}\n\n.magicfeedback-max-diff-input {\n appearance: none;\n -webkit-appearance: none;\n flex-shrink: 0;\n width: 26px;\n height: 26px;\n margin: 0;\n border: 2px solid color-mix(in srgb, var(--mf-text-secondary) 80%, white);\n border-radius: var(--mf-radius-full);\n background: var(--mf-bg-secondary);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-max-diff-pick:hover .magicfeedback-max-diff-input {\n transform: scale(1.08);\n }\n}\n\n.magicfeedback-max-diff-best:checked {\n border-color: var(--mf-primary);\n background: radial-gradient(circle, #FFFFFF 0 4px, var(--mf-primary) 4.5px);\n}\n\n.magicfeedback-max-diff-worst:checked {\n border-color: var(--mf-max-diff-worst);\n background: radial-gradient(circle, #FFFFFF 0 4px, var(--mf-max-diff-worst) 4.5px);\n}\n\n.magicfeedback-max-diff-input:focus-visible {\n outline: 3px solid var(--mf-primary-border);\n outline-offset: 3px;\n}\n\n/* narrow container: same table, slimmer pick columns and the header icon\n stacked over its label */\n@container (max-width: 599px) {\n .magicfeedback-max-diff-head {\n width: 4.75rem;\n padding-inline: 2px;\n font-size: 0.6875rem;\n }\n\n .magicfeedback-max-diff-head-label {\n flex-direction: column;\n gap: 2px;\n }\n\n .magicfeedback-max-diff-item {\n padding-inline: 4px;\n font-size: var(--mf-font-size-base);\n }\n\n .magicfeedback-max-diff-pick {\n min-height: 68px;\n }\n\n .magicfeedback-max-diff-input {\n width: 28px;\n height: 28px;\n }\n}\n\n/* ========================================\n PRIORITY LIST\n ======================================== */\n.magicfeedback-priority-list-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mf-space-md);\n margin: var(--mf-space-sm) 0;\n background-color: var(--mf-bg-primary);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-priority-list-item:hover {\n box-shadow: var(--mf-shadow-sm);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-priority-list-item-label {\n flex: 1;\n font-size: var(--mf-font-size-base);\n text-align: start;\n color: var(--mf-text-primary);\n margin: 0;\n}\n\n.magicfeedback-priority-list-arrow-up,\n.magicfeedback-priority-list-arrow-down {\n padding: var(--mf-space-xs);\n font-size: var(--mf-font-size-lg);\n color: var(--mf-text-secondary);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n user-select: none;\n}\n\n@media (hover: hover) {\n .magicfeedback-priority-list-arrow-up:hover,\n .magicfeedback-priority-list-arrow-down:hover {\n color: var(--mf-primary);\n transform: scale(1.2);\n }\n}\n\n/* ========================================\n POINT SYSTEM\n ======================================== */\n.magicfeedback-point-system-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--mf-space-md);\n margin: var(--mf-space-sm) 0;\n background-color: var(--mf-bg-primary);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n min-height: 45px;\n}\n\n.magicfeedback-point-system-item label {\n flex: 1;\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-primary);\n margin: 0;\n}\n\n.magicfeedback-point-system-input-container {\n display: flex;\n align-items: center;\n gap: var(--mf-space-sm);\n}\n\n.magicfeedback-point-system-item input {\n width: 80px;\n min-width: 80px;\n padding: var(--mf-space-sm);\n text-align: center;\n font-size: var(--mf-font-size-base);\n border: 1.5px solid var(--mf-border);\n border-radius: var(--mf-radius-md);\n margin: 0;\n}\n\n.magicfeedback-point-system-total {\n display: flex;\n justify-content: flex-end;\n margin-top: var(--mf-space-md);\n font-size: var(--mf-font-size-base);\n font-weight: 600;\n color: var(--mf-text-primary);\n}\n\n/* ========================================\n CONSENT\n ======================================== */\n.magicfeedback-consent-container {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--mf-space-sm);\n padding: var(--mf-space-md);\n background-color: var(--mf-bg-secondary);\n border-radius: var(--mf-radius-md);\n margin: var(--mf-space-md) 0;\n}\n\n.magicfeedback-consent-container input[type=\"checkbox\"] {\n margin-top: 2px;\n flex-shrink: 0;\n accent-color: var(--mf-primary);\n}\n\n.magicfeedback-consent-container label {\n flex: 1;\n font-size: var(--mf-font-size-sm);\n text-align: start;\n color: var(--mf-text-secondary);\n line-height: var(--mf-line-height);\n cursor: pointer;\n margin-bottom: 0;\n}\n\n/* ========================================\n ACTION BUTTONS\n ======================================== */\n.magicfeedback-action-container {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--mf-space-md);\n margin-top: var(--mf-space-xl);\n padding-top: var(--mf-space-lg);\n border-top: 1px solid var(--mf-border);\n}\n\n.magicfeedback-submit {\n padding: 0.8rem 1.6rem;\n font-size: var(--mf-font-size-base);\n font-weight: 800;\n color: #FFFFFF;\n background-color: var(--mf-primary);\n border: 2px solid var(--mf-primary);\n border-radius: var(--mf-radius-full);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n box-shadow: var(--mf-shadow-md);\n}\n\n@media (hover: hover) {\n .magicfeedback-submit:hover {\n background-color: var(--mf-primary-hover);\n box-shadow: var(--mf-shadow-lg);\n transform: translateY(-2px);\n }\n}\n\n.magicfeedback-submit:active {\n transform: translateY(0);\n box-shadow: var(--mf-shadow-sm);\n}\n\n.magicfeedback-submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n}\n\n.magicfeedback-back {\n padding: 0.65rem 1rem;\n font-size: var(--mf-font-size-base);\n font-weight: 700;\n color: var(--mf-primary);\n background-color: var(--mf-bg-secondary);\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-full);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-back:hover {\n border-color: var(--mf-primary);\n background-color: var(--mf-bg-hover);\n }\n}\n\n/* hidden on the first page; keep the submit button on the right */\n.magicfeedback-back[hidden] {\n display: none;\n}\n\n.magicfeedback-action-container .magicfeedback-submit {\n margin-left: auto;\n}\n\n/* ========================================\n SKIP & COUNTER\n ======================================== */\n.magicfeedback-skip-container {\n display: flex;\n align-items: center;\n gap: var(--mf-space-xs);\n padding: 0;\n margin: 0;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-muted);\n background: transparent;\n border: none;\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n@media (hover: hover) {\n .magicfeedback-skip-container:hover {\n color: var(--mf-primary);\n }\n}\n\n.magicfeedback-skip-container input[type=\"checkbox\"] {\n margin: 0;\n cursor: pointer;\n accent-color: var(--mf-primary);\n}\n\n.magicfeedback-counter {\n display: flex;\n justify-content: flex-end;\n margin-top: var(--mf-space-md);\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-muted);\n}\n\n/* ========================================\n MODAL STYLES\n ======================================== */\n/* Modal backdrop */\n.magicfeedback-modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n background: rgba(0,0,0,0.4);\n display: none; /* toggled to flex when open */\n align-items: center;\n justify-content: center;\n z-index: 9999;\n}\n\n/* Modal container */\n.magicfeedback-modal {\n background: #fff;\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(0,0,0,0.2);\n max-width: 520px;\n width: 90%;\n max-height: 80vh;\n /* Flex column so the options list scrolls while the actions footer\n (counter + confirm button) stays pinned and always visible. */\n display: flex;\n flex-direction: column;\n overflow: hidden;\n padding: 16px;\n position: relative;\n /* Portalled to document.body (see renderPriorityList.ts) to escape the\n host page's overflow/z-index — so it never inherits\n .magicfeedback-container's neutral text color and instead inherits\n whatever color the host page's own <body> happens to set. Needs its\n own explicit baseline rather than relying on that inheritance. */\n color: var(--mf-text-primary);\n}\n\n/* Modal actions */\n.magicfeedback-modal-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 12px;\n flex-shrink: 0;\n}\n\n.magicfeedback-modal-counter {\n margin-inline-end: auto;\n display: flex;\n align-items: center;\n font-size: 14px;\n color: var(--mf-text-secondary);\n}\n\n/* Modal list */\n.magicfeedback-modal-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n /* Scrollable area; footer stays fixed below it. */\n flex: 1 1 auto;\n overflow-y: auto;\n min-height: 0;\n}\n\n.magicfeedback-modal-row {\n display: flex;\n align-items: center;\n gap: 10px;\n cursor: pointer;\n}\n\n/* Priority list header */\n.magicfeedback-priority-list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 8px;\n}\n\n.magicfeedback-priority-list-reorder {\n margin-top: 8px;\n}\n\n.magicfeedback-priority-list-list {\n padding: 0;\n list-style: none;\n}\n\n.magicfeedback-priority-list-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin: 5px 0;\n}\n\n.magicfeedback-priority-list-item-label {\n font-size: 15px;\n}\n\n.magicfeedback-priority-list-arrows {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n/* Buttons */\n.magicfeedback-button {\n appearance: none;\n border: 2px solid var(--mf-border);\n border-radius: var(--mf-radius-full);\n background: var(--mf-bg-secondary);\n color: var(--mf-text-primary);\n padding: 0.6rem 1rem;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n box-shadow: var(--mf-shadow-sm);\n}\n\n@media (hover: hover) {\n .magicfeedback-button:hover {\n background: var(--mf-bg-hover);\n border-color: var(--mf-primary);\n }\n}\n\n.magicfeedback-button:active {\n transform: translateY(1px);\n}\n\n.magicfeedback-button-primary {\n border-color: var(--mf-primary);\n background: var(--mf-primary);\n color: #fff;\n box-shadow: var(--mf-shadow-md);\n}\n\n@media (hover: hover) {\n .magicfeedback-button-primary:hover {\n background: var(--mf-primary-hover);\n }\n}\n\n.magicfeedback-button-primary:active {\n background: var(--mf-primary-hover);\n}\n\n/* Warning flash */\n.magicfeedback-warning {\n color: #C70039;\n}\n\n/* Start message button */\n.magicfeedback-start-message-button {\n appearance: none;\n border: 2px solid var(--mf-primary);\n border-radius: var(--mf-radius-full);\n background: var(--mf-primary);\n color: #fff;\n padding: 0.8rem 1.4rem;\n font-size: 15px;\n font-weight: 800;\n cursor: pointer;\n box-shadow: var(--mf-shadow-md);\n}\n\n@media (hover: hover) {\n .magicfeedback-start-message-button:hover {\n background: var(--mf-primary-hover);\n }\n}\n\n/* ========================================\n RESPONSIVE DESIGN\n ======================================== */\n@media (max-width: 768px) {\n :root {\n --mf-space-lg: 1.25rem;\n --mf-space-xl: 1.5rem;\n }\n\n .magicfeedback-container {\n padding: 12px;\n border-radius: var(--mf-radius-md);\n }\n\n .magicfeedback-form {\n padding: 14px;\n border-radius: var(--mf-radius-md);\n }\n\n .magicfeedback-div {\n padding: 12px;\n gap: var(--mf-space-lg)\n }\n\n .magicfeedback-label {\n font-size: var(--mf-font-size-base);\n }\n\n .deepdots-popup input[type=\"date\"],\n .deepdots-popup input[type=\"text\"],\n .deepdots-popup input[type=\"number\"],\n .deepdots-popup input[type=\"email\"],\n .deepdots-popup input[type=\"password\"],\n .deepdots-popup textarea,\n .deepdots-popup select {\n padding: 0.85rem 0.95rem;\n font-size: 1rem;\n min-height: 46px;\n }\n\n /*\n .magicfeedback-radio-container,\n .magicfeedback-checkbox-container,\n .magicfeedback-boolean-container,\n .magicfeedback-consent-container {\n width: 100%;\n }\n */\n\n .magicfeedback-radio-container,\n .magicfeedback-checkbox-container {\n padding: 10px 12px;\n border-radius: var(--mf-radius-md);\n border: 1px solid var(--mf-border);\n gap: 10px;\n }\n\n .magicfeedback-rating-container,\n .magicfeedback-rating-number-container,\n .magicfeedback-rating-star-container {\n gap: 10px;\n }\n\n .magicfeedback-boolean-container {\n flex-direction: column;\n gap: 10px;\n }\n\n .magicfeedback-rating-number-container-row,\n .magicfeedback-rating-number-container-row-reverse {\n gap: 6px;\n }\n\n .magicfeedback-rating-number-option-row .magicfeedback-rating-number-value,\n .magicfeedback-rating-number-option-row-reverse .magicfeedback-rating-number-value {\n min-height: 44px;\n padding: var(--mf-space-xs);\n font-size: 0.9rem;\n }\n\n .magicfeedback-action-container {\n flex-direction: column-reverse;\n }\n\n .magicfeedback-submit,\n .magicfeedback-back {\n width: 100%;\n text-align: center;\n min-height: 48px;\n }\n}\n\n/* ========================================\n ACCESSIBILITY\n ======================================== */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n\n/* ========================================\n UTILITY CLASSES\n ======================================== */\n.magicfeedback-visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n\n.magicfeedback-modal-close {\n position: absolute;\n top: 8px;\n inset-inline-end: 8px;\n background: transparent;\n border: none;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n color: #666;\n}\n\n.magicfeedback-modal-close:focus {\n color: #000;\n outline: none;\n}\n\n@media (hover: hover) {\n .magicfeedback-modal-close:hover {\n color: #000;\n outline: none;\n }\n}\n\n/* ========================================\n FILE / IMAGE UPLOAD\n ======================================== */\n.magicfeedback-upload {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-md);\n width: 100%;\n margin: 10px 0;\n}\n\n/* The native input is an invisible, focusable overlay covering the dropzone. */\n.magicfeedback-upload-native {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n opacity: 0;\n cursor: pointer;\n z-index: 2;\n}\n\n.magicfeedback-upload-dropzone {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--mf-space-sm);\n box-sizing: border-box;\n width: 100%;\n min-height: 150px;\n padding: var(--mf-space-xl);\n text-align: center;\n border: 2px dashed var(--mf-border);\n border-radius: var(--mf-radius-md);\n background-color: var(--mf-surface-alt);\n cursor: pointer;\n transition: var(--mf-transition);\n}\n\n@media (hover: hover) {\n .magicfeedback-upload-dropzone:hover {\n border-color: var(--mf-primary);\n background-color: var(--mf-bg-hover);\n }\n}\n\n.magicfeedback-upload-dropzone:focus-within {\n border-color: var(--mf-primary);\n box-shadow: var(--mf-shadow-focus);\n}\n\n.magicfeedback-upload-dropzone.is-dragover {\n border-color: var(--mf-primary);\n border-style: solid;\n background-color: var(--mf-primary-light);\n box-shadow: var(--mf-shadow-focus);\n}\n\n.magicfeedback-upload-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 46px;\n height: 46px;\n color: var(--mf-primary);\n background-color: var(--mf-surface);\n border-radius: var(--mf-radius-full);\n box-shadow: var(--mf-shadow-sm);\n pointer-events: none;\n}\n\n.magicfeedback-upload-icon svg {\n width: 24px;\n height: 24px;\n}\n\n.magicfeedback-upload-cta {\n font-family: inherit;\n font-weight: var(--mf-font-weight-semibold);\n font-size: var(--mf-font-size-base);\n color: var(--mf-text-primary);\n pointer-events: none;\n}\n\n.magicfeedback-upload-hint {\n font-family: inherit;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-text-muted);\n pointer-events: none;\n}\n\n.magicfeedback-upload-list {\n display: flex;\n flex-direction: column;\n gap: var(--mf-space-sm);\n width: 100%;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.magicfeedback-upload-list:empty {\n display: none;\n}\n\n.magicfeedback-upload-file-item {\n display: flex;\n align-items: center;\n gap: var(--mf-space-md);\n padding: var(--mf-space-sm) var(--mf-space-md);\n background-color: var(--mf-surface);\n border: 1px solid var(--mf-border);\n border-radius: var(--mf-radius-sm);\n box-shadow: var(--mf-shadow-sm);\n animation: mf-scale-in 0.2s ease-out;\n}\n\n.magicfeedback-upload-thumb {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n overflow: hidden;\n color: var(--mf-text-secondary);\n background-color: var(--mf-surface-alt);\n border-radius: var(--mf-radius-sm);\n}\n\n.magicfeedback-upload-thumb img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.magicfeedback-upload-thumb svg {\n width: 20px;\n height: 20px;\n}\n\n.magicfeedback-upload-file-info {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-width: 0;\n text-align: start;\n}\n\n.magicfeedback-upload-file-name {\n font-family: inherit;\n font-size: var(--mf-font-size-sm);\n font-weight: var(--mf-font-weight-semibold);\n color: var(--mf-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.magicfeedback-upload-file-size {\n font-family: inherit;\n font-size: 0.75rem;\n color: var(--mf-text-muted);\n}\n\n.magicfeedback-upload-remove {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n padding: 0;\n color: var(--mf-text-muted);\n background-color: transparent;\n border: none;\n border-radius: var(--mf-radius-full);\n cursor: pointer;\n transition: var(--mf-transition-fast);\n}\n\n.magicfeedback-upload-remove:focus-visible {\n color: var(--mf-error);\n background-color: var(--mf-primary-light);\n outline: none;\n}\n\n@media (hover: hover) {\n .magicfeedback-upload-remove:hover {\n color: var(--mf-error);\n background-color: var(--mf-primary-light);\n outline: none;\n }\n}\n\n.magicfeedback-upload-remove svg {\n width: 16px;\n height: 16px;\n}\n\n.magicfeedback-upload-error {\n display: none;\n font-family: inherit;\n font-size: var(--mf-font-size-sm);\n color: var(--mf-error);\n}\n\n.magicfeedback-upload-error.is-visible {\n display: block;\n}\n\n/* ========================================\n RTL (Arabic)\n ========================================\n The SDK stamps dir=\"rtl\" (and .magicfeedback-rtl) on the survey container\n for right-to-left languages. Everything else in this stylesheet uses logical\n properties (text-align: start, padding-inline-*, inset-inline-*), so it\n mirrors on its own; only the handful of rules that cannot be expressed\n logically live here.\n\n Rules are scoped to [dir=\"rtl\"] inside our own subtree so a host page that is\n itself RTL, or one that embeds an Arabic survey in an LTR page, both render\n correctly. */\n\n/* background-position has no logical form: move the select caret to the\n left edge, opposite the padding that inset-inline-end already mirrored. */\n[dir=\"rtl\"] select {\n background-position: left var(--mf-space-md) center;\n}\n\n/* Emails, URLs, numbers, dates and phone numbers are latin/numeric content\n even in an Arabic survey: keep their own direction LTR so punctuation\n (@, ., +, /) lands where the user expects, but align them to the start of\n the RTL line so they still read as part of the form. */\n[dir=\"rtl\"] input[type=\"email\"],\n[dir=\"rtl\"] input[type=\"url\"],\n[dir=\"rtl\"] input[type=\"number\"],\n[dir=\"rtl\"] input[type=\"tel\"],\n[dir=\"rtl\"] input[type=\"date\"],\n[dir=\"rtl\"] input[type=\"time\"] {\n direction: ltr;\n text-align: right;\n}\n\n/* Mirror of mf-slide-in: questions enter from the reading edge. */\n@keyframes mf-slide-in-rtl {\n from {\n opacity: 0;\n transform: translateX(16px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n[dir=\"rtl\"] .magicfeedback-div {\n animation-name: mf-slide-in-rtl;\n}\n\n/* ========================================\n LOCAL — reglas del popup, no vienen del paquete\n ======================================== */\n\n/* Los controles de formulario no heredan font-family: cuando el popup trae fuente propia\n (clase deepdots-has-font), se les fuerza la del contenedor. */\n.deepdots-popup.deepdots-has-font button,\n.deepdots-popup.deepdots-has-font input,\n.deepdots-popup.deepdots-has-font textarea,\n.deepdots-popup.deepdots-has-font select {\n font-family: var(--deepdots-font, inherit);\n}\n\n/* WebKit sí aplica el padding al radio nativo: con padding el control se dibuja enorme (se vio\n en iOS, WebView de RN y Safari, no en Chrome). El paquete ya no lo pone, pero la regla es\n barata y evita que vuelva. */\n.deepdots-popup input[type=\"radio\"] {\n padding: 0;\n}\n","import type { DeepdotsLogger } from '../types';\n\n/**\n * Logger de módulo compartido: el core lo fija en init() con el logger inyectado\n * por el host (o `console` por defecto), y los módulos que no reciben `this`\n * (renderPopup, renderers) enrutan por aquí. `warn`/`error` caen a `log` si el\n * logger del host no los provee (solo `log` es obligatorio en DeepdotsLogger).\n */\nlet active: DeepdotsLogger = console;\n\nexport function setLogger(logger: DeepdotsLogger | undefined): void {\n active = logger ?? console;\n}\n\nexport function sdkLog(...args: unknown[]): void {\n active.log(...args);\n}\n\nexport function sdkWarn(...args: unknown[]): void {\n (active.warn ?? active.log)(...args);\n}\n\nexport function sdkError(...args: unknown[]): void {\n (active.error ?? active.log)(...args);\n}\n","/**\n * Logo del popup (viene en `formData.style` del survey, no del `init`).\n *\n * Vive encima de la barra de progreso, como hermano del header: la marca abre la tarjeta y el\n * progreso queda pegado a la pregunta. Antes se insertaba DENTRO del contenido (web) o del área\n * scrollable (WebView de RN), así que la barra quedaba por encima del logo y en RN el logo se\n * iba con el scroll.\n *\n * El hueco inferior lo pone el bloque siguiente (`.deepdots-progress` cuando se ve, o el\n * `padding-top` del contenido cuando no), así que aquí solo se separa del header.\n */\nexport interface PopupLogoStyle {\n logo?: string;\n logoSize?: string;\n logoPosition?: string;\n}\n\nconst BASE_CSS = 'max-height:40px; max-width:100%; object-fit:contain; display:block; margin:12px 0 0 0;';\n\nconst MAX_HEIGHT: Record<string, string> = {\n small: '30px',\n medium: '50px',\n large: '70px',\n};\n\n/**\n * Inserta el logo en `parent`, justo antes de `anchor` (la barra de progreso).\n * No-op si el estilo no trae logo o si ya existe uno con ese `id`.\n */\nexport function insertPopupLogo(\n parent: HTMLElement,\n anchor: HTMLElement | null,\n style: PopupLogoStyle | undefined,\n id: string,\n): HTMLImageElement | null {\n if (!style?.logo) return null;\n if (parent.ownerDocument.getElementById(id)) return null;\n\n const img = parent.ownerDocument.createElement('img');\n img.id = id;\n img.src = style.logo;\n img.alt = 'Logo';\n img.style.cssText = BASE_CSS;\n\n if (style.logoSize && MAX_HEIGHT[style.logoSize]) {\n img.style.maxHeight = MAX_HEIGHT[style.logoSize];\n }\n // Márgenes lógicos (inline-start/end) en vez de left/right: con un survey RTL (árabe) el\n // popup se pinta con dir=\"rtl\" y el logo tiene que acompañar al resto del chrome. En LTR\n // el resultado es idéntico al de antes.\n switch (style.logoPosition) {\n case 'left':\n img.style.margin = '12px 0 0 0';\n img.style.setProperty('margin-inline-start', '0');\n img.style.setProperty('margin-inline-end', '16px');\n break;\n case 'right':\n img.style.margin = '12px 0 0 0';\n img.style.setProperty('margin-inline-start', 'auto');\n img.style.setProperty('margin-inline-end', '0');\n break;\n case 'center':\n img.style.margin = '12px auto 0 auto';\n break;\n }\n\n parent.insertBefore(img, anchor);\n return img;\n}\n","import {DeepdotsEventType, PopupActions, PopupStyle, FormData} from '../types';\nimport { buildSurveyIdentity } from '../tracking/tracking-manager';\nimport type { PopupRenderOptions } from '../platform/renderer';\nimport { buildFontFaceCss, buildFontFamilyValue } from './font';\nimport { insertPopupLogo } from './logo';\nimport { applySurveyPrimaryColor } from './surveyPalette';\nimport { setSurveyBusy } from './busy';\nimport { sdkLog, sdkWarn, sdkError } from '../util/logger';\nimport { directionFor, getLabels, resolveActionLabels } from '../i18n/labels';\nimport { REVEAL_TIMEOUT_MS, revealWhenPainted as revealWhenImagesLoaded } from './reveal';\nimport magicfeedback from \"@magicfeedback/native\";\nimport magicfeedbackCss from '../assets/style.css';\n\n// Reexportado desde aquí porque este era su sitio original: la constante se mudó a `reveal.ts`\n// para que la ruta del WebView pueda leerla sin arrastrar `@magicfeedback/native`.\nexport { REVEAL_TIMEOUT_MS } from './reveal';\n\n// Inserta la hoja de estilos de MagicFeedback directamente en el popup para garantizar estilos incluso si el bundler no la inyecta globalmente.\nfunction ensureMagicFeedbackStyles(_popup: HTMLElement) {\n const STYLE_ID = 'magicfeedback-sdk-styles';\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = magicfeedbackCss;\n document.head.appendChild(style);\n}\n\n/**\n * Id del bloque que alinea el contenido del survey con el chrome del popup. Exportado para que\n * los tests puedan localizarlo sin depender de la posición en el `<head>`.\n */\nexport const SURVEY_ALIGNMENT_STYLE_ID = 'deepdots-survey-alignment';\n\n/**\n * El margen lateral lo pone la tarjeta del popup, pero el CSS de `@magicfeedback/native` añade el\n * suyo en tres capas: el contenedor, el formulario (hasta 28px) y el bloque de cada pregunta\n * (12px, con fondo igual al de la tarjeta, así que ni se ve). Sumados, dejan el enunciado y las\n * opciones bastante más adentro que el logo, el título y la barra de progreso, que sí llegan al\n * borde. Aquí se anula solo el lateral: el vertical es el que separa las preguntas.\n *\n * Va acotado a `.deepdots-popup` porque esta hoja se inyecta en el `<head>` del host y sin acotar\n * repintaría sus propios formularios; de paso, la especificidad extra hace que gane sobre el CSS\n * del paquete sin depender del orden.\n */\nfunction ensureSurveyAlignmentStyles() {\n if (document.getElementById(SURVEY_ALIGNMENT_STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = SURVEY_ALIGNMENT_STYLE_ID;\n style.textContent = `\n .deepdots-popup .magicfeedback-container,\n .deepdots-popup .magicfeedback-form,\n .deepdots-popup .magicfeedback-div { padding-left: 0; padding-right: 0; }\n `;\n document.head.appendChild(style);\n}\n\n// Añade estilos de spinner si no existen\nfunction ensureSpinnerStyles(_popup: HTMLElement) {\n if (document.getElementById('deepdots-spinner-styles')) return;\n const style = document.createElement('style');\n style.id = 'deepdots-spinner-styles';\n style.textContent = `\n /* El enunciado trae margin-top:20px del CSS de surveys, que sumaba al hueco de la barra\n de progreso y descompensaba la simetría. Solo se anula en el primero: entre preguntas\n de la misma página sigue separando. */\n .deepdots-popup .magicfeedback-questions .magicfeedback-div:first-child .magicfeedback-label { margin-top: 0; }\n @keyframes ddspin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n .mf-spinner { display:flex; justify-content:center; align-items:center; padding:8px 0; }\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 `;\n document.head.appendChild(style);\n}\n\nfunction ensureResponsiveStyles(_popup: HTMLElement) {\n if (document.getElementById('deepdots-responsive-styles')) return;\n const style = document.createElement('style');\n style.id = 'deepdots-responsive-styles';\n style.textContent = `\n /* Responsive adjustments */\n @media (max-width: 640px) {\n .deepdots-popup {\n width: calc(100% - 24px) !important;\n max-width: calc(100% - 24px) !important;\n height: auto !important;\n max-height: 90vh !important;\n border-radius: 12px !important;\n padding: calc(16px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom)) 16px !important;\n box-sizing: border-box;\n }\n .deepdots-popup .mf-spinner-circle { width: 32px; height: 32px; border-width: 4px; }\n .deepdots-popup button { font-size: 16px !important; }\n .deepdots-popup-header button { width:48px; height:48px; }\n .deepdots-popup-header button svg { width:26px; height:26px; }\n /* Apilados en el orden del DOM: la acción principal (Send/Start/Complete) arriba y\n Back debajo. */\n .deepdots-popup-footer { flex-direction: column !important; gap: 4px !important; }\n .deepdots-popup-footer button { width: 100%; }\n }\n @media (max-width: 400px) {\n .deepdots-popup { padding: calc(12px + env(safe-area-inset-top)) 12px calc(12px + env(safe-area-inset-bottom)) 12px !important; }\n .deepdots-popup-header button { width:48px; height:48px; }\n .deepdots-popup-header button svg { width:26px; height:26px; }\n }\n @media (orientation: landscape) and (max-height: 480px) {\n .deepdots-popup {\n height: 100vh !important;\n max-height: 100vh !important;\n overflow-y: auto !important;\n }\n }\n `;\n document.head.appendChild(style);\n}\n\n/**\n * CSS del host, en un <style> propio añadido DESPUÉS de los del SDK y de\n * `@magicfeedback/native` para que gane en cascada sin tocar ninguno de los dos.\n * Se reemplaza en cada render por si cambia entre popups.\n */\nfunction ensureCustomCss(css?: string) {\n const STYLE_ID = 'deepdots-custom-css';\n const existing = document.getElementById(STYLE_ID);\n if (!css) {\n existing?.remove();\n return;\n }\n const el = (existing as HTMLStyleElement | null) ?? document.createElement('style');\n el.id = STYLE_ID;\n if (el.textContent !== css) el.textContent = css;\n // Reanexar mueve el nodo al final del head: garantiza que va tras las hojas del SDK.\n document.head.appendChild(el);\n}\n\n// Inyecta (una sola vez) el @font-face de la fuente personalizada.\nfunction ensureFontFace(family: string, url: string) {\n const css = buildFontFaceCss(family, url);\n if (!css) return;\n const STYLE_ID = 'deepdots-font-face';\n let el = document.getElementById(STYLE_ID) as HTMLStyleElement | null;\n if (!el) {\n el = document.createElement('style');\n el.id = STYLE_ID;\n document.head.appendChild(el);\n }\n if (el.textContent !== css) el.textContent = css;\n}\n\n/**\n * Renderiza el popup dentro del contenedor dado usando MagicFeedback para la encuesta.\n */\nexport async function renderPopup(\n container: HTMLElement,\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 options?: PopupRenderOptions,\n): Promise<void> {\n let surveyCompletedEmitted = false;\n let stylesInjected = false;\n // Profundidad de navegación dentro del survey: +1 al avanzar, -1 al volver. Sustituye a\n // `total > 1 && progress > 0 && progress < total`, que escondía el Back cuando la pantalla\n // siguiente era una follow-up dinámica (no entran en el grafo: suman +0.5 al progress y\n // dejan el total igual, así que un survey de una pregunta con follow-up nunca lo mostraba).\n let pageDepth = 0;\n let onStartPage = false;\n\n // Textos del chrome (botones, progreso, aria-labels, errores). Arrancan con el idioma que\n // conoce el init y se re-resuelven en `onLoadedEvent` con el idioma en el que se muestra el\n // survey (`lang` del evento; `formData.lang[0]`, el idioma por defecto, en native < 2.3),\n // que es el que manda: un survey en danés debe traer también su footer en danés.\n let labels = getLabels(options?.language);\n let actionLabels = resolveActionLabels(actions, options?.language);\n // Dirección del chrome. Desde `@magicfeedback/native` 2.2.22 el survey se voltea solo\n // (`dir=\"rtl\"` en SU contenedor) para los idiomas RTL; si el header, el footer y la barra\n // de progreso se quedaran en LTR, media tarjeta miraría a cada lado. Se marca siempre,\n // también en `ltr`, para no heredar la dirección de la página del host.\n let direction = directionFor(options?.language);\n\n const isDark = style?.theme === 'dark';\n const theme = {\n popupBg: isDark ? '#1e1e1e' : '#fff',\n // El popup fija su propio fondo, así que debe fijar también el esquema de color.\n // Sin esto los controles de formulario (input/textarea/select) caen al estilo por\n // defecto del navegador: en un host con `prefers-color-scheme: dark` se pintan\n // oscuros con texto blanco sobre el fondo claro del popup.\n colorScheme: isDark ? 'dark' : 'light',\n textPrimary: isDark ? '#f0f0f0' : '#111',\n closeBtnHoverColor: isDark ? '#fff' : '#000000',\n textMuted: isDark ? '#9ca3af' : '#6b7280',\n progressTrack: isDark ? '#3f3f46' : '#e5e7eb',\n };\n\n const positionMap: Record<string, { justifyContent: string; alignItems: string; padding?: string; background: string }> = {\n 'center': { justifyContent: 'center', alignItems: 'center', 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 const pos = positionMap[style?.position ?? 'center'] ?? positionMap['center'];\n\n // Crear popup base\n const popup = document.createElement('div');\n popup.id = 'dd-popup';\n popup.className = 'deepdots-popup';\n popup.setAttribute('dir', direction);\n popup.style.cssText = `\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n background: ${theme.popupBg};\n color-scheme: ${theme.colorScheme};\n border-radius: 8px;\n padding: 24px;\n box-shadow: 0 4px 6px rgba(0,0,0,0.1);\n max-width: 600px;\n width: 90%;\n max-height: 90vh;\n min-height: 200px;\n `;\n\n const font = style?.font;\n if (font?.family) {\n const familyValue = buildFontFamilyValue(font.family);\n // El survey vive dentro del contenedor y hereda esta fuente.\n popup.style.fontFamily = familyValue;\n // La variable habilita que el <h2> (Montserrat por defecto) también la use.\n popup.style.setProperty('--deepdots-font', familyValue);\n // Los controles de formulario (button/input/textarea/select) NO heredan\n // font-family por defecto; esta clase activa la regla que los fuerza a heredar.\n popup.classList.add('deepdots-has-font');\n if (font.url) ensureFontFace(font.family, font.url);\n }\n\n // Sección header (título + botón cerrar)\n const header = document.createElement('div');\n header.className = 'deepdots-popup-header';\n header.style.cssText = 'display:flex; justify-content:space-between; align-items:center; gap:12px; width:100%; flex:0 0 auto;';\n\n // Título: `textContent`, nunca innerHTML — el valor viene de la API.\n const titleEl = document.createElement('h2');\n titleEl.id = 'dd-title';\n titleEl.className = 'deepdots-popup-title';\n // text-transform/text-align/margin neutralizan la regla `.deepdots-popup h2` del CSS del\n // survey (uppercase, centrado, margin-bottom 40px), pensada para los enunciados.\n titleEl.style.cssText = `margin:0; font-size:17px; font-weight:600; line-height:1.3; color:${theme.textPrimary}; text-transform:none; text-align:start; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;`;\n titleEl.hidden = true;\n const setTitle = (value?: string) => {\n if (!value) return;\n titleEl.textContent = value;\n titleEl.hidden = false;\n };\n setTitle(options?.title);\n header.appendChild(titleEl);\n\n // Botón de cierre (X)\n const closeBtn = document.createElement('button');\n closeBtn.id = 'dd-close';\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', labels.closeAria);\n closeBtn.innerHTML = `\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M5 5L19 19M5 19L19 5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"butt\"/>\n </svg>\n `;\n closeBtn.style.cssText = `\n background:transparent;\n border:none;\n width:32px;\n height:32px;\n display:flex;\n align-items:center;\n justify-content:center;\n border-radius: 8px;\n cursor:pointer;\n color:${theme.textPrimary};\n padding:4px;\n transition: color .15s ease, transform .15s ease;\n box-shadow: none !important;\n `;\n closeBtn.onmouseenter = () => {\n closeBtn.style.color = theme.closeBtnHoverColor;\n closeBtn.style.transform = 'scale(1.06)';\n };\n closeBtn.onmouseleave = () => {\n closeBtn.style.color = theme.textPrimary;\n closeBtn.style.transform = 'scale(1)';\n };\n closeBtn.onclick = () => {\n emit('popup_clicked', surveyId, {action: 'close_icon'});\n onClose();\n };\n closeBtn.style.flex = '0 0 auto';\n closeBtn.style.marginLeft = 'auto';\n header.appendChild(closeBtn);\n\n // Barra de progreso (\"Question X of Y\" + barra), espejo de LineProgressQuestion (MagicSurvey).\n // `undefined` en el init deja decidir a la plataforma (style.showProgressBar).\n let progressEnabled = options?.showProgressBar === true;\n let progressShowUnit = true;\n let progressUnit: 'percentage' | 'fraction' = 'fraction';\n\n const progressEl = document.createElement('div');\n progressEl.id = 'dd-progress';\n progressEl.className = 'deepdots-progress';\n // Sin padding horizontal propio: se alinea con el título y con el contenido. El vertical es\n // el mismo arriba y abajo, y del mismo valor que el padding de la tarjeta.\n progressEl.style.cssText = 'display:none; flex-direction:column; gap:8px; width:100%; flex:0 0 auto; padding:16px 0; box-sizing:border-box;';\n const progressHead = document.createElement('div');\n progressHead.className = 'deepdots-progress-head';\n progressHead.style.cssText = 'display:flex; flex-direction:row; justify-content:space-between; align-items:center; width:100%; gap:8px;';\n const progressLabel = document.createElement('span');\n progressLabel.id = 'dd-progress-label';\n progressLabel.style.cssText = 'font-size:13px; line-height:1.2;';\n // \"Question 1\" en negrita y \"of 3\" en regular gris, como el mockup.\n const progressCurrent = document.createElement('span');\n progressCurrent.id = 'dd-progress-current';\n progressCurrent.style.cssText = `font-weight:700; color:${theme.textPrimary};`;\n const progressTotal = document.createElement('span');\n progressTotal.id = 'dd-progress-total';\n progressTotal.style.cssText = `font-weight:400; color:${theme.textMuted};`;\n progressLabel.appendChild(progressCurrent);\n progressLabel.appendChild(progressTotal);\n const progressFollowUp = document.createElement('span');\n progressFollowUp.id = 'dd-progress-followup';\n progressFollowUp.textContent = labels.followUp;\n progressFollowUp.style.cssText = 'display:none; font-size:12px; font-weight:600; color:#fff; background:rgba(59,130,246,0.44); border-radius:999px; padding:2px 10px;';\n progressHead.appendChild(progressLabel);\n progressHead.appendChild(progressFollowUp);\n const progressTrack = document.createElement('div');\n progressTrack.className = 'deepdots-progress-track';\n progressTrack.style.cssText = `width:100%; height:4px; border-radius:999px; background:${theme.progressTrack}; overflow:hidden;`;\n const progressBar = document.createElement('div');\n progressBar.id = 'dd-progress-bar';\n progressBar.style.cssText = 'height:100%; width:0%; background:#22C55E; border-radius:999px; transition:width 450ms ease;';\n progressTrack.appendChild(progressBar);\n progressEl.appendChild(progressHead);\n progressEl.appendChild(progressTrack);\n\n function updateProgress(p: { progress?: number; total?: number; completed?: boolean; followup?: boolean }) {\n const total = typeof p.total === 'number' ? p.total : 0;\n const progress = typeof p.progress === 'number' ? p.progress : 0;\n if (!progressEnabled || onStartPage || p.completed || total <= 1) {\n progressEl.style.display = 'none';\n // Sin barra, el hueco cabecera→pregunta lo pone el contenido.\n containerContent.style.paddingTop = '16px';\n return;\n }\n progressEl.style.display = 'flex';\n // Con barra, el hueco inferior ya lo pone el bloque de progreso: no duplicarlo.\n containerContent.style.paddingTop = '0';\n // La barra usa el valor real (las follow-up suman +0.5 y avanzan media casilla); la\n // etiqueta redondea hacia abajo, porque una follow-up es un paso DENTRO de la misma\n // pregunta y no la siguiente.\n const current = Math.min(total, Math.max(1, progress + 1));\n const pct = Math.min(100, Math.max(0, (current / total) * 100));\n progressBar.style.width = `${pct}%`;\n const label = Math.min(total, Math.max(1, Math.floor(progress) + 1));\n progressLabel.style.display = progressShowUnit ? 'block' : 'none';\n if (progressUnit === 'percentage') {\n progressCurrent.textContent = `${Math.round(pct)}%`;\n progressTotal.textContent = '';\n } else {\n progressCurrent.textContent = `${labels.question} ${label}`;\n progressTotal.textContent = ` ${labels.of} ${total}`;\n }\n progressFollowUp.style.display = p.followup ? 'inline-block' : 'none';\n }\n\n ensureMagicFeedbackStyles(popup);\n // Después del CSS del paquete y antes del CSS del host, que sigue teniendo la última palabra.\n ensureSurveyAlignmentStyles();\n ensureSpinnerStyles(popup);\n ensureResponsiveStyles(popup);\n // El último en entrar: el CSS del host gana sobre todo lo anterior.\n ensureCustomCss(options?.surveyCss);\n\n const containerContent = document.createElement('div');\n containerContent.id = 'dd-content';\n // Estaba escrito 'conetent'; el HTML del WebView siempre usó la forma correcta. Con\n // `surveyCss` esta clase pasa a ser superficie pública, así que las dos rutas deben coincidir.\n containerContent.className = 'deepdots-popup-container-content';\n // El popup es una columna: solo el bloque de preguntas hace scroll, para que el footer con\n // las acciones quede siempre pegado al borde inferior (antes vivía dentro del área\n // scrollable y en un survey largo había que bajar hasta el final para ver \"Send\").\n // Sin sangrado extra: el contenido se alinea con el título y con la barra de progreso\n // (antes sumaba 20px a los lados sobre el padding de la tarjeta). `overflow:hidden` deja\n // el scroll en `main`, no en el contenedor.\n containerContent.style.cssText = 'display:flex; flex-direction:column; flex:1 1 auto; min-height:0; padding:16px 0 0 0; overflow:hidden;'\n\n // Sección principal (main) - Contenedor formulario + spinner\n const main = document.createElement('div');\n main.id = 'dd-main';\n main.className = 'deepdots-popup-main';\n main.style.cssText = 'display:flex; flex-direction:column; width:100%; flex:1 1 auto; min-height:0; overflow-y:auto;';\n\n const formWrapper = document.createElement('div');\n formWrapper.id = 'dd-form-wrapper';\n formWrapper.style.cssText = 'width:100%; flex: 1 1 auto;';\n\n // Contenedor de aviso de error de validación\n const errorHint = document.createElement('div');\n errorHint.id = 'dd-error';\n errorHint.className = 'deepdots-error-hint';\n errorHint.style.cssText = `\n display: none;\n margin: 12px 0 0 0;\n padding: 10px 12px;\n border-radius: 6px;\n background: #FEF3C7; /* amber-100 */\n color: #92400E; /* amber-700 */\n border: 1px solid #FCD34D; /* amber-300 */\n font-size: 13px;\n `;\n errorHint.setAttribute('role', 'alert');\n errorHint.setAttribute('aria-live', 'polite');\n\n const spinnerEl = document.createElement('div');\n spinnerEl.className = 'mf-spinner';\n spinnerEl.setAttribute('role', 'status');\n spinnerEl.setAttribute('aria-label', labels.loadingAria);\n spinnerEl.innerHTML = '<div class=\"mf-spinner-circle\"></div>';\n spinnerEl.style.cssText = 'position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);';\n\n const formDivId = `magicfeedback-form-${surveyId}`;\n const formHost = document.createElement('div');\n formHost.id = formDivId;\n formHost.style.cssText = 'width:100%; visibility:hidden;';\n\n formWrapper.appendChild(spinnerEl);\n formWrapper.appendChild(formHost);\n main.appendChild(formWrapper);\n // Insertar el hint justo antes del footer\n main.appendChild(errorHint);\n\n // Sección footer (acciones) - botones en extremos\n const footer = document.createElement('div');\n footer.id = 'dd-footer';\n footer.className = 'deepdots-popup-footer';\n footer.setAttribute('data-actions-wrapper', 'true');\n footer.style.cssText = 'display:flex; flex-direction: row-reverse ;justify-content:space-between; align-items:center; gap:4px; margin-top:auto; width:100%; padding-top:12px;';\n\n // Botones\n const backButton = document.createElement('button');\n backButton.id = 'dd-back';\n backButton.className = 'dd-nav-btn';\n backButton.textContent = actionLabels.back;\n // Secundario como botón de texto: sin borde, fondo ni sombra, para que el primario sea\n // la única CTA con peso visual.\n backButton.style.cssText = `\n background: transparent;\n color: ${theme.textMuted};\n border: none;\n min-height: 44px;\n padding: 12px 24px;\n border-radius: 999px; /* píldora, como el Button de Material3 en KMP */\n cursor: pointer;\n font-size: 15px;\n font-weight: 600;\n transition: filter .15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n `;\n backButton.onmouseenter = () => {\n backButton.style.filter = 'brightness(0.9)';\n }\n backButton.onmouseleave = () => {\n backButton.style.filter = 'brightness(1)';\n }\n backButton.onclick = () => {\n emit('popup_clicked', surveyId, {action: 'back'});\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (formInstance as any)?.back?.();\n };\n\n // Boton start survyes, solo aprece cuando la encuesta empieza con mensaje de inicio\n // Width de 100% para que ocupe todo el espacio disponible\n const startButton = document.createElement('button');\n startButton.id = 'dd-start';\n startButton.className = 'dd-nav-btn';\n startButton.textContent = actionLabels.start;\n startButton.style.cssText = `\n background: #1E293B;\n color: #fff;\n border: none;\n min-height: 44px;\n padding: 12px 24px;\n border-radius: 999px; /* píldora, como el Button de Material3 en KMP */\n cursor: pointer;\n font-size: 15px;\n font-weight: 600;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: filter .15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n `;\n startButton.onclick = () => {\n emit('popup_clicked', surveyId, {action: 'start_survey'});\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (formInstance as any)?.startForm?.();\n onStartPage = false;\n updateProgress({progress: formInstance?.progress, total: formInstance?.total});\n };\n\n // Botón cerrar popup, solo aparece al terminar la encuesta\n // Width de 100% para que ocupe todo el espacio disponible\n const closeButton = document.createElement('button');\n closeButton.id = 'dd-complete';\n closeButton.className = 'dd-nav-btn';\n closeButton.textContent = actionLabels.complete;\n closeButton.style.cssText = `\n background: #1E293B;\n color: #fff;\n border: none;\n min-height: 44px;\n padding: 12px 24px;\n border-radius: 999px; /* píldora, como el Button de Material3 en KMP */\n cursor: pointer;\n font-size: 15px;\n font-weight: 600;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: filter .15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n `;\n closeButton.onmouseenter = () => {\n closeButton.style.filter = 'brightness(0.9)';\n }\n closeButton.onmouseleave = () => {\n closeButton.style.filter = 'brightness(1)';\n }\n closeButton.onclick = () => {\n emit('popup_clicked', surveyId, {action: 'complete'});\n onClose();\n };\n\n // Botón send, si es primera pagina ocupara el espacio completo pero si no estara al lado derecho\n const submitButton = document.createElement('button');\n submitButton.id = 'dd-submit';\n submitButton.className = 'dd-nav-btn';\n submitButton.textContent = actionLabels.accept;\n submitButton.style.cssText = `\n background: #1E293B;\n color: #fff;\n border: none;\n min-height: 44px;\n padding: 12px 24px;\n border-radius: 999px; /* píldora, como el Button de Material3 en KMP */\n cursor: pointer;\n font-size: 15px;\n font-weight: 600;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: filter .15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n `;\n submitButton.onclick = () => {\n if (!surveyCompletedEmitted) {\n emit('popup_clicked', surveyId, {action: 'manual_send'});\n // Dispara envío nativo si existe\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (formInstance as any)?.send?.();\n }\n };\n\n /**\n * Re-resuelve todos los textos del chrome para `lang`. Se llama cuando el survey ya cargó\n * y se conoce su idioma. Las etiquetas que configure la plataforma por popup\n * (`actions.*.label`) siguen ganando: `resolveActionLabels` solo rellena las que falten.\n * El contador de progreso no se toca aquí porque `updateProgress` lo repinta justo después\n * leyendo `labels`.\n */\n function applyLabels(lang: string): void {\n labels = getLabels(lang);\n actionLabels = resolveActionLabels(actions, lang);\n backButton.textContent = actionLabels.back;\n startButton.textContent = actionLabels.start;\n closeButton.textContent = actionLabels.complete;\n submitButton.textContent = actionLabels.accept;\n closeBtn.setAttribute('aria-label', labels.closeAria);\n spinnerEl.setAttribute('aria-label', labels.loadingAria);\n progressFollowUp.textContent = labels.followUp;\n // El survey ya se ha volteado a sí mismo: el chrome tiene que acompañarlo.\n direction = directionFor(lang);\n popup.setAttribute('dir', direction);\n }\n\n backButton.style.display = 'none';\n startButton.style.display = 'none';\n submitButton.style.display = 'none';\n closeButton.style.display = 'none';\n\n // Insertar botones dentro del footer en orden visual (row-reverse deja primario a la derecha)\n footer.appendChild(submitButton);\n footer.appendChild(backButton);\n footer.appendChild(closeButton);\n footer.appendChild(startButton);\n\n // Añadir footer al main y main al containerContent\n containerContent.appendChild(main);\n // El footer va fuera del área scrollable, como hermano de `main`.\n footer.style.flex = '0 0 auto';\n containerContent.appendChild(footer);\n\n // Helper para controlar visibilidad de botones según estado\n type ViewState = 'loading' | 'start' | 'in_progress_first' | 'in_progress_next' | 'completed' | 'error';\n\n function updateButtons(state: ViewState) {\n // Por defecto, ocultar todos\n backButton.style.display = 'none';\n startButton.style.display = 'none';\n submitButton.style.display = 'none';\n closeButton.style.display = 'none';\n // Reset widths por estado\n backButton.style.width = '';\n startButton.style.width = '';\n submitButton.style.width = '';\n closeButton.style.width = '';\n\n switch (state) {\n case 'loading':\n // Footer se ocultará desde setLoading\n break;\n case 'start':\n // Solo botón Start a ancho completo\n startButton.style.display = 'inline-flex';\n startButton.style.width = '100%';\n break;\n case 'in_progress_first':\n // Solo botón Send (lado derecho), ancho auto\n submitButton.style.display = 'inline-flex';\n submitButton.style.width = '';\n setLoading(false);\n break;\n case 'in_progress_next':\n // Mostrar Back (izquierda) + Send (derecha)\n backButton.style.display = 'inline-flex';\n submitButton.style.display = 'inline-flex';\n setLoading(false);\n break;\n case 'completed':\n // Mostrar Close/Complete a ancho completo como acción principal\n closeButton.style.display = 'inline-flex';\n closeButton.style.width = '100%';\n setLoading(false);\n break;\n case 'error':\n // En error, permitir cerrar (ancho auto)\n // closeButton.style.display = 'inline-flex';\n setLoading(false);\n break;\n }\n }\n\n /** Estado de navegación según la profundidad recorrida (no según `total`). */\n function updateNavButtons() {\n updateButtons(pageDepth > 0 ? 'in_progress_next' : 'in_progress_first');\n }\n\n /**\n * Pantalla final con el `successMessage` configurado en la plataforma. La pinta el popup y\n * no `@magicfeedback/native`, cuyo `renderSuccess` usa `textContent` (el mensaje es HTML del\n * editor: imagen + texto) y cuyo fallback es un literal que ignora el estilo del survey.\n * Va por innerHTML igual que `renderStartMessage` con el mensaje de inicio.\n */\n let successMessageHtml = '';\n function showSuccessScreen() {\n progressEl.style.display = 'none';\n formWrapper.style.display = 'none';\n let done = main.querySelector('.deepdots-success') as HTMLElement | null;\n if (!done) {\n done = document.createElement('div');\n done.className = 'deepdots-success';\n done.style.cssText = `width:100%; text-align:center; padding:24px 0; color:${theme.textPrimary};`;\n main.insertBefore(done, errorHint);\n }\n done.innerHTML = successMessageHtml || '<p>Thank you for your feedback!</p>';\n done.querySelectorAll('img').forEach((img) => {\n img.style.cssText = 'max-width:100%; height:auto; margin:0 auto 16px auto; display:block;';\n });\n }\n\n // Ensamblar popup\n popup.appendChild(header);\n popup.appendChild(progressEl);\n popup.appendChild(containerContent);\n\n container.innerHTML = '';\n container.appendChild(popup);\n container.style.display = 'flex';\n // Apertura diferida: el contenedor entra en el layout (el survey necesita medidas reales\n // para pintarse) pero invisible y sin capturar clics, hasta que `reveal()` lo enseñe.\n container.style.visibility = 'hidden';\n container.style.pointerEvents = 'none';\n container.style.justifyContent = pos.justifyContent;\n container.style.alignItems = pos.alignItems;\n container.style.background = pos.background;\n if (pos.padding) container.style.padding = pos.padding;\n\n // ── Apertura diferida ────────────────────────────────────────────────────────────────\n // El spinner de apertura es lo que el cliente reporta como mala sensación: medido, está\n // ~310 ms en pantalla esperando el JSON del survey. En vez de enseñar la espera, se espera\n // en silencio y se abre el popup ya montado.\n let revealed = false;\n let revealTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reveal() {\n if (revealed) return;\n revealed = true;\n if (revealTimer !== null) {\n clearTimeout(revealTimer);\n revealTimer = null;\n }\n // Si el popup se cerró durante la espera, este contenedor ya no es suyo: `hide()` lo ha\n // vaciado y puede estar sirviendo a otra apertura. Revelarlo aquí la sacaría antes de\n // tiempo.\n if (!container.contains(popup)) return;\n container.style.visibility = 'visible';\n container.style.pointerEvents = '';\n }\n\n /**\n * Revela en cuanto el popup está pintado del todo. Se mira el popup entero, no solo el\n * formulario, porque el logo del survey se inserta sobre la barra de progreso y es el que más\n * desplaza el contenido al entrar.\n */\n function revealWhenPainted() {\n if (revealed) return;\n revealWhenImagesLoaded(popup, reveal);\n }\n\n revealTimer = setTimeout(reveal, REVEAL_TIMEOUT_MS);\n\n // Gestión dinámica de loading\n function setLoading(isLoading: boolean) {\n spinnerEl.style.display = isLoading ? 'flex' : 'none';\n // El spinner no tapa el survey (28px en posición absoluta), así que sin esto el usuario\n // puede seguir cambiando de opción mientras se envía la página — y ese cambio ya no\n // viaja con ella. Se bloquea el wrapper, que es el elemento estable: el div de dentro lo\n // sustituye `@magicfeedback/native` por su propio contenedor al generar el survey.\n setSurveyBusy(formWrapper, isLoading);\n if (!isLoading) {\n formHost.style.visibility = 'visible';\n // Fin de la carga, sea por survey cargado o por error: el popup ya puede verse.\n // Entre páginas del survey es no-op, porque `reveal` solo actúa la primera vez.\n revealWhenPainted();\n }\n // Ocultar totalmente los botones cuando está cargando\n footer.style.display = isLoading ? 'none' : 'flex';\n // Ajustar estados de los botones por si se muestran\n backButton.disabled = isLoading;\n startButton.disabled = isLoading;\n closeButton.disabled = isLoading;\n submitButton.disabled = isLoading;\n submitButton.style.opacity = isLoading ? '0.6' : '1';\n submitButton.style.cursor = isLoading ? 'not-allowed' : 'pointer';\n // No sobrescribir el estado de botones al finalizar la carga.\n if (isLoading) {\n updateButtons('loading');\n }\n }\n\n // Estado inicial\n setLoading(true);\n\n // Entorno navegador\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n // Referencia instancia formulario\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let formInstance: any = null;\n\n try {\n if (!magicfeedback || typeof magicfeedback.form !== 'function') {\n sdkWarn('[MagicFeedback] form() no disponible. Fallback manual.');\n setLoading(false);\n return;\n }\n magicfeedback.init({\n debug: true,\n env: env === 'production' ? 'prod' : 'dev'}\n );\n // Contrato Fase 1 §5: profile = identidad del usuario; metadata = contexto de tracking (session_id + user_id)\n const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);\n formInstance = magicfeedback.form(surveyId, productId, profile, metadata);\n\n interface TypedGenerateOptions {\n addButton: boolean;\n getMetaData: boolean;\n addSuccessScreen: boolean;\n lang?: string;\n onLoadedEvent?: (args: {\n formData: FormData,\n lang?: string,\n progress?: number, total?: number\n }) => void;\n beforeSubmitEvent?: () => void;\n afterSubmitEvent?: (args: { error?: string, completed: boolean, progress: number, total: number, followup?: boolean }) => void;\n onBackEvent?: (args: { error?: string, progress: number, total: number, followup: boolean }) => void;\n }\n\n const generateOptions: TypedGenerateOptions = {\n addButton: false,\n getMetaData: true,\n // La pantalla final la pinta el popup: `renderSuccess` de @magicfeedback/native usa\n // textContent, así que el mensaje de la plataforma (HTML con imagen) no se vería, y\n // su fallback es un literal genérico que ignora `style.successMessage`.\n addSuccessScreen: false,\n // Surveys multi-idioma: native pide a la API este idioma (el del init o el del\n // dispositivo) y, si el survey no lo tiene, la API sirve el idioma por defecto.\n lang: options?.language,\n };\n generateOptions.onLoadedEvent = ({formData, lang}) => {\n // Idioma en el que native muestra el survey. Manda sobre el del init para que el\n // chrome no se quede en inglés delante de un survey traducido. Native < 2.3 no lo\n // envía: entonces el primero de `formData.lang` (el idioma por defecto).\n const shownLang = typeof lang === 'string' && lang.trim().length > 0 ? lang : undefined;\n const surveyLang = shownLang ?? (Array.isArray(formData?.lang)\n ? formData.lang.find((l) => typeof l === 'string' && l.trim().length > 0)\n : undefined);\n if (surveyLang) applyLabels(surveyLang);\n // Calcular altura disponible y aplicarla al main (restando header + footer + paddings)\n try {\n /*\n const headerHeight = header.getBoundingClientRect().height;\n const footerHeight = footer.getBoundingClientRect().height;\n const paddingY = 48; // 24px top + 24px bottom\n const viewportLimit = window.innerHeight * 0.8; // coincide con max-height del popup\n const available = viewportLimit - headerHeight - footerHeight - paddingY;\n if (available > 120) { // asegurar un mínimo razonable\n main.style.maxHeight = available + 'px';\n } */\n } catch {\n // silencioso\n }\n // Personalización del popup basada en formData.style\n const s = formData?.style;\n if (s && !stylesInjected) {\n stylesInjected = true;\n if (s.successMessage) successMessageHtml = s.successMessage;\n // La barra de progreso la decide el host (init) y, si no se pronuncia, la plataforma.\n if (options?.showProgressBar === undefined) progressEnabled = s.showProgressBar === true;\n if (s.showProgressUnit !== undefined) progressShowUnit = s.showProgressUnit !== false;\n if (s.progressUnit) progressUnit = s.progressUnit;\n if (s.loadingBarColor) {\n progressBar.style.background = s.loadingBarColor;\n progressFollowUp.style.background = s.loadingBarColor;\n }\n // Fondo del contenedor popup\n if (s.boxBackgroundColor) {\n popup.style.background = s.boxBackgroundColor;\n }\n // Alineación del contenido principal\n if (s.contentAlign) {\n // 'top' => start, 'center' => center\n main.style.justifyContent = s.contentAlign === 'center' ? 'center' : 'flex-start';\n }\n // Botón primario (submit, start)\n // El survey pinta sus controles con `--mf-primary`, que sale de `primaryColor`;\n // si la integración solo configura el del botón, el popup mezclaría el color de\n // la marca con el gris azulado por defecto del paquete.\n applySurveyPrimaryColor(popup, s);\n if (s.buttonPrimaryColor) {\n submitButton.style.background = s.buttonPrimaryColor;\n submitButton.style.border = 'none';\n submitButton.style.color = '#fff';\n\n startButton.style.background = s.buttonPrimaryColor;\n startButton.style.border = 'none';\n startButton.style.color = '#fff';\n\n closeButton.style.background = s.buttonPrimaryColor;\n closeButton.style.border = 'none';\n closeButton.style.color = '#fff';\n }\n // Botón secundario (back) — outlined: fondo blanco, letra y borde del color secundario\n if (s.buttonSecondaryColor) {\n // Botón de texto: solo tiñe la etiqueta, sin recuperar fondo ni borde.\n backButton.style.background = 'transparent';\n backButton.style.color = s.buttonSecondaryColor;\n backButton.style.border = 'none';\n }\n // El logo va encima de la barra de progreso, como hermano del header.\n insertPopupLogo(popup, progressEl, s, 'deepdots-popup-logo');\n\n if (s.startMessage && s.startMessage !== '') {\n // Si hay mensaje de inicio, mostrar botón Start inicialmente\n sdkLog(s.startMessage);\n onStartPage = true;\n updateButtons('start');\n } else {\n // Si no hay mensaje de inicio, mostrar estado de primera página (solo Send)\n updateButtons('in_progress_first');\n }\n } else {\n // Sin estilos, asumir primera página normal\n updateButtons('in_progress_first');\n }\n\n // El total solo se conoce con el form ya montado.\n updateProgress({progress: formInstance.progress, total: formInstance.total});\n emit('popup_clicked', surveyId, {action: 'loaded'});\n setLoading(false); // hace visible el formulario y oculta el spinner\n };\n generateOptions.beforeSubmitEvent = () => {\n setLoading(true);\n emit('popup_clicked', surveyId, {action: 'before_submit'});\n };\n generateOptions.afterSubmitEvent = ({error, completed, total, progress, followup}) => {\n // No cambiar estado de loading aquí; lo gestiona cada transición\n // Normalizar el error a texto seguro\n const errText = error ? (typeof error === 'string' ? error : ((error as unknown as {message?: string}).message ?? String(error))) : '';\n if (errText) {\n // Desactivar loading para que se vean los botones\n setLoading(false);\n // Caso específico: error de pregunta obligatoria\n if (errText.toLowerCase().includes('no response')) {\n errorHint.textContent = labels.errorRequired;\n errorHint.style.display = 'block';\n emit('popup_clicked', surveyId, {action: 'validation_error_required'});\n // La página no ha cambiado: el estado de navegación se queda como estaba.\n updateNavButtons();\n return;\n }\n // Otros errores: mostrar mensaje genérico y permitir cerrar\n errorHint.textContent = labels.errorSubmit;\n errorHint.style.display = 'block';\n emit('popup_clicked', surveyId, {action: 'submit_error', error: errText});\n // updateButtons('error');\n return;\n }\n // Limpiar hint si no hay error\n errorHint.style.display = 'none';\n setLoading(false);\n if (completed) {\n emit('survey_completed', surveyId, {action: 'completed'});\n surveyCompletedEmitted = true;\n showSuccessScreen();\n updateButtons('completed');\n updateProgress({progress, total, completed: true});\n return;\n }\n emit('popup_clicked', surveyId, {action: 'partial'});\n pageDepth++;\n updateNavButtons();\n updateProgress({progress, total, followup});\n };\n generateOptions.onBackEvent = ({progress, error, followup}) => {\n emit('popup_clicked', surveyId, {action: 'back'});\n // Con error (\"No page found\") no hubo navegación: la profundidad no se toca.\n if (!error) pageDepth = Math.max(0, pageDepth - 1);\n updateNavButtons();\n // onBackEvent no trae `total`: se lee del form, que es su dueño.\n updateProgress({progress, total: formInstance.total, followup});\n };\n\n // Ejecutar generación con opciones tipadas\n formInstance.generate(formDivId, generateOptions)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .catch((err: any) => {\n sdkError('[MagicFeedback] Error generating form:', err);\n setLoading(false);\n });\n } catch (e) {\n sdkError('[MagicFeedback] Exception initializing form:', e);\n setLoading(false);\n }\n}\n","/**\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 // El Modal se abre en cuanto hay popup, y es el HTML del WebView el que se revela solo\n // cuando el survey está pintado (apertura diferida): con el Modal transparente, hasta ese\n // momento no se ve nada, ni velo ni spinner. No se difiere el Modal con `onReady` porque\n // React Native no monta los hijos de un Modal cerrado: el WebView no llegaría a cargar y\n // el aviso no llegaría nunca. El host que quiera diferir su contenedor debe montar el\n // WebView invisible (ver el ejemplo de `ReactNativePopupRenderer`).\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\n/**\n * A session of the analytics channel as the Deepdots API sees it. Every session becomes ONE\n * feedback, and `sessionId` is what the API stores as `sdkSessionId` on it: the value to look\n * that feedback up with (`GET /feedbacks?filter={\"where\":{\"sdkSessionId\":\"<sessionId>\"}}`).\n * A session that closes again keeps that one feedback: the API updates it.\n * - `open`: the API acknowledged the session's first batch; its events keep accumulating.\n * - `closed`: the API acknowledged the session's closing batch (`completed: true`); the\n * feedback is created from it within seconds.\n */\nexport interface FeedbackSession {\n sessionId: string;\n status: 'open' | 'closed';\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: nothing is sent, and the payload is only\n * logged when `debug` is true.\n */\n analytics?: AnalyticsKeys;\n /**\n * Called when the analytics channel opens or closes a session (needs `analytics`), with the\n * id the Deepdots API stores as `sdkSessionId` on the feedback the session becomes: send it\n * to your backend to attach data to that feedback later. A session whose only batch is the\n * closing one reports just `closed`. `closed` does not come when the page unloads before the\n * closing request returns, for a session dropped after 30 min hidden, nor when the closing\n * request fails (the API closes those itself, later): their id already came with `open`. Key\n * by `sessionId`, not by order: the next session's `open` can arrive before the previous\n * one's `closed`. Same value as `getFeedbackSessionId()` while the session is open.\n */\n onFeedbackSession?: (session: FeedbackSession) => void;\n /**\n * IP geolocation (country/city) added to analytics. Default `true`. The lookup calls\n * third-party services (ipapi.co, then ipwho.is, then ipinfo.io as fallbacks, 3 s timeout\n * each) and only runs when `analytics` is configured, tracking is enabled and the 30-day\n * cache is missing or expired. Set `false` to never call them nor attach country/city.\n */\n geolocation?: boolean;\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 * Whether the SDK renders the popup \"modal\" (card + backdrop + positioning) around the survey.\n * Default `true`. Only affects React Native (the survey WebView HTML); the web DOM popup is\n * unaffected. With `false` the survey is delivered transparent and edge-to-edge so the host\n * mounts its own Modal/container and controls the styling — avoids the \"double modal\" when the\n * host already wraps the WebView. The survey stays fully functional (bridge, form, nav buttons,\n * close). Ver `ReactNativePopupRenderer`.\n */\n renderChrome?: boolean;\n /**\n * Barra de progreso (\"Question X of Y\" + barra) en la cabecera del popup. Si se omite,\n * manda el `showProgressBar` que la plataforma configura en el estilo del survey; `true` o\n * `false` lo fuerzan desde el host. Solo se pinta con más de una página, fuera de la\n * pantalla de inicio y antes de completar, igual que en la web de surveys.\n */\n showProgressBar?: boolean;\n /**\n * CSS propio del host, inyectado como último bloque de estilos del popup: gana en cascada\n * sobre el del SDK y sobre el de `@magicfeedback/native` sin tener que tocar ninguno. Es la\n * vía para reestilar el área de preguntas (enunciados, opciones, escalas) por integración,\n * en vez de cambiar los defaults que comparten todos los clientes.\n */\n surveyCss?: string;\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 * - `idle_timeout`: la pestaña vuelve a verse tras más de 30 min oculta (web). La sesión\n * anterior se cierra y se abre otra, como hace GA con su timeout de sesión.\n */\nexport type SessionEndReason =\n | 'page_hide'\n | 'background'\n | 'user_change'\n | 'tracking_disabled'\n | 'manual'\n | 'idle_timeout';\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 /**\n * App routes where it must NOT be shown. Wins over `path`: a route listed here\n * blocks the popup even if `path` matches it (so `path: ['/']` +\n * `excludedPaths: ['/cart']` = everywhere but the cart).\n * Same matching rules as `path`: an absolute URL matches the full href, an entry\n * starting with `/` matches any href containing it (this is what covers hash\n * routes like `/#/cart`), anything else matches the pathname exactly.\n */\n excludedPaths?: string[];\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 /**\n * Idiomas configurados en la integración del survey. `@magicfeedback/native` localiza\n * preguntas y placeholders con el primero, y el popup traduce su chrome con el mismo.\n */\n lang?: 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\n/** `TriggerConfig.value` también acepta string (id/nombre para click/event); para time/scroll se necesita un número. */\nfunction toNumericValue(value: number | string | undefined, fallback: number): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : fallback;\n}\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 = toNumericValue(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 = toNumericValue(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 {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 { DeepdotsEventType, PopupActions, PopupStyle } from '../types';\nimport type { PopupRenderer, PopupRenderOptions } from './renderer';\nimport { buildSurveyIdentity } from '../tracking/tracking-manager';\nimport { buildSurveyHtml } from '../ui/surveyHtml';\nimport { REVEAL_TIMEOUT_MS } from '../ui/reveal';\nimport { sdkWarn } from '../util/logger';\n\n/**\n * Margen que el aviso nativo de \"listo\" deja por detrás del techo que ya lleva el HTML. El\n * WebView se revela solo a `REVEAL_TIMEOUT_MS`; este respaldo solo debe entrar cuando el WebView\n * no llega ni a ejecutar el HTML (motor que no arranca, documento que no carga), que es el único\n * caso en el que el host se quedaría esperando para siempre.\n */\nexport const NATIVE_READY_GRACE_MS = 500;\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 * Se llama cuando el survey ya está pintado dentro del WebView, o pasado el techo de espera.\n *\n * Sirve para no enseñar un WebView en blanco: monta el WebView en cuanto llegue `onShow` (fuera\n * de pantalla o transparente) y ábrelo al recibir `onReady`. El HTML se revela solo de todas\n * formas, así que el host que no lo use sigue funcionando igual.\n */\n onReady?: (payload: { surveyId: string }) => 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: monta el WebView con `onShow` y enséñalo con `onReady`, para que el usuario no vea el\n * WebView arrancando en blanco (motor + bundle del CDN + fetch del survey).\n * ```tsx\n * const renderer = new ReactNativePopupRenderer({\n * onShow: (p) => setSurvey({ ...p, ready: false }),\n * onReady: () => setSurvey((s) => (s ? { ...s, ready: true } : s)),\n * onHide: () => setSurvey(null),\n * });\n * sdk.setRenderer(renderer);\n * // …\n * // ⚠️ Con `<Modal visible={survey.ready}>` esto NO funciona: React Native no monta los hijos\n * // de un Modal cerrado, así que el WebView no empezaría a cargar y `ready` no llegaría nunca.\n * // El WebView tiene que estar montado y solo invisible.\n * {survey ? (\n * <View style={[StyleSheet.absoluteFill, { opacity: survey.ready ? 1 : 0 }]}\n * pointerEvents={survey.ready ? 'auto' : 'none'}>\n * <WebView source={{ html: survey.html }}\n * onMessage={(e) => renderer.handleMessage(e.nativeEvent.data)} />\n * </View>\n * ) : null}\n * ```\n *\n * Quien prefiera no complicarse puede ignorar `onReady` y abrir su Modal en `onShow`: el HTML se\n * revela solo igualmente, así que el usuario verá el contenedor del host unos cientos de ms antes\n * que la tarjeta, pero nunca el spinner.\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 private readyEmitted = false;\n private readyTimer: ReturnType<typeof setTimeout> | null = null;\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 renderChrome?: boolean,\n options?: PopupRenderOptions,\n ): void {\n this.emitFn = emit;\n this.onCloseFn = onClose;\n this.currentSurveyId = surveyId;\n this.partialEmitted = false;\n this.readyEmitted = false;\n this.armReadyFallback(surveyId);\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 // renderChrome:false (init) → HTML sin tarjeta/overlay: el host lo envuelve en su Modal.\n chrome: renderChrome,\n title: options?.title,\n showProgressBar: options?.showProgressBar,\n surveyCss: options?.surveyCss,\n language: options?.language,\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.clearReadyFallback();\n this.options.onHide?.();\n this.currentSurveyId = null;\n }\n\n /** Respaldo del aviso de listo por si el WebView no llega a ejecutar el HTML. */\n private armReadyFallback(surveyId: string): void {\n this.clearReadyFallback();\n if (!this.options.onReady) return;\n this.readyTimer = setTimeout(() => {\n this.readyTimer = null;\n this.emitReady(surveyId);\n }, REVEAL_TIMEOUT_MS + NATIVE_READY_GRACE_MS);\n }\n\n private clearReadyFallback(): void {\n if (this.readyTimer !== null) {\n clearTimeout(this.readyTimer);\n this.readyTimer = null;\n }\n }\n\n /** Idempotente: el WebView puede avisar a la vez que vence el respaldo. */\n private emitReady(surveyId: string): void {\n if (this.readyEmitted) return;\n this.readyEmitted = true;\n this.clearReadyFallback();\n this.options.onReady?.({ surveyId });\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 'ready':\n // El survey ya está pintado dentro del WebView. NO es interacción del usuario: no debe\n // marcar el popup como PARTIAL.\n this.emitReady(surveyId);\n break;\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 // Solo reporta el estado COMPLETED. NO cierra: el WebView acaba de pintar la pantalla\n // final del survey y cerrar aquí la hacía invisible. El cierre llega después con\n // `popup_close`, cuando el usuario pulsa el botón de completar.\n this.emitFn('survey_completed', surveyId);\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 type { IdentityAnswer } from '../tracking/tracking-manager';\nimport { buildFontFaceCss, buildFontFamilyValue } from './font';\nimport type { PopupActions, PopupFont } from '../types';\nimport { LABELS, RESOLVE_LOCALE_JS, RTL_LOCALES, directionFor, getLabels, resolveActionLabels } from '../i18n/labels';\nimport { REVEAL_TIMEOUT_MS } from './reveal';\nimport { PRIMARY_COLOR_JS } from './surveyPalette';\nimport { BUSY_JS } from './busy';\nimport magicfeedbackCss from '../assets/style.css';\n\n/**\n * Apertura diferida, versión que corre DENTRO del WebView: espejo ES5 de `revealWhenPainted`\n * (`reveal.ts`). El popup se monta invisible y se enseña cuando el survey está pintado y sus\n * imágenes han llegado, en vez de enseñar el spinner girando; `timeoutMs` es el techo para lo\n * que no llegue nunca. En RN esto pesa más que en web, porque a la espera del survey se le suma\n * el arranque del WebView y la descarga del bundle del CDN.\n *\n * Va como texto (y no como import) porque el WebView no comparte módulos con el bundle del SDK.\n * `surveyHtml.reveal.test.ts` lo evalúa para probar el comportamiento, no solo el string.\n */\nexport const REVEAL_JS = `\nfunction ddCreateReveal(popup, onReveal, timeoutMs){\n var revealed=false, timer=null;\n function reveal(){\n if(revealed){ return; }\n revealed=true;\n if(timer){ clearTimeout(timer); timer=null; }\n onReveal();\n }\n function revealWhenPainted(){\n if(revealed){ return; }\n var imgs=popup.querySelectorAll('img'), pending=[];\n for(var i=0;i<imgs.length;i++){ if(!imgs[i].complete){ pending.push(imgs[i]); } }\n if(!pending.length){ reveal(); return; }\n var left=pending.length;\n function onSettled(){ left--; if(left<=0){ reveal(); } }\n for(var j=0;j<pending.length;j++){\n pending[j].addEventListener('load', onSettled);\n pending[j].addEventListener('error', onSettled);\n }\n }\n timer=setTimeout(reveal, timeoutMs);\n return { reveal: reveal, whenPainted: revealWhenPainted };\n}`;\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 * Renderiza el \"modal\" del popup (tarjeta con fondo/sombra/bordes + overlay + posicionamiento).\n * Default `true`. Con `false` el HTML es transparente y llena el contenedor: el host monta su\n * propio Modal/tarjeta y controla el estilo. El survey sigue siendo funcional (bridge, form,\n * botones y botón de cerrar). Pensado para React Native cuando el host gestiona el contenedor.\n */\n chrome?: boolean;\n /**\n * Título de la cabecera, a la izquierda del botón de cerrar. Es el `title` de la definición\n * del popup (`GET /sdk/{publicKey}/popups`), así que cambia de un popup a otro. Vacío es un\n * valor legítimo: la cabecera se queda solo con la X. NO cae al título del survey.\n */\n title?: string;\n /**\n * Barra de progreso (\"Question X of Y\" + barra) bajo la cabecera. `undefined` respeta el\n * `showProgressBar` que configure la plataforma en el estilo del survey; `true`/`false` lo\n * fuerzan desde el host. Solo se pinta con más de una página y fuera de la pantalla de inicio.\n */\n showProgressBar?: boolean;\n /**\n * CSS del host, inyectado como último bloque de estilos: gana en cascada sobre el del SDK\n * y sobre el de `@magicfeedback/native` sin tener que tocar ninguno de los dos. Es la vía\n * para reestilar el área de preguntas (enunciados, opciones, escalas) por integración.\n */\n surveyCss?: string;\n /**\n * Idioma con el que arranca el chrome (botones, progreso, aria-labels, errores). Es el\n * idioma resuelto en `init()`. Dentro del WebView lo pisa el idioma del survey\n * (`formData.lang[0]`), y ambos los pisa `actions.*.label` de la API.\n */\n language?: string;\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\n/**\n * Serializa un valor para incrustarlo dentro de un `<script>`. `JSON.stringify` no escapa `<`,\n * así que un `</script>` dentro de un texto de la API (título, etiqueta de botón, metadata)\n * cerraría el bloque y el resto se interpretaría como HTML. Escapa también los separadores de\n * línea U+2028/U+2029, que son JSON válido pero rompen un literal de JavaScript.\n */\nfunction jsonForScript(value: unknown): string {\n return JSON.stringify(value ?? null)\n .replace(/</g, '\\\\u003C')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\n/** Devuelve el label de la API solo si trae contenido; en blanco cuenta como \"no configurado\". */\nfunction trimmedOrNull(value: string | undefined): string | null {\n const trimmed = typeof value === 'string' ? value.trim() : '';\n return trimmed ? value as string : null;\n}\n\nexport function buildSurveyHtml(opts: BuildSurveyHtmlOptions): string {\n const env = opts.env === 'development' ? 'dev' : 'prod';\n // Misma versión que la dep npm del bundle web, para que las dos rutas pinten igual.\n const version = opts.version ?? '2.2.31';\n const cdn = `https://cdn.jsdelivr.net/npm/@magicfeedback/native@${version}/dist/magicfeedback-sdk.browser.js`;\n const sid = jsonForScript(opts.surveyId);\n const pid = jsonForScript(opts.productId);\n const profileJson = jsonForScript(opts.profile ?? []);\n const metaJson = jsonForScript(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 textMuted = isDark ? '#9ca3af' : '#6b7280';\n const trackBg = isDark ? '#3f3f46' : '#e5e7eb';\n const pos = POSITION_MAP[opts.position ?? 'center'] ?? POSITION_MAP.center;\n\n // chrome:false → el host envuelve el survey en su propio Modal/tarjeta. El HTML se vuelve\n // transparente y llena el contenedor (sin fondo/sombra/borde/overlay/posicionamiento propios);\n // así se evita el \"doble modal\" cuando el host ya monta el suyo. Survey funcional intacto.\n const chrome = opts.chrome !== false;\n const bodyBg = chrome ? pos.background : 'transparent';\n const bodyPadding = chrome ? pos.padding : '0';\n const bodyDisplay = chrome ? 'flex' : 'block';\n const cardBg = chrome ? popupBg : 'transparent';\n const cardShadow = chrome ? '0 4px 6px rgba(0,0,0,0.1)' : 'none';\n const cardRadius = chrome ? '8px' : '0';\n const cardMaxWidth = chrome ? '600px' : 'none';\n const cardWidth = chrome ? '90%' : '100%';\n const cardMinHeight = chrome ? '200px' : '0';\n // Con chrome el alto lo marca el contenido (hasta 90vh); sin chrome llena el contenedor del\n // host. En ambos casos hace falta un alto acotado para que el footer pueda quedar fijo abajo.\n const cardHeight = chrome ? 'auto' : '100%';\n const cardMaxHeight = chrome ? '90vh' : '100%';\n // En móvil la tarjeta se estrecha y se separa de los bordes de la ventana... salvo con\n // chrome:false, donde el contenedor es del host: ahí llena el ancho y NO añade los insets\n // de safe-area (el host ya los aplica; sumarlos otra vez dejaba un hueco muerto bajo el footer).\n const mobileCard = chrome\n ? `.deepdots-popup{width:calc(100% - 24px);max-width:calc(100% - 24px);border-radius:12px;padding:calc(16px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom)) 16px}`\n : `.deepdots-popup{width:100%;max-width:100%;border-radius:0;padding:16px}`;\n\n // El título viaja como literal JSON y se aplica con textContent: viene de la API y no debe\n // interpolarse en el HTML (mismo criterio anti-inyección que `font.family`).\n const titleJson = jsonForScript(opts.title ?? '');\n // CSS del host: va el último para ganar en cascada. Se corta en `</style>` porque el bloque\n // se cierra ahí y todo lo que siguiera pasaría a interpretarse como HTML.\n const customCss = (opts.surveyCss ?? '').replace(/<\\/\\s*style/gi, '<\\\\/style');\n // `null` = decide la plataforma (style.showProgressBar); true/false = lo fuerza el host.\n const progressPref = opts.showProgressBar === undefined ? 'null' : String(opts.showProgressBar);\n\n // Textos del chrome. Arrancan con el idioma del init; dentro del WebView se re-resuelven con\n // el idioma del survey (`formData.lang[0]`), que es el que la plataforma configura en la\n // integración y con el que `@magicfeedback/native` localiza preguntas y placeholders.\n const initLabels = getLabels(opts.language);\n const initActionLabels = resolveActionLabels(opts.actions, opts.language);\n const backLabel = jsonForScript(initActionLabels.back);\n const startLabel = jsonForScript(initActionLabels.start);\n const completeLabel = jsonForScript(initActionLabels.complete);\n const submitLabel = jsonForScript(initActionLabels.accept);\n // La tabla entera viaja con el HTML: el idioma del survey no se conoce hasta que carga.\n const labelsTableJson = jsonForScript(LABELS);\n // Los labels de la API van aparte para que sigan ganando después de re-resolver.\n const actionOverridesJson = jsonForScript({\n accept: trimmedOrNull(opts.actions?.accept?.label),\n start: trimmedOrNull(opts.actions?.start?.label),\n complete: trimmedOrNull(opts.actions?.complete?.label),\n back: trimmedOrNull(opts.actions?.back?.label),\n });\n const initLangJson = jsonForScript(opts.language ?? '');\n // Dirección del chrome. El survey se voltea solo desde 2.2.22 (`dir=\"rtl\"` en su contenedor);\n // el header/footer/progreso son nuestros y tienen que acompañarlo. Siempre explícito.\n const initDirection = directionFor(opts.language);\n const rtlLocalesJson = jsonForScript(RTL_LOCALES);\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}}\n/* Apertura diferida: ni la tarjeta ni el velo se pintan hasta que el survey está listo\n (\\`dd-ready\\`). Si el fondo entrara antes, en RN se vería el velo oscuro varios cientos de ms\n antes que el contenido, que es la misma mala sensación que el spinner. */\nbody{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:transparent;padding:${bodyPadding};box-sizing:border-box}\nbody.dd-ready{background:${bodyBg}}\n#dd-popup{visibility:hidden}\nbody.dd-ready #dd-popup{visibility:visible}\n.deepdots-popup{position:relative;display:flex;flex-direction:column;justify-content:flex-start;background:${cardBg};color-scheme:${colorScheme};border-radius:${cardRadius};padding:24px;box-shadow:${cardShadow};max-width:${cardMaxWidth};width:${cardWidth};min-height:${cardMinHeight};height:${cardHeight};max-height:${cardMaxHeight};box-sizing:border-box}\n.deepdots-popup-header{display:flex;justify-content:space-between;align-items:center;gap:12px;width:100%;flex:0 0 auto}\n/* Neutraliza la regla \\`.deepdots-popup h2\\` del CSS del survey (uppercase, centrado y\n margin-bottom 40px), pensada para los enunciados y no para el título de la cabecera. */\n#dd-title{margin:0;font-family:var(--deepdots-font,'Montserrat',inherit);font-size:17px;font-weight:600;line-height:1.3;color:${textPrimary};text-transform:none;text-align:start;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\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;flex:0 0 auto;margin-inline-start:auto}\n/* Progreso alineado con la cabecera: sin padding horizontal propio, lo marca la tarjeta.\n El vertical es el mismo arriba y abajo, y del mismo valor que el padding de la tarjeta. */\n.deepdots-progress{display:none;flex-direction:column;gap:8px;width:100%;flex:0 0 auto;padding:16px 0;box-sizing:border-box}\n.deepdots-progress-head{display:flex;flex-direction:row;justify-content:space-between;align-items:center;width:100%;gap:8px}\n#dd-progress-label{font-size:13px;line-height:1.2}\n/* \"Question 1\" en negrita y \"of 3\" en regular gris, como el mockup. */\n#dd-progress-current{font-weight:700;color:${textPrimary}}\n#dd-progress-total{font-weight:400;color:${textMuted}}\n#dd-progress-followup{display:none;font-size:12px;font-weight:600;color:#fff;background:rgba(59,130,246,0.44);border-radius:999px;padding:2px 10px}\n.deepdots-progress-track{width:100%;height:4px;border-radius:999px;background:${trackBg};overflow:hidden}\n#dd-progress-bar{height:100%;width:0%;background:#22C55E;border-radius:999px;transition:width 450ms ease}\n/* El popup es una columna: solo el bloque de preguntas hace scroll, para que el footer\n con las acciones quede siempre visible en el borde inferior (antes vivía dentro del\n área scrollable y había que bajar hasta el final para ver \"Send\"). */\n/* Sin sangrado extra: el contenido se alinea con el título y con la barra de progreso (antes\n sumaba 20px a los lados sobre el padding de la tarjeta y quedaba más adentro que la cabecera). */\n/* Sin barra de progreso, este padding-top es el que separa la cabecera de la pregunta. Con\n barra, el JS lo pone a 0 y el hueco lo aporta el padding inferior del bloque de progreso,\n de modo que el espacio por arriba y por abajo de la barra es idéntico. */\n.deepdots-popup-container-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:16px 0 0 0;overflow:hidden;box-sizing:border-box}\n/* El enunciado trae margin-top:20px del CSS de surveys, que sumaba al hueco de la barra y\n descompensaba la simetría. Solo se anula en el primero: entre preguntas sigue separando. */\n.deepdots-popup .magicfeedback-questions .magicfeedback-div:first-child .magicfeedback-label{margin-top:0}\n.deepdots-popup-main{display:flex;flex-direction:column;width:100%;flex:1 1 auto;min-height:0;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/* El margen lateral lo pone la tarjeta: el CSS del survey lo añade otra vez en el contenedor, en\n el formulario y en el bloque de cada pregunta, y eso deja el enunciado y las opciones más\n adentro que el logo, el título y la barra de progreso. Solo el lateral; el vertical separa las\n preguntas. Mismas reglas que en el popup DOM (renderPopup.ts). */\n.deepdots-popup .magicfeedback-container,\n.deepdots-popup .magicfeedback-form,\n.deepdots-popup .magicfeedback-div{padding-left:0;padding-right:0}\n#dd-logo{max-height:40px;max-width:100%;object-fit:contain;display:block;margin:12px 0 0 0}\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/* Pantalla final: el HTML del editor de la plataforma (imagen + texto), centrado. */\n.deepdots-success{display:none;width:100%;text-align:center;padding:24px 0;color:${textPrimary}}\n.deepdots-success img{max-width:100%;height:auto;margin:0 auto 16px auto;display:block}\n.deepdots-success p{margin:0;font-size:16px;font-weight:600;line-height:1.4}\n.deepdots-popup-footer{display:flex;flex-direction:row-reverse;justify-content:space-between;align-items:center;gap:4px;margin-top:auto;width:100%;padding-top:12px}\n.dd-nav-btn{display:none;border:none;min-height:44px;padding:12px 24px;border-radius:999px;cursor:pointer;font-size:15px;font-weight:600;align-items:center;justify-content:center;text-align:center}\n/* Secundario como botón de texto: sin borde ni fondo, para que el primario sea la única CTA. */\n#dd-back{background:transparent;color:${textMuted};border:none}\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 ${mobileCard}\n /* Apilados: la acción principal (Send/Start/Complete) arriba y Back debajo. El orden del\n DOM ya es ese, así que \\`column\\` (no \\`column-reverse\\`) es lo que lo respeta. */\n .deepdots-popup-footer{flex-direction:column;gap:4px}\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${customCss}\n</style>\n</head><body>\n<div class=\"${popupClass}\" id=\"dd-popup\" dir=\"${initDirection}\">\n <div class=\"deepdots-popup-header\">\n <h2 id=\"dd-title\" class=\"deepdots-popup-title\" hidden></h2>\n <button type=\"button\" id=\"dd-close\" aria-label=\"${initLabels.closeAria}\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M5 5L19 19M5 19L19 5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"butt\"/>\n </svg>\n </button>\n </div>\n <div class=\"deepdots-progress\" id=\"dd-progress\">\n <div class=\"deepdots-progress-head\">\n <span id=\"dd-progress-label\"><span id=\"dd-progress-current\"></span><span id=\"dd-progress-total\"></span></span>\n <span id=\"dd-progress-followup\">${initLabels.followUp}</span>\n </div>\n <div class=\"deepdots-progress-track\"><div id=\"dd-progress-bar\"></div></div>\n </div>\n <div class=\"deepdots-popup-container-content\" id=\"dd-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=\"${initLabels.loadingAria}\"><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>\n <div class=\"deepdots-popup-footer\" id=\"dd-footer\">\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-submit\"></button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-back\"></button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-complete\"></button>\n <button type=\"button\" class=\"dd-nav-btn\" id=\"dd-start\"></button>\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 var chromeOn=${chrome ? 'true' : 'false'};\n\n var popup=document.getElementById('dd-popup');\n var main=document.getElementById('dd-main');\n var content=document.getElementById('dd-content');\n var footer=document.getElementById('dd-footer');\n var spinner=document.getElementById('dd-spinner');\n var formHost=document.getElementById('mf');\n var formWrapper=document.getElementById('dd-form-wrapper');\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 var titleEl=document.getElementById('dd-title');\n var progressEl=document.getElementById('dd-progress');\n var progressLabel=document.getElementById('dd-progress-label');\n var progressCurrent=document.getElementById('dd-progress-current');\n var progressTotal=document.getElementById('dd-progress-total');\n var progressFollowUp=document.getElementById('dd-progress-followup');\n var progressBar=document.getElementById('dd-progress-bar');\n var closeEl=document.getElementById('dd-close');\n\n // --- i18n del chrome -------------------------------------------------------------------\n // La tabla completa viaja embebida porque el idioma del survey (\\`formData.lang[0]\\`) solo se\n // conoce ya dentro del WebView. \\`ddResolveLocale\\` es el espejo del \\`resolveLocale\\` de TS\n // (paridad cubierta por test).\n var DD_LABELS=${labelsTableJson};\n var DD_ACTION_OVERRIDES=${actionOverridesJson};\n ${RESOLVE_LOCALE_JS}\n var DD_LABELS_ACTIVE=DD_LABELS[ddResolveLocale(${initLangJson})];\n var DD_RTL=${rtlLocalesJson};\n function ddIsRtl(lang){ return DD_RTL.indexOf(ddResolveLocale(lang))!==-1; }\n function ddApplyLabels(lang){\n var next=DD_LABELS[ddResolveLocale(lang)];\n if(!next) return;\n DD_LABELS_ACTIVE=next;\n // El survey ya se ha volteado a sí mismo: el chrome tiene que acompañarlo.\n popup.setAttribute('dir', ddIsRtl(lang) ? 'rtl' : 'ltr');\n backBtn.textContent=DD_ACTION_OVERRIDES.back||next.back;\n startBtn.textContent=DD_ACTION_OVERRIDES.start||next.start;\n completeBtn.textContent=DD_ACTION_OVERRIDES.complete||next.complete;\n submitBtn.textContent=DD_ACTION_OVERRIDES.accept||next.accept;\n if(closeEl){ closeEl.setAttribute('aria-label',next.closeAria); }\n if(spinner){ spinner.setAttribute('aria-label',next.loadingAria); }\n progressFollowUp.textContent=next.followUp;\n }\n\n function setTitle(t){\n if(!t) return;\n titleEl.textContent=t;\n titleEl.hidden=false;\n }\n setTitle(${titleJson});\n\n // Estado de la barra de progreso. \\`progressPref\\` null = manda la plataforma (style).\n var progressPref=${progressPref};\n var progressEnabled=progressPref===true;\n var progressShowUnit=true;\n var progressUnit='fraction';\n var onStartPage=false;\n\n function updateProgress(p){\n var total=p&&typeof p.total==='number'?p.total:0;\n var progress=p&&typeof p.progress==='number'?p.progress:0;\n // Solo tiene sentido con más de una página, fuera de la pantalla de inicio y sin completar.\n if(!progressEnabled||onStartPage||p&&p.completed||total<=1){\n progressEl.style.display='none';\n // Sin barra, el hueco cabecera→pregunta lo pone el contenido.\n content.style.paddingTop='16px';\n return;\n }\n progressEl.style.display='flex';\n // Con barra, el hueco inferior ya lo pone el bloque de progreso: no duplicarlo.\n content.style.paddingTop='0';\n // Espejo de LineProgressQuestion (MagicSurvey): la barra usa el valor real (las follow-up\n // suman +0.5 y avanzan media casilla) y la etiqueta redondea hacia abajo, porque una\n // follow-up es un paso DENTRO de la misma pregunta, no la siguiente.\n var current=Math.min(total,Math.max(1,progress+1));\n var pct=Math.min(100,Math.max(0,(current/total)*100));\n progressBar.style.width=pct+'%';\n var label=Math.min(total,Math.max(1,Math.floor(progress)+1));\n progressLabel.style.display=progressShowUnit?'block':'none';\n if(progressUnit==='percentage'){\n progressCurrent.textContent=Math.round(pct)+'%';\n progressTotal.textContent='';\n } else {\n progressCurrent.textContent=DD_LABELS_ACTIVE.question+' '+label;\n progressTotal.textContent=' '+DD_LABELS_ACTIVE.of+' '+total;\n }\n progressFollowUp.style.display=p&&p.followup?'inline-block':'none';\n }\n\n backBtn.textContent=${backLabel};\n startBtn.textContent=${startLabel};\n completeBtn.textContent=${completeLabel};\n submitBtn.textContent=${submitLabel};\n\n closeEl.onclick=function(){ emitJSON('popup_close'); };\n backBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.back){ window.DeepdotsForm.back(); } };\n startBtn.onclick=function(){\n if(window.DeepdotsForm && window.DeepdotsForm.startForm){\n window.DeepdotsForm.startForm();\n onStartPage=false;\n updateProgress({ progress:window.DeepdotsForm.progress, total:window.DeepdotsForm.total, completed:false, followup:false });\n }\n };\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 // Mensaje final configurado en la plataforma (\\`style.successMessage\\`). Se guarda al cargar\n // y se pinta al completar. Va por innerHTML porque es HTML del editor de la plataforma\n // (imagen + texto), igual que ya hace \\`renderStartMessage\\` con el mensaje de inicio.\n var successMessageHtml='';\n function showSuccessScreen(){\n progressEl.style.display='none';\n var wrapper=document.getElementById('dd-form-wrapper');\n if(wrapper){ wrapper.style.display='none'; }\n var done=document.getElementById('dd-success');\n if(!done){\n done=document.createElement('div');\n done.id='dd-success';\n done.className='deepdots-success';\n main.insertBefore(done, errorHint);\n }\n done.innerHTML=successMessageHtml || '<p>Thank you for your feedback!</p>';\n done.style.display='block';\n }\n\n // Profundidad de navegación DENTRO del survey: +1 por página avanzada, -1 al volver.\n // Sustituye a la condición \\`total>1 && progress>0 && progress<total\\`, que escondía el\n // Back siempre que la siguiente pantalla era una follow-up dinámica: las follow-up no\n // entran en el grafo (suman +0.5 al progress y no tocan el total), así que un survey de\n // una pregunta con follow-up tiene total=1 y nunca cumplía \\`total>1\\`.\n var pageDepth=0;\n\n function updateNavButtons(){\n updateButtons(pageDepth>0?'in_progress_next':'in_progress_first');\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${PRIMARY_COLOR_JS}\n${BUSY_JS}\n${REVEAL_JS}\n // El popup se enseña cuando el survey está pintado; \\`ready\\` deja al host abrir su propio\n // Modal en ese momento en vez de montar un WebView en blanco.\n var ddReveal=ddCreateReveal(popup, function(){\n document.body.classList.add('dd-ready');\n emitJSON('ready');\n }, ${REVEAL_TIMEOUT_MS});\n\n function setLoading(isLoading){\n spinner.style.display=isLoading?'flex':'none';\n // El spinner no tapa el survey (28px en posicion absoluta), asi que sin esto el usuario\n // puede seguir cambiando de opcion mientras se envia la pagina, y ese cambio ya no viaja\n // con ella. Se bloquea el wrapper, que es el elemento estable: el div de dentro lo\n // sustituye @magicfeedback/native por su propio contenedor al generar el survey.\n ddSetSurveyBusy(formWrapper, isLoading);\n if(!isLoading){\n formHost.style.visibility='visible';\n // Fin de la carga, sea por survey cargado o por error. Entre páginas es no-op: la\n // revelación solo actúa la primera vez.\n ddReveal.whenPainted();\n }\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 // La pantalla final la pinta el popup: renderSuccess de @magicfeedback/native usa\n // textContent, así que el mensaje de la plataforma (HTML con imagen) no se vería, y su\n // fallback es un literal genérico que ignora style.successMessage.\n addSuccessScreen:false,\n // Surveys multi-idioma: native pide a la API este idioma (el del init o el del\n // dispositivo); si el survey no lo tiene, la API sirve el idioma por defecto.\n lang:${initLangJson}||undefined,\n onLoadedEvent:function(args){\n // Idioma en el que native muestra el survey: manda sobre el del init para que el\n // chrome no se quede en inglés delante de un survey traducido. Native < 2.3 no lo\n // envía: entonces el primero de formData.lang (el idioma por defecto).\n var shown=args && typeof args.lang==='string' && args.lang.trim() ? args.lang : null;\n var langs=shown ? [shown] : (args && args.formData ? args.formData.lang : null);\n if(langs && langs.length){\n for(var li=0;li<langs.length;li++){\n if(typeof langs[li]==='string' && langs[li].trim()){ ddApplyLabels(langs[li]); break; }\n }\n }\n var s=args && args.formData ? args.formData.style : null;\n if(s && !stylesInjected){\n stylesInjected=true;\n if(s.successMessage){ successMessageHtml=s.successMessage; }\n // La barra de progreso la decide el host (init) y, si no se pronuncia, la plataforma.\n if(progressPref===null){ progressEnabled=s.showProgressBar===true; }\n if(s.showProgressUnit!==undefined){ progressShowUnit=s.showProgressUnit!==false; }\n if(s.progressUnit){ progressUnit=s.progressUnit; }\n if(s.loadingBarColor){ progressBar.style.background=s.loadingBarColor; progressFollowUp.style.background=s.loadingBarColor; }\n if(chromeOn && s.boxBackgroundColor){ popup.style.background=s.boxBackgroundColor; }\n if(s.contentAlign){ main.style.justifyContent = s.contentAlign==='center' ? 'center' : 'flex-start'; }\n // El survey pinta sus controles con --mf-primary, que sale de primaryColor; si la\n // integración solo configura el del botón, el popup mezclaría el color de la marca\n // con el gris azulado por defecto del paquete.\n ddApplySurveyPrimaryColor(popup, s);\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 // Botón de texto: solo tiñe la etiqueta, sin recuperar fondo ni borde.\n backBtn.style.background='transparent'; backBtn.style.color=s.buttonSecondaryColor; backBtn.style.border='none';\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 // Márgenes lógicos: con un survey RTL el popup va con dir=\"rtl\" y el logo\n // tiene que acompañar al chrome. En LTR el resultado es el mismo de antes.\n if(s.logoPosition==='left'){ logoImg.style.margin='12px 0 0 0'; logoImg.style.setProperty('margin-inline-start','0'); logoImg.style.setProperty('margin-inline-end','16px'); }\n else if(s.logoPosition==='right'){ logoImg.style.margin='12px 0 0 0'; logoImg.style.setProperty('margin-inline-start','auto'); logoImg.style.setProperty('margin-inline-end','0'); }\n else if(s.logoPosition==='center'){ logoImg.style.margin='12px auto 0 auto'; }\n // Encima de la barra de progreso, como hermano del header (espejo de ui/logo.ts en\n // web). Fuera de #dd-main, así el logo no se va con el scroll del formulario.\n popup.insertBefore(logoImg, progressEl);\n }\n if(s.startMessage && s.startMessage!==''){ onStartPage=true; updateButtons('start'); }\n else { updateButtons('in_progress_first'); }\n } else {\n updateButtons('in_progress_first');\n }\n // El total solo se conoce una vez montado el form; sin él no hay barra que pintar.\n updateProgress({ progress:form.progress, total:form.total, completed:false, followup:false });\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 // La página no ha cambiado: el estado de navegación se queda como estaba.\n errorHint.textContent=DD_LABELS_ACTIVE.errorRequired;\n errorHint.style.display='block';\n updateNavButtons();\n return;\n }\n errorHint.textContent=DD_LABELS_ACTIVE.errorSubmit;\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 showSuccessScreen();\n updateButtons('completed');\n updateProgress(p);\n emitJSON('survey_completed', p);\n return;\n }\n pageDepth++;\n updateNavButtons();\n updateProgress(p);\n emitJSON('after_submit', p);\n },\n onBackEvent:function(p){\n // Con error (\"No page found\") no hubo navegación: la profundidad no se toca.\n if(!(p&&p.error)){ pageDepth=Math.max(0,pageDepth-1); }\n updateNavButtons();\n // onBackEvent no trae \\`total\\`: se lee del form, que es su dueño.\n updateProgress({ progress:p&&p.progress, total:form.total, completed:false, followup:p&&p.followup });\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","/**\n * Precalentado del chunk de `renderPopup`.\n *\n * `renderPopup` se carga por dynamic import para que importar el SDK sea seguro en React Native\n * y en SSR, pero eso deja su descarga (DOM del popup + `@magicfeedback/native` + el CSS\n * vendorizado, ~238 KB sin comprimir) dentro del camino crítico: empieza cuando el trigger ya ha\n * disparado y el usuario está mirando. Traerlo en idle en cuanto se sabe que hay algún popup que\n * puede abrirse deja el import de `show()` resuelto al instante.\n *\n * Es oportunista por diseño: si falla, no rompe nada porque `show()` hace su propio import.\n */\n\ntype Loader = () => Promise<unknown>;\n\nconst defaultLoader: Loader = () => import('./renderPopup');\n\n/** Precalentado en curso o ya resuelto. Null tras un fallo, para permitir reintento. */\nlet pending: Promise<void> | null = null;\n\n/**\n * Descarga el chunk una sola vez por sesión. Nunca rechaza: un precalentado que falla es un\n * no-evento, la carga de verdad la hará `show()`.\n */\nexport function preloadRenderPopup(loader: Loader = defaultLoader): Promise<void> {\n if (pending) return pending;\n try {\n // El loader se invoca ya, no en un microtask: el import debe empezar en este tick.\n pending = loader().then(() => undefined).catch(() => { pending = null; });\n } catch {\n pending = null;\n return Promise.resolve();\n }\n return pending;\n}\n\n/**\n * Agenda el precalentado para cuando el navegador esté ocioso, para no competir con el render\n * de la página del host. `requestIdleCallback` no existe en Safari, ahí basta con salir del\n * tick actual.\n */\nexport function schedulePreloadRenderPopup(loader?: Loader): void {\n const run = () => { void preloadRenderPopup(loader); };\n const idle = (globalThis as { requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => unknown }).requestIdleCallback;\n if (typeof idle === 'function') {\n idle(run, { timeout: 2000 });\n return;\n }\n setTimeout(run, 0);\n}\n","import {DeepdotsEventType, PopupActions, PopupStyle} from '../types';\nimport { isReactNativeEnv, createReactNativeRenderer } from './react-native-renderer';\nimport { schedulePreloadRenderPopup } from '../ui/preload';\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\n/**\n * Ajustes de presentación que no vienen del estilo del popup. Van agrupados para no seguir\n * alargando la lista posicional de `show`, que ya arrastra doce parámetros.\n */\nexport interface PopupRenderOptions {\n /**\n * Título de la cabecera: el `title` de la definición del popup, tal cual llega de la API.\n * Varía por popup y vacío es un valor válido (la cabecera se queda solo con la X).\n */\n title?: string;\n /** Barra de progreso: `undefined` deja decidir a la plataforma, `true`/`false` la fuerzan. */\n showProgressBar?: boolean;\n /** CSS del host, inyectado el último: gana sobre el del SDK y el de `@magicfeedback/native`. */\n surveyCss?: string;\n /**\n * Idioma con el que arranca el chrome del popup (botones, progreso, aria-labels, errores).\n * Es el idioma resuelto en `init()` (host > navigator > Intl). Lo pisa el idioma del survey\n * (`formData.lang[0]`) en cuanto este carga, y ambos los pisa `actions.*.label` de la API.\n */\n language?: string;\n}\n\nexport interface PopupRenderer {\n /** Preparar recursos si aplica */\n init?(): void;\n /**\n * Traer por adelantado lo que la apertura necesitará, en idle. Opcional: solo lo implementan\n * los renderers cuyo coste de apertura se paga delante del usuario.\n */\n preload?(): 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 renderChrome?: boolean,\n options?: PopupRenderOptions,\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 /**\n * Descarga el chunk de `renderPopup` antes de que ningún trigger dispare, para que abrir el\n * popup no empiece por esperar ~238 KB de JS.\n */\n preload(): void {\n schedulePreloadRenderPopup();\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 _renderChrome?: boolean, // solo aplica a RN (WebView); el popup DOM web siempre lleva su chrome\n options?: PopupRenderOptions,\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, options);\n });\n }\n\n hide(): void {\n this.visible = false;\n if (this.container) {\n this.container.style.display = 'none';\n // `renderPopup` abre el contenedor invisible y lo revela al cargar el survey; si se cierra\n // antes de esa revelación, el estado oculto no puede quedarse pegado al contenedor.\n this.container.style.visibility = '';\n this.container.style.pointerEvents = '';\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","import {\n DeepdotsConfig,\n TriggerConfig,\n DeepdotsEvent,\n DeepdotsEventType,\n EventListener,\n DeepdotsInitParams,\n DeepdotsLogger,\n FeedbackSession,\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 { clearPopupState, readPopupState, writePopupState } from './popup-state-store';\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/**\n * Latido: con la pestaña visible (o la app en foreground), el flush periódico manda el\n * engagement acumulado al menos cada ANALYTICS_HEARTBEAT_MS. Sin él, alguien leyendo una\n * sola página no enviaba nada — el page_view sale al SALIR de la pantalla y el engagement\n * al ocultar/cerrar — y el backend daba la sesión por abandonada con solo su session_start.\n */\nconst ANALYTICS_HEARTBEAT_MS = 5 * 60_000;\n/** Una pestaña que vuelve a verse tras este tiempo oculta empieza sesión nueva (timeout de sesión, como GA). */\nconst ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60_000;\n/**\n * Inactividad tras la que el backend cierra una sesión por su cuenta (Run_Jobs\n * `incomplete-surveys`, `IDLE_MINUTES`, 60 min; el barrido corre cada 20). Mantener en sincronía.\n */\nconst ANALYTICS_SERVER_IDLE_MS = 60 * 60_000;\n/**\n * Pasado este tiempo oculta, la sesión se da por cerrada en el backend: reenviarle el cierre\n * la volvería a montar entera en un segundo feedback, así que se descarta sin enviar nada.\n * Margen de 10 min bajo la ventana del backend: el último lote enviado puede ser algo\n * anterior al momento en que se ocultó la pestaña.\n */\nconst ANALYTICS_SESSION_DISCARD_MS = ANALYTICS_SERVER_IDLE_MS - 10 * 60_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 // Estado que alimenta las reglas de redisplay. Copia de trabajo en memoria; la fuente\n // duradera es el storage (`popup-state-store`), rehidratada en `init()` — sin eso, una\n // recarga reiniciaba el cooldown y el popup volvía a salir. `answeredSurveys` se deriva\n // de las entradas COMPLETED de `surveyProgress` (los dos los escribe `markSurveyAnswered`).\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 /** Si el SDK pinta el \"modal\" del popup (RN). Default true; false = el host lo envuelve. */\n private renderChrome = true;\n /** Barra de progreso. undefined = la decide la plataforma (style.showProgressBar). */\n private showProgressBar: boolean | undefined = undefined;\n /** CSS del host inyectado al final de los estilos del popup. */\n private surveyCss: string | undefined = undefined;\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 /** `init({ onFeedbackSession })`: avisa al host al abrir y cerrar cada registro. */\n private onFeedbackSession: DeepdotsInitParams['onFeedbackSession'] = 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 /** Último `user_engagement` emitido (ms epoch): marca el latido. */\n private lastEngagementAt = 0;\n /** Cuándo se ocultó la pestaña por última vez (ms epoch); null si está visible. */\n private hiddenAt: number | null = null;\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 /** `geolocation` del init: si es false no se usa ni la caché ni el lookup. */\n private geoEnabled = true;\n /** Solo se consulta a terceros si el país/ciudad se va a enviar (analytics configurado). */\n private geoWanted = false;\n /** Evita más de un lookup por instancia (el consentimiento puede ir y venir). */\n private geoLookupStarted = false;\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 this.renderChrome = config.renderChrome ?? true;\n this.showProgressBar = config.showProgressBar;\n this.surveyCss = config.surveyCss;\n this.onFeedbackSession = config.onFeedbackSession;\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.loadPopupState(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: no envía nada y el payload\n // solo se pinta con `debug`, para no ensuciar la consola de la web del host en producción.\n const analyticsSink = config.analytics\n ? createFeedbackSink({\n baseUrl: this.baseUrl,\n keys: config.analytics,\n log: (...a) => this.log(...a),\n onSessionId: (id) => {\n this.analyticsFeedbackSessionId = id;\n this.notifyFeedbackSession(id, 'open');\n },\n onSessionClosed: (id) => this.notifyFeedbackSession(id, 'closed'),\n onSessionReset: () => { this.analyticsFeedbackSessionId = undefined; },\n })\n : createDryRunSink((...a) => { if (this.config?.debug) 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: ver `resolveGeo()` para cuándo se consulta a terceros.\n this.geoEnabled = config.geolocation ?? true;\n this.geoWanted = this.geoEnabled && !!config.analytics;\n this.resolveGeo();\n // Fase 2: navegación → eventos page_view por el canal de analytics.\n this.navObserver = new NavigationObserver();\n this.navObserver.onVisit((v) =>\n this.track('deepdots_page_view', { screen: v.screen, path: v.path, duration_seconds: v.durationSeconds })\n );\n this.navObserver.install();\n // Engagement time (#8): cuenta tiempo activo en primer plano.\n // No arranca aquí: lo hace `openSession`, y solo con la pestaña visible.\n this.engagement = new EngagementTracker();\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 // Con al menos un popup que pueda abrirse, el renderer se trae ya lo que costará la\n // apertura (en navegador, el chunk de renderPopup). Un host sin popups no descarga\n // nada.\n if (this.popupDefinitions.length) this.renderer.preload?.();\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 /**\n * Id del registro de analytics abierto: el `sessionId` que devolvió `POST /sdk/feedback`,\n * que la API guarda como `sdkSessionId` en el Feedback en que se convierte la sesión. No es\n * `getSessionId()`. Null hasta que el backend acepta el primer lote y de nuevo al cerrar la\n * sesión: para saber también qué registro se cerró, usa `init({ onFeedbackSession })`.\n */\n getFeedbackSessionId(): string | null {\n return this.analyticsFeedbackSessionId ?? null;\n }\n\n /** Avisa al host del registro abierto o cerrado; un fallo del host no corta el envío. */\n private notifyFeedbackSession(sessionId: string, status: FeedbackSession['status']): void {\n try {\n this.onFeedbackSession?.({ sessionId, status });\n } catch (error) {\n this.log('analytics · onFeedbackSession threw:', error);\n }\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 // El lookup de geo se aplaza hasta que haya consentimiento.\n if (enabled) this.resolveGeo();\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 * Product Effectiveness: una interacción significativa del usuario (`interaction_type` es la\n * dimensión de agrupación, p. ej. `get_help`, `homepage`, `contact_support`).\n *\n * Existe como evento RESERVADO para que la convención no la invente cada host: un `track()`\n * custom acaba como `deepdots_event_…` y no lo cuenta la analítica de effectiveness.\n * Un `interactionType` vacío se descarta con warning (no aporta dato agrupable).\n */\n trackMeaningfulInteraction(interactionType: string, params?: Record<string, unknown>): void {\n const type = typeof interactionType === 'string' ? interactionType.trim() : '';\n if (!type) {\n // Siempre visible (como en `trackMessage`): es un error de integración del host.\n console.warn('[DeepdotsPopups] trackMeaningfulInteraction discarded: empty interaction_type');\n return;\n }\n this.track('deepdots_meaningful_interaction', { interaction_type: type, ...(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 // El historial de redisplay era del usuario anterior: el nuevo empieza de cero.\n this.lastShown.clear();\n this.surveyProgress.clear();\n this.answeredSurveys.clear();\n clearPopupState(storage);\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 // Parado hasta la sesión siguiente. Si siguiera en marcha, lo que llega después del\n // cierre (en Chromium el `visibilitychange` va DESPUÉS de `pagehide`) emitía un\n // `user_engagement` sin sessionId que abría en la API un registro que nunca se cierra.\n this.engagement?.pause();\n this.track('deepdots_session_end', { reason });\n // `final` solo cuando el documento se está muriendo (el transporte no espera respuestas).\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.lastEngagementAt = Date.now();\n this.track('deepdots_session_start', {});\n // El cierre anterior dejó sin pantalla al observador: sin esto, la pantalla en la que\n // está el usuario al reabrir (cambio de usuario, vuelta de la bfcache) no contaba.\n this.navObserver?.restart();\n // Una pestaña abierta en segundo plano no ha sido vista: no cuenta hasta que se muestre.\n if (this.isDocumentVisible()) this.engagement?.resume();\n }\n\n /** Web: si la pestaña se ve. Sin `document` (RN) manda AppState vía onForeground/onBackground. */\n private isDocumentVisible(): boolean {\n return typeof document === 'undefined' || document.visibilityState !== 'hidden';\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): el lote no espera al primer POST en vuelo, porque\n * el documento se muere; 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) {\n this.track('deepdots_user_engagement', { engagement_time_msec: ms });\n this.lastEngagementAt = Date.now();\n }\n }\n\n /** Tick del flush periódico: latido de engagement si toca, y envío de lo acumulado. */\n private onAnalyticsFlushTick(): void {\n if (\n this.sessionOpen &&\n this.isDocumentVisible() &&\n Date.now() - this.lastEngagementAt >= ANALYTICS_HEARTBEAT_MS\n ) {\n this.flushEngagement();\n }\n this.flushAnalytics();\n }\n\n /**\n * La pestaña vuelve a verse. Tras más de ANALYTICS_SESSION_TIMEOUT_MS oculta, la sesión\n * anterior termina y empieza otra: seguir con el mismo sessionId mezclaba dos visitas y,\n * si el backend ya la había cerrado por inactividad, la partía en dos feedbacks.\n * - Oculta menos de ANALYTICS_SESSION_DISCARD_MS: el backend aún la tiene abierta → se\n * cierra con normalidad (page_view de la pantalla, session_end `idle_timeout`, completed).\n * - Más: ya la cerró el backend → se descarta sin enviar nada.\n */\n private onDocumentVisible(): void {\n const hiddenFor = this.hiddenAt === null ? 0 : Date.now() - this.hiddenAt;\n this.hiddenAt = null;\n if (!this.sessionOpen) return; // sin sesión abierta no hay dónde contar ese tiempo\n if (hiddenFor < ANALYTICS_SESSION_TIMEOUT_MS) {\n this.engagement?.resume();\n return;\n }\n if (hiddenFor < ANALYTICS_SESSION_DISCARD_MS) {\n this.closeSession('idle_timeout');\n } else {\n this.discardSession();\n }\n this.openSession();\n }\n\n /** Olvida la sesión abierta sin enviar nada (ver onDocumentVisible). */\n private discardSession(): void {\n this.sessionOpen = false;\n this.navObserver?.discard();\n this.analytics?.discardSession(this.analyticsIdentity());\n this.engagement?.consume(); // tiramos lo acumulado: era de la sesión descartada\n this.engagement?.pause();\n this.analyticsFeedbackSessionId = undefined;\n this.tracking?.setSessionId(null);\n this.log('tracking · session discarded after', ANALYTICS_SESSION_DISCARD_MS / 60_000, 'min hidden');\n }\n\n /**\n * Geolocalización por IP (país/ciudad para analytics). La caché persistente es local y se\n * aplica siempre que `geolocation` no esté desactivado. El lookup a terceros (ipapi.co →\n * ipwho.is → ipinfo.io, fallback con timeout) solo se hace si el dato se va a ENVIAR:\n * analytics configurado + tracking activo + caché ausente o caducada. Una vez por instancia.\n */\n private resolveGeo(): void {\n const storage = this.storage;\n if (!this.geoEnabled || !storage || !this.analytics) return;\n const cachedGeo = readCachedGeo(storage, Date.now());\n if (cachedGeo) {\n this.analytics.updateDevice(cachedGeo);\n return;\n }\n if (!this.geoWanted || this.geoLookupStarted) return;\n if (!this.tracking?.isTrackingEnabled()) return;\n this.geoLookupStarted = true;\n fetchGeo()\n .then((geo) => {\n if (geo) {\n this.analytics?.updateDevice(geo);\n writeCachedGeo(storage, geo, Date.now());\n }\n })\n .catch(() => {});\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 // Flush periódico + latido en todos los entornos. En RN no había ninguno: una app en\n // foreground que no llegaba a `maxBatchSize` no enviaba nada hasta ir a background.\n this.startAnalyticsFlushTimer();\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\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.\n window.addEventListener('pagehide', () => {\n clearInterval(this.analyticsFlushTimer);\n this.closeSession('page_hide');\n });\n // Vuelta desde la bfcache (botón atrás): la página no se recarga, así que `init()` no\n // vuelve a correr. El `pagehide` cerró la sesión y paró el flush; sin reabrir ambos, la\n // pestaña no mandaba nada más hasta una recarga.\n window.addEventListener('pageshow', (event) => {\n if (!(event as PageTransitionEvent).persisted) return;\n this.startAnalyticsFlushTimer();\n this.openSession();\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 this.hiddenAt = Date.now();\n } else if (document.visibilityState === 'visible') {\n this.onDocumentVisible();\n }\n });\n }\n\n private startAnalyticsFlushTimer(): void {\n clearInterval(this.analyticsFlushTimer);\n this.analyticsFlushTimer = setInterval(() => this.onAnalyticsFlushTick(), ANALYTICS_FLUSH_INTERVAL_MS);\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.persistPopupState();\n this.renderPopup(def.surveyId, def.productId, def.actions, def.style, def.title);\n this.emitEvent('popup_shown', def.surveyId, { popupId: def.id });\n }\n\n private shouldShow(def: NormalizedPopupDefinition, pathUrl?: string, skipPathCheck = false): boolean {\n // Otra pestaña puede haber enseñado o contestado este popup desde nuestro `init()`.\n if (this.storage) this.loadPopupState(this.storage);\n // La exclusión se evalúa SIEMPRE, también cuando `skipPathCheck` levanta la\n // restricción de \"dónde PUEDE mostrarse\" (popup de exit ya encolado): decir\n // \"no en /cart\" es una regla sobre la pantalla en la que se va a pintar.\n if (this.isPathExcluded(def, pathUrl)) {\n return false;\n }\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 /** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */\n private matchesSegmentsPath(def: PopupDefinition, pathUrl?: string): boolean {\n const paths = def.segments?.path;\n if (!Array.isArray(paths) || paths.length === 0) return true;\n\n const location = this.resolveCurrentLocation(pathUrl);\n if (!location) {\n this.debug('No window.location available for path comparison', { popupId: def.id, paths });\n return true;\n }\n\n const matches = paths.some((rawCandidate) => {\n const match = this.matchesPathCandidate(rawCandidate, location);\n this.debug('Path comparison', {\n popupId: def.id,\n candidate: rawCandidate,\n currentPath: location.path,\n currentHref: location.href,\n match,\n });\n return match;\n });\n\n if (!matches) {\n this.debug('No path match for popup', { popupId: def.id, paths, currentPath: location.path });\n }\n\n return matches;\n }\n\n /**\n * `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre\n * `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el\n * carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).\n */\n private isPathExcluded(def: PopupDefinition, pathUrl?: string): boolean {\n const excluded = def.segments?.excludedPaths;\n if (!Array.isArray(excluded) || excluded.length === 0) return false;\n\n const location = this.resolveCurrentLocation(pathUrl);\n if (!location) {\n // Sin `window.location` (RN/SSR) no se puede evaluar: no bloqueamos, igual\n // que hace `matchesSegmentsPath` con la lista de inclusión.\n this.debug('No window.location available for excluded path comparison', { popupId: def.id, excluded });\n return false;\n }\n\n const hit = excluded.find((rawCandidate) => this.matchesPathCandidate(rawCandidate, location));\n if (hit !== undefined) {\n this.debug('Popup excluded by path', {\n popupId: def.id,\n candidate: hit,\n currentPath: location.path,\n currentHref: location.href,\n });\n return true;\n }\n return false;\n }\n\n /**\n * Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta\n * actual. Mismas reglas para incluir y para excluir:\n * - URL absoluta → href completo exacto\n * - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)\n * - resto → pathname exacto\n * Ignora entradas que no sean string: el backend puede colar un `null` en la lista\n * y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con\n * `segments.lang`).\n */\n private matchesPathCandidate(rawCandidate: unknown, location: { href: string; path: string }): boolean {\n if (typeof rawCandidate !== 'string') return false;\n const candidate = this.normalizeUrl(rawCandidate);\n if (!candidate) return false;\n if (candidate.startsWith('http://') || candidate.startsWith('https://')) {\n return location.href === candidate;\n }\n if (candidate.startsWith('/')) {\n return location.href.includes(candidate);\n }\n return location.path === candidate;\n }\n\n /** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */\n private resolveCurrentLocation(pathUrl?: string): { href: string; path: string } | null {\n if (typeof window === 'undefined' || !window.location) return null;\n const href = this.normalizeUrl(pathUrl || window.location.href || '');\n const parsed = this.safeParseUrl(href);\n return { href, path: this.normalizeUrl(parsed?.pathname || window.location.pathname || '') };\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 // El backend aplica las reglas de redisplay AQUI: si este userId ya tiene un\n // SHOWED/PARTIAL/COMPLETED dentro del cooldown, no devuelve ese popup. Para que\n // cuadre, hay que preguntar por el MISMO id con el que se reporta el estado en\n // `POST /sdk/popups` — el del tracking (host si lo hay, si no el anonimo\n // persistente). Con `config.userId` a secas, la integracion que no pasa userId\n // (el caso por defecto) preguntaba sin filtro y el backend devolvia todo.\n const userId = this.tracking?.getUserId() ?? 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, title?: string): 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 this.renderChrome,\n { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language },\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 { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language },\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 this.persistPopupState();\n }\n\n /**\n * Fusiona el estado de redisplay del storage con el de memoria: por cada popup/survey gana\n * la entrada MÁS RECIENTE. Fusionar y no sustituir es lo que hace convivir a varias\n * pestañas sobre el mismo `localStorage`: se llama en el `init()`, antes de cada decisión\n * de mostrar y antes de cada escritura. `answeredSurveys` se reconstruye desde las\n * entradas COMPLETED: es exactamente lo que persiste `markSurveyAnswered`.\n */\n private loadPopupState(storage: KeyValueStorage): void {\n const state = readPopupState(storage);\n Object.entries(state.lastShown).forEach(([popupId, timestamp]) => {\n if (timestamp > (this.lastShown.get(popupId) ?? -Infinity)) this.lastShown.set(popupId, timestamp);\n });\n Object.entries(state.progress).forEach(([surveyId, entry]) => {\n const current = this.surveyProgress.get(surveyId);\n if (current && current.timestamp >= entry.timestamp) return;\n this.surveyProgress.set(surveyId, { status: entry.status, timestamp: entry.timestamp });\n if (entry.status === 'COMPLETED') this.answeredSurveys.add(surveyId);\n });\n }\n\n /**\n * Vuelca el estado de redisplay al storage. Sin storage (o bloqueado) degrada a memoria.\n * Fusiona antes de escribir: escribir solo la foto en memoria borraba lo que otra pestaña\n * hubiese guardado desde nuestro `init()`.\n */\n private persistPopupState(): void {\n if (!this.storage) return;\n this.loadPopupState(this.storage);\n writePopupState(this.storage, {\n lastShown: Object.fromEntries(this.lastShown),\n progress: Object.fromEntries(this.surveyProgress),\n });\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 * 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 (`fetch` con `keepalive`) y no puede esperar\n * 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 * `true` para olvidar la sesión SIN enviar nada (el payload llega vacío): el backend ya la\n * cerró por inactividad. El sink descarta su `sessionId` para que el lote siguiente abra un\n * registro nuevo, igual que tras `sessionEnd`, pero sin POST.\n */\n resetSession?: 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, meta) => {\n if (meta?.resetSession) return;\n log(\n '[DeepdotsAnalytics] (dry-run · NOT sent · no init.analytics) POST /sdk/feedback →',\n JSON.stringify(payload, null, 2),\n );\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 la sesión en curso SIN emitir nada: vacía el buffer y los mini-services activos\n * (sin `mini_service_exit`). Para una sesión que el backend ya cerró por inactividad: lo\n * que quedara pendiente pertenecía a ella y no debe llegar con la sesión nueva.\n */\n discardSession(identity: AnalyticsIdentity): void {\n this.events = [];\n this.activeMiniServices.clear();\n try {\n void this.sink({ ...this.buildPayload(identity), events: [] }, { resetSession: true });\n } catch {\n /* el sink no debe romper el ciclo de vida */\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 * @deprecated Se ignora. El flush final salía por `sendBeacon`, que va SIEMPRE con\n * credenciales; contra la API (`Access-Control-Allow-Origin: *` + `Allow-Credentials:\n * true`) el preflight fallaba y el lote de cierre, el único con `completed:true`, no\n * llegaba nunca. Ahora sale por `fetch` con `keepalive`. Se mantiene en el tipo para no\n * romper a quien lo pase.\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 * Callback invocado cuando el backend acepta el lote de cierre, con el sessionId del\n * registro cerrado: el que llevaba el lote o, en una sesión de un solo lote, el que\n * devuelve la respuesta. Ese registro se convierte en Feedback en segundos.\n */\n onSessionClosed?: (id: string) => void;\n}\n\n/** Límite práctico de `fetch({keepalive:true})` (~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 ni al cerrar la\n * página, también en el flush final (sin `sendBeacon`: ver `sendBeaconImpl`);\n * - sin credenciales, que es lo único que pasa el CORS de la API (`Allow-Origin: *`);\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 * Sube cada vez que se cierra u olvida una sesión. Una respuesta que llega con otra\n * generación es de una sesión ya cerrada (p. ej. el primer POST seguía en vuelo en el\n * `page_hide`) y no puede volver a cachear su sessionId ni avisarlo como abierto.\n */\n let generation = 0;\n\n const url = `${options.baseUrl}/sdk/feedback`;\n\n const post = async (body: AnalyticsFeedbackBody, final: boolean, sessionEnd = false): Promise<void> => {\n const postGeneration = generation;\n const json = JSON.stringify(body);\n const small = json.length <= KEEPALIVE_MAX_BYTES;\n\n // Sin `sendBeacon` a propósito, también en el cierre de página (ver `sendBeaconImpl`):\n // `keepalive` sobrevive igual al unload y, sin credenciales, pasa el preflight de la API.\n if (final) options.log?.('[DeepdotsAnalytics] final flush via fetch keepalive');\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 // La API autentica por `publicKey` en el body y responde `Allow-Origin: *`: una\n // petición con credenciales fallaría el CORS. Explícito para no depender del default.\n credentials: 'omit',\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 // pero se avisa por onSessionClosed, porque ese registro ya va a ser un Feedback.\n if (sessionEnd) {\n let closedId = body.sessionId;\n try {\n const data = (await res.json()) as { sessionId?: string };\n closedId = data?.sessionId ?? closedId;\n } catch {\n /* respuesta sin JSON: vale el sessionId que llevaba el lote */\n }\n try {\n if (closedId) options.onSessionClosed?.(closedId);\n } catch {\n // Un fallo del callback no puede convertir el lote entregado en uno fallido:\n // el manager lo re-encolaría y el cierre se enviaría dos veces.\n }\n return;\n }\n\n try {\n const data = (await res.json()) as { sessionId?: string };\n if (postGeneration !== generation) {\n options.log?.('[DeepdotsAnalytics] sessionId of a closed session ignored:', data?.sessionId);\n return;\n }\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 if (meta?.resetSession) {\n // Sesión cerrada por el backend: no se envía nada, solo se olvida el registro.\n feedbackSessionId = undefined;\n firstPostInFlight = null;\n generation++;\n options.onSessionReset?.();\n return;\n }\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 generation++;\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 /** Pantalla normalizada (IDs → `:id`): agrupa las visitas a la misma pantalla. */\n screen: string;\n /**\n * La ruta real, sin query: `/insights/requests/2f1c…` donde `screen` dice\n * `/insights/requests/:id`. Es lo que permite enlazar la visita al elemento concreto.\n */\n path: 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/** La ruta de una URL: path (+hash route), sin query. Sin normalizar los IDs. */\nexport function screenPath(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 return pathAndHash.replace(/\\?[^#]*/g, '') || '/';\n}\n\n/** Normaliza una URL a un nombre de pantalla: path (+hash route), sin query, IDs → :id. */\nexport function normalizeScreen(href: string): string {\n const pathAndHash = screenPath(href);\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 currentPath: 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.currentPath = screenPath(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 nextPath = screenPath(href);\n // Misma ruta (solo cambia la query) → no es una visita nueva. Se compara la ruta y no\n // la pantalla normalizada: pasar de una research a otra (`/insights/requests/A` → `/B`)\n // sí es otra visita, aunque las dos sean `/insights/requests/:id`.\n if (this.currentScreen != null && nextPath === this.currentPath) return;\n this.closeCurrent();\n this.currentScreen = normalizeScreen(href);\n this.currentPath = nextPath;\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 this.currentPath = null;\n }\n\n /**\n * Olvida la pantalla actual SIN emitir su visita. Para una sesión que el backend ya dio\n * por cerrada: su visita no tiene dónde ir, y mandarla con la sesión nueva la contaría\n * donde no ocurrió. Después, `restart()` vuelve a empezar en la pantalla actual.\n */\n discard(): void {\n this.currentScreen = null;\n this.currentPath = null;\n }\n\n /**\n * Retoma el seguimiento en la pantalla actual tras un `stop()`, cuando se abre una sesión\n * nueva sin navegar (cambio de usuario, vuelta de la bfcache). No-op si nunca se instaló\n * (navegación manual por `setScreen`, RN) o si ya hay pantalla en curso.\n */\n restart(): void {\n if (!this.installed || this.currentScreen != null || typeof window === 'undefined') return;\n this.begin(window.location.href);\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 path: this.currentPath ?? 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 * Persistencia del estado que alimenta las reglas de redisplay (cooldown) de los popups.\n *\n * Hasta la 1.7.0 `lastShown`/`surveyProgress` vivían SOLO en memoria, así que cualquier\n * recarga de página reiniciaba el cooldown y el popup volvía a salir aunque su regla\n * dijese \"no antes de N días\". El SDK nativo (KMP) sí lo persistía (`getLastShown`/\n * `setLastShown` contra el `KeyValueStorage`); esto cierra esa divergencia en Web.\n *\n * ⚠️ El alcance es POR NAVEGADOR: otro dispositivo, otro perfil o un borrado de datos\n * vuelven a empezar. El cooldown por usuario de verdad exige que el backend filtre el\n * `GET /sdk/{publicKey}/popups` con el historial que ya recibe en `POST /sdk/popups`.\n */\nimport type { KeyValueStorage } from '../tracking/tracking-manager';\nimport { POPUP_TRIGGER_CONDITION_STATUSES } from '../types';\nimport type { PopupTriggerConditionStatus } from '../types';\n\n/** Clave única en el namespace `deepdots.*` (mismo que `STORAGE_KEYS`). */\nexport const POPUP_STATE_STORAGE_KEY = 'deepdots.popups.state';\n\n/**\n * Techo de entradas por mapa, con desalojo del más ANTIGUO (mismo trade-off explícito\n * que `MAX_TRACKED_MESSAGES` en `MessageGuard`). Una cuenta real tiene unos pocos popups;\n * el techo solo evita que el registro crezca sin límite si se rotan definiciones.\n * No se poda por antigüedad a propósito: un `cooldownDays` largo debe seguir valiendo.\n */\nexport const MAX_TRACKED_POPUP_ENTRIES = 200;\n\nexport interface PersistedProgressEntry {\n status: PopupTriggerConditionStatus;\n timestamp: number;\n}\n\nexport interface PersistedPopupState {\n /** popupId → timestamp de la última vez que se mostró. */\n lastShown: Record<string, number>;\n /** surveyId → último estado conocido (PARTIAL/COMPLETED) y cuándo. */\n progress: Record<string, PersistedProgressEntry>;\n}\n\nfunction emptyState(): PersistedPopupState {\n return { lastShown: {}, progress: {} };\n}\n\nfunction isValidTimestamp(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\n/** Se queda con las `MAX_TRACKED_POPUP_ENTRIES` entradas de timestamp más reciente. */\nfunction capEntries<T>(entries: Array<[string, T]>, timestampOf: (value: T) => number): Array<[string, T]> {\n if (entries.length <= MAX_TRACKED_POPUP_ENTRIES) return entries;\n return [...entries]\n .sort((a, b) => timestampOf(b[1]) - timestampOf(a[1]))\n .slice(0, MAX_TRACKED_POPUP_ENTRIES);\n}\n\n/**\n * Lee el estado persistido. Tolerante a todo: sin clave, JSON corrupto, entradas con\n * forma inválida o un storage que lanza (Safari en privado, cookies bloqueadas) →\n * estado vacío o entradas buenas, nunca una excepción.\n */\nexport function readPopupState(storage: KeyValueStorage): PersistedPopupState {\n let raw: string | null = null;\n try {\n raw = storage.getItem(POPUP_STATE_STORAGE_KEY);\n } catch {\n return emptyState();\n }\n if (!raw) return emptyState();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return emptyState();\n }\n if (typeof parsed !== 'object' || parsed === null) return emptyState();\n\n const source = parsed as Partial<Record<keyof PersistedPopupState, unknown>>;\n const state = emptyState();\n\n if (typeof source.lastShown === 'object' && source.lastShown !== null) {\n const valid = Object.entries(source.lastShown as Record<string, unknown>)\n .filter((entry): entry is [string, number] => isValidTimestamp(entry[1]));\n capEntries(valid, (timestamp) => timestamp).forEach(([popupId, timestamp]) => {\n state.lastShown[popupId] = timestamp;\n });\n }\n\n if (typeof source.progress === 'object' && source.progress !== null) {\n const valid = Object.entries(source.progress as Record<string, unknown>)\n .filter((entry): entry is [string, PersistedProgressEntry] => {\n const value = entry[1];\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<PersistedProgressEntry>;\n return (\n POPUP_TRIGGER_CONDITION_STATUSES.includes(candidate.status as PopupTriggerConditionStatus)\n && isValidTimestamp(candidate.timestamp)\n );\n });\n capEntries(valid, (entry) => entry.timestamp).forEach(([surveyId, entry]) => {\n state.progress[surveyId] = { status: entry.status, timestamp: entry.timestamp };\n });\n }\n\n return state;\n}\n\n/** Escribe el estado (acotado). Un storage que lanza se ignora en silencio. */\nexport function writePopupState(storage: KeyValueStorage, state: PersistedPopupState): void {\n const capped: PersistedPopupState = emptyState();\n capEntries(Object.entries(state.lastShown), (timestamp) => timestamp).forEach(([popupId, timestamp]) => {\n capped.lastShown[popupId] = timestamp;\n });\n capEntries(Object.entries(state.progress), (entry) => entry.timestamp).forEach(([surveyId, entry]) => {\n capped.progress[surveyId] = entry;\n });\n\n try {\n storage.setItem(POPUP_STATE_STORAGE_KEY, JSON.stringify(capped));\n } catch {\n // Storage lleno o bloqueado por el navegador: el cooldown degrada a solo-memoria.\n }\n}\n\n/** Borra el registro (cambio de usuario: el historial era del anterior). */\nexport function clearPopupState(storage: KeyValueStorage): void {\n try {\n storage.removeItem(POPUP_STATE_STORAGE_KEY);\n } catch {\n // idem\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","/**\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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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["clean","pending","revealWhenPainted","magicfeedback","renderPopup","pending","renderPopup","sdk"]}
|