Saltar al contenido

Instalación

Instalar el widget toma dos pasos: cargar el script y luego iniciarlo con el slug de tu espacio. Chat, ideas, roadmap y novedades quedan dentro de tu app; carga en segundo plano y vive en Shadow DOM, así que no toca tus estilos.

Con el lanzador por defecto, eso basta para que aparezca el botón flotante en tu página.

1. Agrega el script

Pega esto cerca del final de tu página, justo antes de cerrar </body>:

<script>  window.Jotly = window.Jotly || function () { (Jotly.q = Jotly.q || []).push(arguments) };</script><script async src="https://jotly.si/widget.js"></script>

La primera línea crea la función global window.Jotly y guarda en una cola las llamadas que hagas antes de que cargue widget.js; al cargar, las ejecuta en orden.

2. Inicia el widget

Después del script, inicia Jotly con tu slug:

<script>  Jotly('init', 'TU_ESPACIO');</script>

Cambia TU_ESPACIO por el slug de tu espacio. Lo ves en la dirección de tu panel (/app/TU_ESPACIO) y en Ajustes → Instalar widget, donde el código ya viene con tu slug. Con el mismo slug se arma tu portal público en /b/TU_ESPACIO.

Ejemplo mínimo

Ésta es la instalación completa más corta:

<script>  window.Jotly = window.Jotly || function () { (Jotly.q = Jotly.q || []).push(arguments) };  Jotly('init', 'TU_ESPACIO');  // Opcional: identifica a tu usuario  Jotly('identify', { user: { id: 'user_123', email: 'ana@empresa.com', firstName: 'Ana' } });</script><script async src="https://jotly.si/widget.js"></script>

Una sola etiqueta (sin script en línea)

Si tu política de seguridad de contenido (CSP) no permite scripts en línea, basta con una etiqueta. El widget se inicia solo con data-project; si además llamas a Jotly('init', …), tu llamada gana sobre el atributo.

<script async src="https://jotly.si/widget.js" data-project="TU_ESPACIO"></script>

Opcional: identifica a tus usuarios

Si tu producto ya sabe quién es el usuario, identifícalo después de iniciar:

