Skip to content

Tutorial paso a paso · Modelo C

Esta guía es un walkthrough completo del Modelo C — widget_betickets_payment (widget embebido + cobro beTickets). Está pensada para un integrador que parte de cero y necesita poner el widget en producción en el site de su canal.

Si todavía estás eligiendo modelo de integración, consulta primero la tabla comparativa o el overview.

Demo ejecutable

El repositorio incluye una demo lista para abrir en el navegador en ticketera/sales-channels/sala-el-sol/. Refleja exactamente lo que hace esta guía. Si en algún paso te pierdes, abre ese HTML como referencia.

1. ¿Cuándo elegir el Modelo C?

Elige este modelo si todas estas afirmaciones son ciertas para tu canal:

  • Quieres que el comprador seleccione asientos dentro de tu propio site (no en un portal externo) — branding total, sin redirecciones para descubrir entradas.
  • No quieres asumir el cobro: no tienes integración con un TPV, no quieres gestionar PCI-DSS, ni emitir facturas como vendedor.
  • Aceptas que el comprador sea redirigido al portal de beTickets en el último paso (entrada de datos personales + pago con Redsys), con el branding del canal aplicado.

Si quieres mantener al comprador 100% en tu site y cobrar tú, mira el Modelo D. Si no necesitas widget, mira los Modelos A y B.

2. Lo que hace y lo que no hace el integrador

ResponsabilidadModelo C
Mostrar el catálogo de eventos en el siteIntegrador (puede listar eventos como prefiera)
Selección de sesión y asientosWidget (lo entrega beTickets)
Botón "Ir al pago"Widget (lo pinta automáticamente)
Recogida de datos del compradorPortal beTickets (tras redirect)
Cobro con pasarelabeTickets (Redsys)
Envío de entradas por emailbeTickets
Notificar pago a beTickets (/confirm)No aplica — beTickets cierra la orden internamente
Llamada a /auth/tokenNo aplica desde el integrador — el widget la hace por dentro

Resumen: el integrador solo embebe el widget. Todo lo demás lo gestiona el widget + el portal de beTickets.

3. Cómo funciona internamente (resumen de un minuto)

                  TU SITE                     │       beTickets

  comprador ──► tu página de evento           │
                  │                           │
                  │ <div id="bt-widget">…     │
                  │                           │
                  ▼                           │
            widget.iife.js  ──── POST /sales-channel/v1/auth/token (widget_key → Bearer)
                  │           ──── GET  /sales-channel/v1/{slug}/config (modo + branding)
                  │           ──── GET  /sales-channel/v1/{slug}/{lang}/sessions/{id}
                  │                           │
            selección de asientos             │
            (crea orden vacía + añade seats)  │
                  │                           │
                  ▼                           │
           botón "Ir al pago"                 │
                  │                           │
                  └─► window.location.href =  │
                       {portal}/{slug}/{lang}/checkout?order_token=…


                                        Portal beTickets
                                        (datos + Redsys + email)

El widget detecta el modelo en /config (integration_mode = "widget_betickets_payment") y se configura solo:

  • showCheckoutButton = true → muestra el botón "Ir al pago".
  • checkoutBaseUrl → toma el valor de portal_checkout_base_url que devuelve el backend.
  • checkoutMode = 'redirect' → ignora cualquier checkoutMode pasado en init().

Si pasas checkoutMode o checkoutBaseUrl en init(), el widget emite un console.warn y aplica los valores del backend de todas formas.

4. Paso 1 — Configurar el canal en el dashboard

Esto lo hace el equipo de beTickets (o un usuario con permiso de admin) en el dashboard de administración.

  1. Administración → Canales de venta → [tu canal] → Configuración
  2. En "Modelo de integración" selecciona widget_betickets_payment.
  3. Comprueba que el campo "URL base del portal de checkout" (portal_checkout_base_url) apunta al portal que el comprador debe ver tras pulsar "Ir al pago". Lo normal es:
    • Producción: https://tickets.bticketing.com
    • Preproducción: https://tickets-pre.betickets.io
  4. En "Branding": logo, colores (accent_color, background_color, text_color), tipografía. El widget los aplica automáticamente.
  5. En "Widget keys" genera una nueva widget_key (formato wk_…). Es el secreto que identifica al canal desde el frontend. Trátala como pública (es válida solo desde el dominio que autorices en CORS), pero rótala si sospechas que se filtró.

No mezcles entornos

Cada entorno (prod/pre) tiene sus propias widget_key y portal_checkout_base_url. Equivocarte de entorno es el error de integración más frecuente.

5. Paso 2 — Embeber el widget

En la página donde quieres mostrar el selector de asientos:

html
<!-- 1. Contenedor (el ID puede ser cualquiera) -->
<div id="bt-widget"></div>

<!-- 2. Script del widget -->
<script src="https://widget.bticketing.com/v1/widget.iife.js"></script>

