Contenido

Documentación

Empezar

Qué es POV, el modelo mental, y tu primera venta de punta a punta.

Qué es POV

POV es un sistema de reservas de asientos que se embebe en tu web. No es un sitio de venta de entradas al que mandás a tu público: es un módulo que vive dentro de tu página, con tu diseño y tu dominio a la vista.

Trae dos cosas que normalmente vienen separadas: un selector de butacas 2D —el plano de la sala, con lo que está libre, tomado y vendido— y una sala en 3D donde el espectador puede pararse en la butaca y ver desde ahí. Las dos leen el mismo asiento: no hay dos mapas que mantener.

El modelo mental

Todo lo demás se entiende a partir de esta secuencia. Vale la pena leerla antes de copiar código.

1. widget        el espectador elige butacas en tu página
2. hold          POV bloquea esas butacas 10 minutos y te avisa
3. cobro         VOS cobrás, con tu pasarela, tu checkout, tus reglas
4. confirm       tu servidor le dice a POV "cobrado" y la venta se cierra
5. entradas      POV emite un QR por butaca

Dónde termina POV y dónde empezás vos: POV administra el inventario —qué butacas hay, cuáles están libres, cuánto vale cada una, quién las tiene bloqueadas y quién las compró— y emite las entradas. La plata no pasa por POV en el plan estándar: el cobro es tuyo, con tu pasarela. Nosotros bloqueamos, avisamos y confirmamos.

Si preferís no montar un cobro, existe el plan Pasarela POV, donde el espectador paga dentro del widget y POV te liquida. Ahí el cobro con tarjeta lo procesa Stripe, que es la pasarela de POV. Está en Administración → Pasarela POV.

Las tres piezas

PiezaQué esDónde vive
Widgetel iframe con el selector y la sala 3Del navegador de tu espectador
APIfunciones, disponibilidad, bloqueos, confirmación, entradastu servidor (y el widget)
Panelsalas, programación, precios, reportes, clavesempresa.pov.uy

Las dos claves

ClaveDónde vaQué puede hacer
pk_live_… / pk_test_…en el navegador (tu HTML, tu JS)leer funciones y disponibilidad, crear y soltar bloqueos
sk_live_… / sk_test_…sólo en tu servidorconfirmar ventas, validar entradas en la puerta, deshacer ventas, programar funciones

La regla es corta: si una operación mueve dinero o quema una entrada, va con sk_. Una clave secreta en el navegador es una clave publicada; si se te escapó una, revocala desde Integración → Claves y creá otra.

Las claves públicas están además acotadas por orígenes permitidos: si tu sitio llama a la API con la pk_ desde https://tucine.com, ese dominio tiene que estar en la lista.

Quickstart (5 minutos)

En dos líneas de HTML tenés el selector funcionando. Poné esto en la página de la función:

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

Eso ya te da: el plano de la sala con la disponibilidad en vivo, la vista 3D, el cálculo del total con impuestos y descuentos, y el bloqueo temporal de las butacas elegidas.

Lo que todavía no te da es la venta cerrada: para eso hay que escuchar el bloqueo, cobrarlo y confirmarlo desde tu servidor. Son los tres pasos de Tu primera venta. Si sólo querés mostrar la sala, con estas dos líneas alcanza y podés parar acá.

¿Preferís partir de algo que ya anda? Hay proyectos completos para clonar.

De dónde sale cmf9k2p7x0004qz8lhd3v6r1a: del panel, en Programación → la función → botón Código, que te copia el <div> entero con tu clave real adentro. Si tu cartelera ya vive en tu CMS y no querés copiar un id por función, mirá Programación externa.

Modo de prueba

POV tiene un sandbox de verdad, no un interruptor de fachada.

Cada clave nace en un modo, y el prefijo lo dice: pk_test_ / sk_test_ operan sólo sobre funciones marcadas de prueba; pk_live_ / sk_live_ sólo sobre las reales. Una función de prueba se marca al crearla en el panel.

Se comparte entre modosEstá aislado
tus salas y su planoel inventario: butacas, bloqueos, ventas
tu contenido (películas, obras)los reportes y los ingresos
tu marca, tus impuestos, tus itemslas entradas y el check-in

Una sk_test_ no puede quemar una entrada real, y una pk_test_ no puede bloquear una butaca que alguien está por pagar. El cruce responde 404, no «modo incorrecto»: la función del otro modo, para esa clave, no existe.

Necesitás una clave pública de cada modo que uses. El iframe de una función de prueba se sirve con tu pk_test_ y el de una real con tu pk_live_; si te falta la que corresponde, la fila de Programación te lo dice en vez de darte un snippet que carga bien y falla al tocar la primera butaca. Cuando terminaste de probar, borrá la función de prueba y listo.

