Skip to Content
Documentación oficial de Tu Banca.
DesarrolladorAPI de ventas v1

API de ventas v1

La API de ventas concentra en el servidor la autorización, disponibilidad de sorteos, límites, validaciones y creación de tickets. Los clientes no deben calcular ni enviar moneda, agencia, comisiones, límites o el total final.

URL de producción:

/api/v1/store/sales

Migraciones de base de datos

La creación de tickets requiere las migraciones de supabase/migrations/. Los triggers que validan loterías activas y horarios usan referencias calificadas (public.lotteries, public.lottery_schedules) porque la RPC de creación de tickets ejecuta con search_path vacío. Si una venta devuelve relation "lotteries" does not exist, aplica la migración 20261012120606_qualify_ticket_number_validation_relations.sql; no es necesario crear otra tabla ni ampliar el search_path.

Autenticación

La API requiere un usuario autenticado. Desde la web de esta aplicación se usa la sesión por cookies (no se envía el header Authorization). Para un cliente externo se usa un API token de taquilla, que el dueño genera, rota y revoca desde la web (/banca/taquillas/:id):

Authorization: Bearer tbk_<api-token>

El token empieza con el prefijo tbk_ y está vinculado a una sola taquilla: todas las solicitudes (access, sales) deben apuntar a esa taquilla; si el taquillaId no coincide, la API responde 403 TOKEN_TAQUILLA_MISMATCH. El token solo se muestra una vez al generarlo o rotarlo: guárdalo de forma segura en el cliente. Si se pierde o se sospecha de fuga, revócalo y genera uno nuevo.

El usuario debe estar activo, no tener rol client y ser el propietario de la taquilla solicitada. La API también aplica las excepciones de desarrollo configuradas por el servidor.

La ruta permite CORS para GET, POST y OPTIONS. No envíes claves service_role ni otros secretos al cliente.

Cada solicitud a access o sales valida el token de manera independiente. No es obligatorio llamar primero a access: aunque un cliente omita esa consulta, POST /sales vuelve a comprobar autenticación y todas las reglas de acceso antes de crear el ticket. Un token tbk_ inválido o revocado devuelve 401 INVALID_API_TOKEN; una sesión web ausente o vencida devuelve 401 AUTHENTICATION_REQUIRED.

Comprobar acceso a una taquilla

Este endpoint ejecuta los mismos controles usados para la creación de tickets. Es útil para bloquear o redirigir al usuario en una aplicación externa antes de cargar el contexto completo.

GET /api/v1/store/access?taquillaId=<uuid> Authorization: Bearer tbk_<api-token>

Respuesta permitida (200):

{ "data": { "allowed": true, "timerExpiresAt": 1786993200000 } }

Respuesta denegada (400, 401, 403, 404 o 500, según la causa):

{ "data": { "allowed": false, "code": "PORTAL_CLOSED", "status": 403, "variant": "warning", "title": "Portal cerrado", "message": "El horario de ventas ha finalizado.", "recoveryAction": "OPEN_TAQUILLA" } }

El endpoint comprueba banca activa, horario, modo standby y grupos excluidos; autenticación, rol, estado y propiedad del usuario; estado y moneda de la taquilla; agencia, grupo y zona; antigüedad del último cuadre; límites de venta y utilidad de banca; y creación del temporizador de sesión. Los textos, estilos y reglas habilitadas se toman de bank_access_control.

El cliente debe usar el estado HTTP para el flujo de red y data.code para decisiones programáticas. title, message y variant están listos para presentar el rechazo al usuario.

recoveryAction expresa una intención independiente de la plataforma y nunca contiene una URL. Sus valores actuales son:

AcciónSignificado
GO_HOMESalir del flujo de venta y volver a la pantalla principal definida por el cliente.
OPEN_TAQUILLASalir de la venta y abrir la información de la taquilla solicitada.

Cada aplicación decide cómo navegar. Por ejemplo, la web puede mapear OPEN_TAQUILLA a /banca/taquillas/:id, mientras una app móvil puede abrir TaquillaDetails y otro sistema web puede usar su propia ruta. El cliente también puede ignorar recoveryAction y limitarse a mostrar el rechazo.

Consultar la venta disponible

Obtiene el contexto vigente de una taquilla: sorteos abiertos, loterías permitidas, límites, ventas del día y la hora de Caracas usada por el servidor.

GET /api/v1/store/sales?taquillaId=<uuid> Authorization: Bearer tbk_<api-token>

Ejemplo con fetch:

const response = await fetch( `${API_URL}?taquillaId=${taquillaId}`, { headers: { Authorization: `Bearer ${apiToken}` }, } ) const result = await response.json() if (!response.ok) throw new Error(result.error.message) const saleContext = result.data

Respuesta exitosa (200):