<!-- 3. Inicialización -->
<script>
  BeTickets.init({
    container:   '#bt-widget',
    channelSlug: 'sala-el-sol',    // tu slug
    sessionId:   11,               // ID de la sesión a vender
    apiKey:      'wk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    lang:        'es',             // 'es' | 'en' (default: 'es')
  });
</script>

Eso es todo lo que necesitas para el Modelo C. El widget hace por dentro:

  1. Inyecta sus estilos en <style id="bt-widget-inline-styles"> en el <head>.
  2. Llama a POST /api/sales-channel/v1/auth/token con tu widget_key y obtiene un Bearer token.
  3. Llama a GET /api/sales-channel/v1/{channelSlug}/config y aplica branding + detecta el modelo.
  4. Llama a GET /api/sales-channel/v1/{channelSlug}/{lang}/sessions/{sessionId} para pintar el mapa de butacas.
  5. Cuando el comprador añade asientos: crea la orden, añade items, mantiene el order_token en localStorage para sobrevivir a reloads.
  6. Cuando se pulsa "Ir al pago": window.location.href = {portal}/{channelSlug}/{lang}/checkout?order_token={token}.

Parámetros mínimos vs opcionales

ParámetroRequeridoNotas
containerSelector CSS o HTMLElement. Debe existir antes de llamar init().
channelSlugEl slug del canal en el dashboard.
sessionIdEl widget muestra una sesión por instancia. Para varias sesiones, mira §8.
apiKeyLa widget_key del canal.
langNo'es' por defecto.
apiBaseUrlNoÚtil solo en local (http://localhost:8000/api) o pre. En prod no lo pases.
themeNoSobreescribe el branding del canal. Úsalo solo si necesitas variar visuales por página.
onOrderUpdateNoÚtil para tracking; ver §7.
onCheckoutStartNoÚtil para tracking; ver §7.
onErrorNoRecomendado: loggea errores del widget en tu monitor.
checkoutModeNo pasarEl widget lo ignora en Modelo C y emite warning.
checkoutBaseUrlNo pasarEl widget lo ignora en Modelo C y emite warning.

6. Paso 3 — Aislar el contenedor (CSS)

El widget aplica sus propios estilos dentro del contenedor mediante variables CSS:

VariableOrigenSobreescrita por
--bt-primary/configaccent_colortheme.primaryColor
--bt-bg/configbackground_color
--bt-text/configtext_color
--bt-font/configfont_familytheme.fontFamily
--bt-radius(no enviada por backend)theme.borderRadius

Si tu site tiene un CSS oscuro o muy agresivo (resets globales, *, variables --bg/--text propias) puede colarse en el widget. Aísla el contenedor con un wrapper neutro:

html
<style>
  /* Aislar el host del widget si tu site tiene CSS oscuro/agresivo */
  #bt-widget {
    background: #ffffff;
    color: #111827;
    border-radius: 12px;
    overflow: hidden;
    /* Cuidado: variables con el mismo nombre que las tuyas */
    --bg: #ffffff;
    --text: #111827;
    --surface: #f9fafb;
    --muted: #6b7280;
    --color-primary: #f59e0b;
  }
</style>

<div id="bt-widget"></div>

La demo sales-channels/sala-el-sol/index.html muestra exactamente este patrón para integrar el widget dentro de un layout oscuro.

7. Paso 4 — Tracking y analytics (opcional)

El widget expone callbacks que no afectan al flujo, pero te dan ganchos para enviar eventos a tus herramientas de marketing/analytics:

html
<script>
  BeTickets.init({
    container:   '#bt-widget',
    channelSlug: 'sala-el-sol',
    sessionId:   11,
    apiKey:      'wk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    lang:        'es',

    // Cambio en el carrito (selección/deselección de asientos)
    onOrderUpdate: (order) => {
      const items = (order?.events ?? [])
        .flatMap(ev => (ev.sessions ?? []).flatMap(s => s.items ?? []));
      window.dataLayer?.push({
        event: 'bt_cart_updated',
        cart_items: items.length,
        cart_value: Number(order?.total_amount ?? 0),
      });
    },

    // Justo antes del redirect al portal de beTickets
    onCheckoutStart: () => {
      window.dataLayer?.push({ event: 'bt_checkout_initiated' });
      window.fbq?.('track', 'InitiateCheckout');
    },

    // Errores del widget (auth, /config, API)
    onError: (err) => {
      console.error('[bt-widget]', err);
      // Sentry, Datadog, etc.
    },
  });
</script>

Conversión Purchase

El evento Purchase ocurre en el portal de beTickets, fuera de tu site. Si necesitas atribuirlo en tu lado, dos opciones:

  1. Configurar Meta Conversions API / GA4 Measurement Protocol en el dashboard (beTickets dispara los eventos server-to-server con los identificadores del comprador).
  2. Pasar parámetros UTM en el redirect; el portal los reenvía a los pixels configurados.

Coordina con el equipo de beTickets antes de duplicar tracking.

8. Paso 5 — Varios eventos en la misma página