const { viewer, identityTrust, warnings } = await Jotly.identify({  user: {    id: 'user_123',            // obligatorio (o un token firmado)    email: 'ana@empresa.com',    firstName: 'Ana',    lastName: 'López',    avatar: 'https://…/ana.png',    traits: { plan: 'pro', monthlySpend: 499 },  },  companies: [{ id: 'acme', name: 'Acme SA' }],});

Hace falta user.id o un token firmado en tu servidor. La misma entrada con la sesión viva no hace nada y, si llamas varias veces, gana la última. Para que nadie pueda hacerse pasar por otro usuario, firma la identidad en tu servidor como se explica en Identificar usuarios. El formato anterior ({ id, email, name, signature }) sigue funcionando.

Opciones de init

Las opciones del widget también se configuran desde Ajustes → Instalar widget; si las pasas en init, mandan las tuyas. init devuelve una promesa con el estado y sólo rechaza por errores de programación (INVALID_PROJECT_ID, INVALID_OPTIONS).

Jotly('init', 'TU_ESPACIO', {  widget: {                         // false lo desactiva (sigues teniendo createEmbed e identify)    launcher: true,                 // false, o { selector: '#ayuda' } para usar tu propio botón    whispers: true,                 // avisos efímeros junto al lanzador    notifications: { updates: 'whisper' }, // 'whisper' | 'full' | 'none'    position: 'right',              // 'right' | 'left'    offset: { bottom: 20, side: 20 },      // px    theme: 'auto',                  // 'auto' | 'light' | 'dark'  },  locale: 'es-MX',                  // por defecto, el idioma del navegador  pageContext: 'origin',            // 'origin' | 'path' | 'none': cuánto de la URL ve tu equipo en el chat  onReady: function () {},  onError: function (error) {},     // { code, message, recoverable }});

Con notifications.updates: 'full', una novedad nueva abre el widget en el artículo como máximo una vez por carga, sólo con la pestaña visible y con foco y el widget cerrado. Con 'whisper' aparece un aviso junto al lanzador que dura 7 segundos.

API en el navegador

Cada método se llama igual como Jotly('open', { to: 'roadmap' }) o, ya cargado el script, como Jotly.open({ to: 'roadmap' }). Las llamadas que hagas antes de que cargue widget.js se guardan en una cola y se ejecutan en orden.

// Promesa con el estado al terminar de iniciar (no rechaza por red: revisa state.status)const state = await Jotly.ready(); // Abrir en un punto exacto; resuelve cuando el panel está visibleJotly.open({ to: 'home' });Jotly.open({ to: 'roadmap' });Jotly.open({ to: 'notifications' });Jotly.open({ to: 'feedback' });Jotly.open({ to: 'feedback', compose: true });            // nueva solicitudJotly.open({ to: 'feedback', submissionId: 'pst_123' });  // una solicitudJotly.open({ to: 'updates', updateId: 'chg_123' });       // una novedadJotly.open({ to: 'messages', compose: { text: 'Hola, necesito ayuda con…' } }); Jotly.close();Jotly.toggle(); // Apariencia e idiomaJotly.setTheme('dark');           // 'auto' | 'light' | 'dark'await Jotly.setLocale('es-MX'); // Estado de sólo lecturaJotly.getState(); // Al cerrar sesión en tu appawait Jotly.logout(); // Quitar todo; en kioscos borra también conversaciones anónimas, borradores y contacto recordadoJotly.destroy({ clearConversationData: true });

Eventos

Jotly.on(evento, función) devuelve la función para dejar de escuchar. Los listeners sobreviven a destroy().

EventoRecibe
readyEl estado. Si ya estaba listo, se llama de inmediato.
statechangeEl estado, en cada cambio.
openEl estado al abrir el panel.
closeEl estado al cerrar el panel.
identify{ viewer, identityTrust }
unread{ count }
presence{ availability: 'online' | 'neutral' }
error{ code, message, recoverable }
destroyEl estado tras destroy().
const off = Jotly.on('unread', function ({ count }) {  document.querySelector('#ayuda-badge').textContent = count || '';});off(); // deja de escuchar Jotly.on('error', function (error) {  if (!error.recoverable) console.error(error.code, error.message);});

Escuchar unread o presence mantiene el conteo al día aunque uses widget: false.

Estado

{  status: 'ready',                   // 'idle' | 'initializing' | 'ready' | 'error' | 'destroyed'  projectId: 'TU_ESPACIO',  locale: 'es-MX',  unreadCount: 1,                    // respuestas del chat + novedades sin ver; null al inicio  presence: 'online',                // 'online' | 'neutral' ('neutral' no significa desconectado)  lastError: null,  widget: { enabled: true, open: false, position: 'right', theme: 'auto' },  session: { status: 'anonymous', identityTrust: null, viewer: null },}

Errores

Todos los errores tienen la forma { code, message, recoverable }. Llegan al evento error, a onError y a state.lastError; las promesas rechazan con el mismo objeto.

CódigoCuándo
INVALID_PROJECT_IDEl id del proyecto no tiene formato válido o el espacio no existe.
INVALID_OPTIONSAlguna opción de init, setTheme, setLocale o createEmbed no es válida.
INVALID_NAVIGATIONEl destino de open o la ruta de navigate no son válidos.
INVALID_IDENTIFYidentify sin token ni user.id.
IDENTIFY_FAILEDEl servidor rechazó la identidad (firma inválida, espacio que exige firma, etc.). Es el único error de red que rechaza la promesa.
BOOTSTRAP_FAILEDNo se pudo cargar la configuración; el estado queda en error.
NOT_READYLlamaste un método después de que init falló.
WIDGET_DISABLEDopen con widget: false.
CONVERSATIONS_DISABLEDopen({ to: 'messages' }) con los mensajes apagados; se abre Inicio.
LAUNCHER_NOT_FOUNDNingún elemento coincide con launcher.selector (se sigue escuchando por si aparece).
EMBED_CONTAINER_NOT_FOUNDNo existe el contenedor del embebido.
DESTROYEDLlamaste un método después de destroy; vuelve a llamar init.

Embebidos en tu página

Jotly.createEmbed muestra el portal (feedback, roadmap y novedades) dentro de tu app en un iframe, sin su barra de navegación y con la misma identidad que el widget. Puedes tener varios a la vez.

<div data-jotly-embed style="height: 720px"></div><script>  Jotly('init', 'TU_ESPACIO', { widget: false });  var portal = Jotly.createEmbed({    // container: '#feedback',      // por defecto [data-jotly-embed]    path: '/roadmap',               // por defecto '/'    basePath: '/feedback',          // sincroniza la URL: /feedback/p/<slug>    theme: 'auto',    onReady: function () {},    onError: function (error) {},  });  portal.navigate('/updates');  // portal.destroy();</script>
  • Con basePath, cada cambio de página dentro del embebido actualiza la URL de tu sitio con history, y los botones atrás y adelante del navegador mueven el embebido. Al cargar una URL bajo basePath, el embebido abre esa misma página.
  • El iframe y tu página sólo se hablan con mensajes jotly: (ready, route:change, navigate, identify, logout) y cada lado verifica la ventana y el origen del otro.
  • También puedes embeber el portal a mano: cualquier página de /b/TU_ESPACIO acepta ?navbar=hidden y ?theme=light|dark|auto.

Next.js

Con next/script lo cargas una vez para todo el sitio y llamas a window.Jotly desde cualquier componente.

app/layout.tsx
// app/layout.tsximport Script from "next/script"; export default function RootLayout({ children }) {  return (    <html lang="es">      <body>        {children}        <Script id="jotly-init" strategy="afterInteractive">          {`window.Jotly = window.Jotly || function () { (Jotly.q = Jotly.q || []).push(arguments) };Jotly('init', 'TU_ESPACIO');`}        </Script>        <Script src="https://jotly.si/widget.js" strategy="afterInteractive" />      </body>    </html>  );}

Verifica la instalación

Al recargar la página:

  1. El lanzador del widget aparece en la esquina inferior.
  2. Al hacer clic se abre el widget.
  3. Puedes moverte entre mensajes, ideas, roadmap y novedades dentro del widget.
  4. En la consola, Jotly.getState().status dice 'ready'.

Si usas tu propio botón (launcher: { selector } o launcher: false), el widget puede estar bien instalado aunque no aparezca ningún botón flotante.

Problemas comunes

El widget no aparece

Revisa primero:

  • El script de widget.js está en la página
  • Jotly('init', …) corre con el slug correcto (o la etiqueta trae data-project)
  • launcher no está en false y widget no está en false
  • Jotly.getState().lastError no trae un error

El widget aparece, pero no reconoce al usuario

Casi siempre significa que la instalación está bien pero falta la identidad. Empieza por Identificar usuarios.

Si mandas una firma que no coincide, Jotly rechaza la identificación con IDENTIFY_FAILED en vez de crear un usuario sin verificar.

Ver también Widget de feedback en tu app