{ "data": { "taquilla": { "id": "uuid", "name": "Taquilla principal", "currency": "VES", "agency_id": "uuid" }, "agency": { "min_bet": 100, "max_bet": 100000, "lotteries_ids": ["uuid"], "mp": true }, "bank": { "timeout": 5, "bank_sales_fee": 0, "partner_sales_fee": 0, "partner_percent_fee": 0 }, "lotteries": [ { "id": "uuid", "name": "Lotería", "lottery_type": "O", "timeout": 5, "min_number": 0, "max_number": 99 } ], "schedules": [ { "id": "uuid", "lottery_id": "uuid", "h": 14, "m": 30, "days_available": [0, 1, 2, 3, 4, 5, 6] } ], "caracasTime": { "year": 2026, "month": 7, "day": 29, "weekday": 3, "h": 14, "m": 12, "s": 0 } } }

data también incluye dailyNumberSales, customTicketNumbers, customScheduleNumbers, customWinnerTimesNumbers, agencyLimits y numbersByAgencyIdInDates. agency.mp indica si la agencia envía ventas al operador MP. bank contiene el tiempo de cierre y las comisiones aplicables.

lottery_type permite filtrar las loterías por tipo y puede tener los valores O, N, Z o COND. cond_by_lottery es el UUID nullable de la lotería principal. Las COND no son entradas de venta directa.

schedules contiene únicamente sorteos activos para el día y la hora de Caracas. Los arreglos adicionales se exponen para presentar disponibilidad en tiempo real. El servidor vuelve a consultar y validar todo al crear el ticket.

Crear un ticket

Envía únicamente la taquilla, el número, el sorteo y el monto. amount se expresa en la unidad visible de la moneda, no en centavos. Por ejemplo, para vender 10.00, envía 10.

No envíes prize: el servidor lo calcula con el multiplicador personalizado vigente para el número y la lotería, o con winner_times de la lotería cuando no hay una configuración personalizada.

Jugadas condicionadas

Envía únicamente las jugadas principales. El servidor agrega automáticamente todas sus loterías COND activas y habilitadas para la agencia en sorteos con la misma hora y minuto. Para Triple Fácil (ad609cf6-63de-4533-9723-fc04b63920fd), 123 produce 22 y 24, 000 produce 99 y 01, y 299 produce 98 y 00.

Cada derivado hereda el monto del triple para calcular su premio con el multiplicador de la COND, sin aumentar el cobro, ventas o comisiones y sin consumir límites. Si varios triples generan el mismo número/sorteo, se acumulan sus apuestas conservando todos los orígenes. No envíes derivados ni metadatos de origen en lines: se reconstruyen en el servidor.

Los números bloqueados por las reglas de la lotería y los sorteos no disponibles rechazan la jugada antes de guardar. Para una venta con jugadas condicionadas, si un número principal o derivado está bloqueado por un operador, se rechaza la venta antes de crear el ticket; una COND habilitada con regla aún no implementada también rechaza la venta. En una venta sin jugadas condicionadas, los números bloqueados por un operador se quitan del ticket: si todos quedan bloqueados, el ticket se anula y la API responde 422; si quedan jugadas, conserva y ajusta las aceptadas. Si la agencia tiene mp: true, solo las jugadas principales se envían a MP y los derivados siguen las jugadas y montos que MP acepte. Si falla la reconciliación de derivados, se intenta anular el ticket y se devuelve 502 CONDITIONAL_RECONCILIATION_ERROR, sin confirmar la venta. Si data.cancel es false, también falló la anulación: el ticket requiere revisión antes de reintentar.

Las respuestas exitosas de tickets con COND incluyen totalAmount (centavos cobrados) y countNumbers (jugadas finales, incluidos derivados) en la raíz de la respuesta. Úsalos al compartir o mostrar un ticket ajustado por MP.

POST /api/v1/store/sales Content-Type: application/json Authorization: Bearer tbk_<api-token>
{ "taquillaId": "11111111-1111-1111-1111-111111111111", "lines": [ { "number": "05", "scheduleId": "22222222-2222-2222-2222-222222222222", "amount": 10 } ] }

Ejemplo con fetch:

const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiToken}`, }, body: JSON.stringify({ taquillaId, lines }), }) const result = await response.json() if (!response.ok) throw new Error(result.error.message) const ticket = result.data

Respuesta exitosa (201):

{ "data": { "ticket_id": "33333333-3333-3333-3333-333333333333", "ticket_code": 123456, "cancel": false } }

Si el operador MP acepta solo algunas jugadas o ajusta sus montos, la API también puede devolver observaciones y reconciled en la respuesta 201. reconciled indica si se pudo actualizar el ticket con las jugadas aceptadas.

Ticket anulado o rechazado

La API crea el ticket local antes de comprobar algunos rechazos de proveedores u obtener la respuesta del operador MP. Si luego debe rechazar la venta, la respuesta de error puede incluir data con el identificador y código del ticket:

{ "data": { "ticket_id": "33333333-3333-3333-3333-333333333333", "ticket_code": 123456, "cancel": true }, "error": { "code": "MP_TICKET_REJECTED", "message": "El operador rechazó todas las jugadas", "observaciones": [] } }

data.cancel: true confirma que la anulación se completó. Si cancel es false, la anulación no se pudo completar; el ticket debe verificarse antes de reintentar la venta. En ambos casos, si la respuesta HTTP no es exitosa, no se debe tratar la operación como una venta confirmada.

Si todos los números de una venta sin jugadas condicionadas están bloqueados por operadores, la API responde 422 (BLOCKED_NUMBERS_REJECTED) e incluye el resultado de la anulación. Si solo algunos están bloqueados, conserva las jugadas restantes y ajusta el ticket; si hay jugadas condicionadas y se bloquea cualquier número principal o derivado, rechaza la venta antes de crear el ticket con 422 (CONDITIONAL_NUMBER_BLOCKED). Cuando MP rechaza todas las jugadas, la API responde 422 (MP_TICKET_REJECTED). Si MP rechaza o ajusta solo algunas, puede conservar las aceptadas, reconciliar el ticket y responder 201 con observaciones y reconciled. Si la agencia no tiene MP habilitado, no se reenvían las jugadas al operador MP. Si no se pudo conectar con MP, la API responde 502 (MP_TICKET_ERROR) e incluye el ticket junto con el resultado de la anulación.

Validaciones del servidor

Antes de crear el ticket, la API verifica, entre otras reglas:

  • Que la taquilla, usuario, agencia, lotería y sorteo estén disponibles.
  • Que el token de acceso (si se usa un API token) pertenezca a la taquilla solicitada.
  • Que el sorteo pertenezca a una lotería permitida para la agencia.
  • Horario de cierre usando la hora de Caracas del servidor.
  • Números bloqueados, montos positivos, mínimos, máximos y múltiplos por moneda.
  • Límites por ticket, sorteo, número y ventas diarias de agencia.
  • Comisiones, moneda, agencia, total y premio usando los datos actuales del servidor.

Una respuesta 422 indica que una regla de la banca no pasó:

{ "error": { "code": "VALIDATION_ERROR", "message": "El sorteo ... ha cerrado. (C20)" } }

Errores

EstadoCódigoSignificado
400INVALID_REQUESTEl body no cumple el contrato: UUID inválido, línea incompleta, más de 200 líneas, monto no positivo, etc.
401INVALID_API_TOKENEl API token tbk_ es inválido o fue revocado.
401AUTHENTICATION_REQUIREDFalta el token o la sesión web es inválida o está vencida.
403TOKEN_TAQUILLA_MISMATCHEl API token no pertenece a la taquilla solicitada.
503DATABASE_UNAVAILABLENo se pudo establecer la conexión con la base de datos durante la autenticación.
400SALE_ERROREl usuario autenticado no puede operar la taquilla o falló otra regla de acceso.
422VALIDATION_ERROREl ticket no cumple una regla de la banca; muestra error.message al usuario.
422CONDITIONAL_NUMBER_BLOCKEDUn número principal o derivado está bloqueado por un operador; no se crea el ticket.
422BLOCKED_NUMBERS_REJECTEDTodas las jugadas están bloqueadas por operadores; la respuesta incluye el ticket y el resultado de la anulación en data.
422MP_TICKET_REJECTEDEl operador MP rechazó la venta o todas las jugadas; la respuesta incluye el ticket y el resultado de la anulación en data.
429RATE_LIMIT_EXCEEDEDLímite de solicitudes excedido. Aplica espera incremental antes de reintentar.
502MP_TICKET_ERRORNo se pudo completar la comunicación con el operador MP; la respuesta incluye el ticket y el resultado de la anulación en data.
502CONDITIONAL_RECONCILIATION_ERRORNo se pudieron ajustar los derivados al monto aceptado por MP; data.cancel indica si se pudo anular el ticket.

Formato común de error:

{ "error": { "code": "SALE_ERROR", "message": "No autorizado para vender en esta taquilla" } }

En los casos de anulación descritos arriba, la respuesta incluye además data; consulta data.cancel para saber si la anulación se completó.

Recomendaciones de cliente

  • Consulta GET al abrir la pantalla y vuelve a hacerlo al reanudar la aplicación o antes de mostrar disponibilidad actualizada.
  • Trata el POST como la decisión final: una disponibilidad mostrada previamente puede cambiar mientras el usuario prepara el ticket.
  • Tras un 422, actualiza el contexto con GET y muestra el mensaje recibido.
  • Guarda el API token de forma segura (por ejemplo, en el almacenamiento seguro del dispositivo) y revócalo desde la web si se filtra.
  • El endpoint no incluye idempotencia. Cada POST es considerado como un nuevo ticket.

Límites de solicitudes

Límites predeterminados:

  • 90 solicitudes por API key (o token) por minuto.
  • 90 solicitudes por IP por minuto.

El control es en memoria por instancia del servidor. Para identificar el cliente usa x-api-key o Authorization y la IP; el límite puede variar entre instancias.

Las respuestas que incluyen información del límite exponen RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset (segundos restantes de la ventana). Al superar el límite, la API responde 429 RATE_LIMIT_EXCEEDED con Retry-After en segundos:

{ "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Límite de solicitudes excedido. Espere antes de reintentar." } }

Evita consultar saldo en cada pulsación del usuario. Ante un 429, usa espera incremental con variación aleatoria y no ejecutes reintentos en bucle.

Last updated on