El widget mantiene un cliente HTTP y un Bearer token compartidos a nivel de módulo. Por eso solo puedes tener una instancia activa a la vez por canal. Si necesitas cambiar de sesión (por ejemplo, al pulsar en otro evento de un listado lateral):

html
<script>
  let instance = BeTickets.init({
    container:   '#bt-widget',
    channelSlug: 'sala-el-sol',
    sessionId:   11,
    apiKey:      WIDGET_KEY,
    lang:        'es',
  });

  function showSession(newSessionId) {
    instance.destroy();   // imprescindible
    instance = BeTickets.init({
      container:   '#bt-widget',
      channelSlug: 'sala-el-sol',
      sessionId:   newSessionId,
      apiKey:      WIDGET_KEY,
      lang:        'es',
    });
  }
</script>

Antipatrón a evitar: dos BeTickets.init() activos a la vez en la misma página con el mismo canal. Romperás la autenticación de la primera instancia.

9. Probar localmente

El repositorio incluye un demo HTML del Modelo C que sirve como referencia ejecutable:

ticketera/sales-channels/sala-el-sol/index.html

Para probarlo en tu Laragon local:

  1. Levanta la API: cd ticketera && composer dev (o php artisan serve).
  2. Build del widget: cd ticketera-widget && npm run build.
  3. Abre en el navegador: http://localhost/beTickets/ticketera/sales-channels/sala-el-sol/.
  4. Las URLs del demo apuntan a http://localhost:8000/api (API) y al portal en http://localhost:5174 (ticketera-portal/ npm run dev).

Cuando pulses "Ir al pago", verás que el redirect va a http://localhost:5174/sala-el-sol/es/checkout?order_token=.... Si tienes el portal corriendo, completarás el flujo en tu máquina.

10. Checklist antes de producción

Antes de publicar el widget en el site del canal en producción:

  • [ ] widget_key de producción (no de pre). Verifica el prefijo wk_… con el equipo de beTickets.
  • [ ] Modelo del canal en el dashboard = widget_betickets_payment. Confirma con GET /api/sales-channel/v1/{slug}/config que integration_mode devuelve exactamente ese valor.
  • [ ] portal_checkout_base_url del canal = https://tickets.bticketing.com (o tu CNAME, p. ej. tickets.tucanal.com).
  • [ ] El dominio donde se sirve la página está autorizado en CORS / Origin allowlist de beTickets. Confirma con Origin: https://tu-dominio.com en una request real.
  • [ ] El branding (logo, colores, tipografía) se aplica como esperas. Si no, comprueba que el contenedor está aislado del CSS del site.
  • [ ] No pasas checkoutMode ni checkoutBaseUrl en init(). Si los pasas, mira los console.warn del widget.
  • [ ] El callback onError está conectado a tu sistema de logging.
  • [ ] Multi-sesión: si listas varias sesiones, llamas destroy() antes de cada init() nuevo.
  • [ ] HTTPS en producción. El portal de beTickets solo redirige bajo HTTPS.
  • [ ] Probado el flujo end-to-end con una tarjeta real de pruebas: selección → ir al pago → datos → Redsys (3D Secure) → email con entradas.
  • [ ] Idempotencia probada: si el comprador pulsa "Atrás" desde el portal y vuelve al site, el order_token sigue en localStorage y el carrito se recupera.

Para una checklist más completa de certificación (incluye partes que no son tuyas como pen-test, monitoring), ver Checklist de certificación.

11. Errores comunes y cómo diagnosticarlos

SíntomaCausa probableSolución
[BeTickets] No se encontró el contenedorEl <div id="..."> no existe cuando init() se ejecuta.Mueve el <script> después del contenedor o usa DOMContentLoaded.
Error de autenticación. Comprueba la widget key.La widget_key está revocada, mal copiada, o es de otro entorno.Regenera en el dashboard y comprueba el prefijo wk_….
Este canal no permite el uso del widget embebidoEl canal está configurado como portal_only (Modelo A).Cambia el modelo del canal a widget_betickets_payment en el dashboard.
console.warn "checkoutMode is overridden by backend"Pasaste checkoutMode o checkoutBaseUrl en init().Quítalos: el widget se autoconfigura.
El botón "Ir al pago" no apareceEl canal está en widget_external_payment (Modelo D) o el backend devuelve otro modelo.Verifica integration_mode en GET /config.
El redirect va a https://tickets.bticketing.com en vez de a tu CNAMEEl campo portal_checkout_base_url del canal no está configurado o apunta al default.Edita el canal en el dashboard.
401 al hacer cualquier llamadaEl Bearer token expiró y el widget no lo renegoció.Llama instance.destroy() y re-inicializa.
Los estilos del widget se rompen con tu siteTu CSS global (*, resets, variables CSS con mismo nombre) está pisando los del widget.Aísla el contenedor con un wrapper como en §6.
El carrito se vacía al recargarHas limpiado localStorage o el order_token ha expirado en el backend.Espera a un expires_at razonable o consume expires_at en tu UI para avisar al comprador.

Para más casos, consulta el troubleshooting general.

12. Referencias

beTickets — Plataforma de ticketing