Contenido

Documentación

Widget

El embed, sus atributos, los eventos que emite y los métodos que le podés llamar.

El embed

El loader es un archivo de JavaScript sin dependencias que busca en tu página todos los elementos con data-pov y le pone adentro un iframe a pov.uy.

<div data-pov data-showtime="cmf9k2p7x0004qz8lhd3v6r1a" data-key="pk_test_xxxxxxxx"></div>
<script src="https://pov.uy/v1/embed.js" async></script>

La URL está versionada y es un contrato. Dentro de v1 sólo se agregan atributos, eventos y métodos: nunca se quita ni se cambia el significado de lo que ya existe. Un cambio incompatible saldría en /v2/embed.js y habría que optar por él cambiando la URL. Tu sitio no se rompe solo. (https://pov.uy/embed.js sirve exactamente el mismo archivo, congelado en v1.)

Podés poner varios widgets en la misma página —una cartelera con tres funciones, por ejemplo—: el loader monta cada data-pov por separado y cada uno emite sus eventos sobre su propio nodo. Un <script> alcanza para todos.

El alto se ajusta solo. El iframe le avisa a la página cuánto mide y el loader lo acomoda (entre 200 y 4000 px). No hace falta que le pongas alto ni que escuches nada.

Qué trae el iframe puesto: sandbox acotado (scripts, mismo origen, formularios y los popups de 3-D Secure; no puede navegar tu página), referrerpolicy que no le manda tu URL completa a POV, y carga diferida.

Atributos del div

AtributoRequeridoQué hace
data-povSíMarca el elemento. Sin valor.
data-showtimeSí*Id de la función en POV. Es una cadena opaca: copiala de Programación → «Código» y no intentes construirla ni validar su formato.
data-venue + data-refSí*Alternativa a data-showtime: la sala de POV más tu referencia de la función. Ver Programación externa.
data-keySíTu clave pública pk_….
data-modeNoQué superficie se muestra. Ver la tabla de abajo.
data-title · data-starts-atNoSólo con data-venue+data-ref: título a mostrar y comienzo (ISO 8601). Sin fecha se toma el momento del primer embebido.
data-localeNoIdioma/región con que se escribe la fecha de la función. Sin él manda la configuración regional de tu cuenta: lo normal es no ponerlo. La zona horaria no se cambia desde acá — es a qué hora empieza la función, no cómo se escribe.
data-accentNoColor de acento del selector (#2c69d6), sin entrar al panel. Lo permanente se configura en Marca → Diseño.
data-baseNoOrigen alternativo del widget (desarrollo). Restringido a pov.uy y localhost.

* Va uno de los dos: data-showtime, o el par data-venue + data-ref. Los dos a la vez es un error y el loader lo dice por consola en vez de elegir en silencio.

data-mode

ValorComportamiento
sin atributoSelector 2D + previsualización 3D + compra. Es el modo por defecto.
viewer (o 3d)Sólo el visor 3D, con la disponibilidad de la función. Sin precio, sin selección de compra, sin bloqueo.
3d-compraLa sala 3D es la superficie de selección, con el mismo panel de compra y los mismos eventos que el modo por defecto.

Sobre data-key

data-key es una declaración, no un parámetro: la clave pública que el widget usa la resuelve el servidor a partir de la función, y además por modo (una función de prueba se sirve con tu clave de prueba, una real con la real). Por eso la clave no viaja en la URL del iframe —quedaría en el Referer, en el historial y en los logs de acceso.

Consecuencia práctica que conviene saber: si te equivocás de pk_, el widget funciona igual. Ponela bien de todos modos: es lo que mira soporte cuando algo no anda, y es la clave que usarías si llamás a la API directamente desde tu página.

Eventos que emite

El widget avisa a tu página por dos caminos, y podés usar cualquiera de los dos:

const nodo = document.querySelector('[data-pov]');

// 1. eventos del DOM sobre el propio div
nodo.addEventListener('pov:hold', (e) => console.log(e.detail));

// 2. un callback global, que recibe todos los eventos de todos los widgets
window.POVOnEvent = (payload, nodo) => {
  if (payload.source !== 'pov') return;          // ignorá cualquier otra cosa
  switch (payload.type) {
    case 'pov:hold':  cobrar(payload.holdToken, payload.amountCents); break;
    case 'pov:error': avisar(payload.code, payload.message);          break;
  }
};

El payload va siempre PLANO en e.detail: e.detail.holdToken, e.detail.seats, e.detail.amountCents. Nunca anidado. Además de sus campos propios, todo evento trae source: 'pov' y type, así que payload.type es seguro de leer y sirve para despachar con un solo manejador.

EventoCuándoCampos
pov:readyel widget terminó de montarshowtimeId
pov:selectioncambió la selección de butacasseats, amountCents, currency
pov:holdse creó el bloqueoholdToken, expiresAt, seats, amountCents, currency, discountCents?, concessions?
pov:hold-expiredse agotó la cuenta regresivaholdToken
pov:errorfalló una operacióncode, message
pov:resizecambió el altoheight — lo consume el loader; no necesitás escucharlo

pov:selection se emite en cada cambio, también cuando la selección queda vacía: sirve para pintar un resumen en tu página que siempre esté al día.

amountCents y priceCents son enteros en centavos: 32000 es $ 320,00. Los code de pov:error son los mismos de la API. El más frecuente es SEAT_TAKEN, y viene con la lista exacta de butacas en conflicto — el widget ya las saca de la selección, refresca el plano y se lo dice al espectador con nombre y apellido («La butaca F-12 la tomaron recién»).

Tipos de entrada

Si la cuenta definió tipos —jubilado, estudiante, menor—, el panel de compra suma un desplegable por butaca: dos generales y un jubilado en la misma compra es el caso normal de una familia, no la excepción. El precio de la línea se actualiza al elegir, y pov:selection y pov:hold traen el ticketType de cada butaca.

No hay nada que hacer del lado del anfitrión: se configuran en el panel (Tipos de entrada) y aparecen solos. El importe lo sigue calculando el servidor.

Métodos JavaScript

Cada nodo montado expone su propia instancia:

const nodo = document.querySelector('[data-pov]');

nodo.pov.release(holdToken);                          // soltar el bloqueo
nodo.pov.setTheme({ accent: '#2c69d6' });             // colores de marca, en vivo
nodo.pov.setCss('.pov-seat { border-radius: 2px }');  // CSS propio, en vivo
nodo.pov.focusSeat({ row: 'F', number: '12' });       // enfocar una butaca en el 3D
nodo.pov.key;                                         // la pk_ declarada, para diagnóstico

Y el loader expone su propia API, por si montás widgets a mano:

POV.init();        // monta todos los [data-pov] que todavía no estén montados
POV.mount(nodo);   // monta un div concreto (p. ej. tras traer contenido con JS)
POV.version;       // versión del loader, útil para soporte

release(holdToken) manda el bloqueo de vuelta al inventario en el acto. Llamalo cuando tu checkout se cancela o el comprador cierra el modal: sin eso las butacas quedan tomadas los 10 minutos completos, y a las otras personas que están mirando la sala eso les pesa.

setTheme(colores) acepta accent, screen, available, occupied, selected, seatIcon y background. Es para ajustar en vivo (una previsualización, un tema que cambia con el modo oscuro de tu sitio); lo permanente se configura en Marca → Diseño.

setCss(css) inyecta CSS dentro del widget, saneado antes de aplicarse. Lo permanente vive en Marca → CSS.

focusSeat(butaca) acepta focusSeat('seat_abc') o focusSeat({ row, number }), y repetir la misma butaca vuelve a centrar la cámara. Sólo tiene efecto en data-mode="viewer", que es para lo que se hizo: mostrar en 3D la butaca que alguien eligió en tu sistema de reservas.

Sólo la sala 3D

Para el cine o teatro que ya tiene su sistema de reservas y quiere sumar la sala 3D como argumento de venta, sin cambiar su flujo.

<div data-pov data-mode="viewer" data-showtime="cmf9k2p7x0004qz8lhd3v6r1a" data-key="pk_test_xxxxxxxx"></div>
<script src="https://pov.uy/v1/embed.js" async></script>

Muestra la sala con la disponibilidad real de esa función —libre, tomada, vendida— y deja recorrerla y pararse en cualquier butaca. No muestra precios, no deja seleccionar para comprar y nunca emite pov:hold. Emite pov:ready.

El iframe toma un alto fijo (72 % del alto de la ventana, mínimo 520 px): la escena llena el marco y no se auto-ajusta.

Combinado con focusSeat(), el patrón habitual es: el espectador elige la butaca en tu sistema, y vos le mostrás desde ahí. El mapeo entre tu asiento y el de POV lo resolvés por seatId (del plano de la sala) o por row + number.

Si la sala está marcada solo-2D en el panel, el modo visor no se sirve aunque lo pidas: cae al selector 2D, que vende igual. Es lo que hace que el interruptor de la sala mande de verdad.

Comprar desde la sala 3D

La escena reemplaza al mapa 2D como superficie de selección. El panel de compra de la derecha es exactamente el mismo, y los eventos también.

<div data-pov data-mode="3d-compra" data-showtime="cmf9k2p7x0004qz8lhd3v6r1a" data-key="pk_test_xxxxxxxx"></div>
<script src="https://pov.uy/v1/embed.js" async></script>

La venta nunca depende de que la escena se dibuje. Si el navegador no tiene WebGL, o la sala está marcada solo-2D, aparece el mapa 2D y se vende con él. Mientras se averigua si hay WebGL se muestra el mapa dentro de una caja del alto de la escena, así el cambio no mueve la página de lugar.

Ese orden es a propósito: lo primero que aparece es lo que funciona siempre. Un placeholder se vería más prolijo, pero dejaría la pantalla sin nada usable si la hidratación tarda. Y dentro de la sala, el botón «Elegir butaca» abre un mapa accesible por teclado, así que tampoco hace falta apuntar en 3D.

Programación externa

Para cuando tu cartelera ya vive en tu sistema y no querés cargarla dos veces.

En vez de nombrar la función con el id de POV, la nombrás con el tuyo. El único id de POV que copiás es el de la sala, y lo copiás una vez:

<div data-pov
     data-venue="cmf9jw1n50002qz8l7b4tc9ky"
     data-ref="funcion-4271"
     data-key="pk_test_xxxxxxxx"></div>
<script src="https://pov.uy/v1/embed.js" async></script>

La plantilla de tu web escribe data-ref desde tu propia base. No hay ningún id nuestro que guardar por función. Hay dos formas de que esa referencia exista, y elegís una.

Nivel 1 — tu backend da de alta la función

Tu CMS llama a POV cada vez que publica o mueve una función:

PUT /api/v1/showtimes/by-ref/funcion-4271
Authorization: Bearer sk_live_xxxxxxxx
Content-Type: application/json

{ "venueId": "cmf9jw1n50002qz8l7b4tc9ky", "startsAt": "2026-09-12T23:30:00Z", "title": "La Última Órbita" }

Es idempotente: llamalo mil veces con lo mismo y hay una sola función. Eso es justamente lo que te permite dispararlo en cada guardado, sin llevar la cuenta de si ya existía. Detalle completo en Programar por referencia.

Nivel 2 — la función nace al embeberla

Si activás «Programación desde tu web» en la sala (panel → Salas → la sala), embeber una referencia que todavía no existe la crea en el momento, con las butacas y los precios de esa sala. Sin ninguna llamada previa, sin cargar programación.

<div data-pov data-venue="cmf9jw1n50002qz8l7b4tc9ky" data-ref="funcion-4271"
     data-title="La Última Órbita" data-starts-at="2026-09-12T23:30:00Z"
     data-key="pk_test_xxxxxxxx"></div>

Es la integración más corta que existe y también la más expuesta —el id de la sala está en el HTML de quien la embebe, a la vista de cualquiera—, así que viene con frenos:

el interruptor está apagado por defecto, y es por sala (apagado, una referencia desconocida es un 404 y no pasa nada) · hay un tope de funciones auto-creadas por cuenta · una cuenta suspendida no crea nada · las funciones así creadas quedan marcadas en Programación como «Desde tu web», para que nadie se encuentre con una lista de funciones que no recuerda haber creado.

data-title y data-starts-at los declara el navegador, y es aceptable porque no son dinero: el precio sale de los sectores de la sala configurados en POV y no se recibe de ningún lado. Sin data-title el widget no dibuja el bloque de título — el nombre lo ponés vos, en tu página, alrededor del iframe.

Sala por día

Una muestra permanente, un museo, una sala que abre de corrido: no tiene funciones con horario, tiene días. Se activa por sala («Sala por día») e implica el alta automática, porque el día sólo puede nacer al embeberlo.

Cada día es su propio inventario —la butaca A-1 del martes no es la del miércoles— y el widget muestra el día sin la hora: poner un horario en una sala que abre de corrido haría creer que hay que llegar a esa hora.