El cobro no se simula. POST /holds/{token}/pay (Pasarela POV) responde 409 con clave de prueba: POV tiene una sola cuenta de cobro, así que un pago «de prueba» sería plata de verdad. Para ejercitar tu receptor de webhooks está POST /test/events.

Tu primera venta

Los tres pasos que van después del widget.

Paso 1 — escuchar el bloqueo

Cuando el espectador aprieta «Reservar», el widget crea el bloqueo y avisa a tu página:

document.querySelector('[data-pov]').addEventListener('pov:hold', (e) => {
  const { holdToken, seats, amountCents, currency, expiresAt } = e.detail;
  // amountCents ya viene con descuentos, items e impuestos aplicados,
  // calculado en el servidor.
  arrancarCheckout({ holdToken, amountCents, currency, expiresAt });
});

El importe no lo calculás vos. amountCents es lo que hay que cobrar, en centavos enteros. Recalcularlo del lado del cliente es la forma más común de terminar cobrando otra cosa que la que se vendió.

Tenés hasta expiresAt —10 minutos desde el bloqueo— para cobrar y confirmar. Pasado ese punto las butacas vuelven al inventario.

Paso 2 — cobrar

Con tu pasarela, tu checkout y tus reglas. POV no participa. Guardá el holdToken junto al pago: es lo que los une.

Paso 3 — confirmar, desde tu servidor

El cuerpo es opcional. Sin él la venta se cierra igual; con él nos decís quién compró y con qué pago, y eso es lo que después ves en Reservas y lo que te deja cuadrar tus pagos contra nuestras reservas. Ningún importe viaja acá: el total sale del bloqueo, calculado en el servidor.

POST /api/v1/holds/hld_abc123/confirm
Authorization: Bearer sk_live_xxxxxxxx
Idempotency-Key: pago-99182
Content-Type: application/json

{ "buyer": { "name": "Ana Pérez", "email": "[email protected]" },
  "externalPaymentRef": "mp_12345" }
200 OK
{
  "reservationId": "cmt63dj3b000vcpqsxbasv1fq",
  "publicToken": "rsv_5f3a…",
  "status": "CONFIRMED",
  "tickets": [
    { "id": "cmt63dj3e000zcpqsw13w3epw",
      "seat": { "id": "F-12", "row": "F", "number": "12", "type": "STANDARD" },
      "qr": "qr_7c1e…" }
  ],
  "amountCents": 64000,
  "currency": "UYU"
}

Con eso la venta está cerrada: las butacas pasan a SOLD y quedan emitidas las entradas, una por butaca, cada una con su qr.

El qr de cada entrada habilita esa butaca en la puerta. El publicToken es la compra entera: sirve para el enlace https://pov.uy/r/{publicToken}, que le muestra al comprador sus entradas y sus QR.

Los datos del comprador se guardan una sola vez, al crear la reserva. Un confirm repetido devuelve la misma venta y no pisa lo registrado: si el reintento trajera otros datos, la reserva terminaría describiendo un pago que no es el que la cerró. Y si preferís no mandarnos nada, no lo mandes: la venta se cierra igual y la columna queda vacía a propósito.

Reintentá sin miedo. Confirmar es idempotente: el mismo holdToken devuelve siempre la misma reserva y las mismas entradas. Mandá Idempotency-Key igual —una por venta— y un timeout de red deja de ser un problema.

Qué pasa si el pago entra justo cuando vence el bloqueo

Es el caso caro y está resuelto. Si el bloqueo venció hace menos de 10 minutos y nadie tomó esas butacas, el confirm las retoma y cierra la venta igual: el comprador pagó, y lo que pagó es lo que recibe. Se factura con el precio cotizado en el bloqueo, no con el vigente hoy.

Si en cambio alguien ya se las llevó, no hay nada que rescatar:

410 Gone
{ "error": { "code": "HOLD_EXPIRED",
             "message": "El bloqueo venció y las butacas ya no están disponibles",
             "details": { "unavailableSeats": ["F-12", "F-13"] } } }

Ese unavailableSeats está para que reembolses con información: podés decirle al comprador qué butaca se perdió, en vez de un error genérico. Un bloqueo que vos soltaste nunca se rescata: soltarlo es una cancelación deliberada.

Si tu checkout se cancela

Soltá el bloqueo para que las butacas vuelvan enseguida al inventario, en vez de esperar los 10 minutos:

document.querySelector('[data-pov]').pov.release(holdToken);