API de Disco (club sin efectivo)¶
El módulo Disco representa la operación de un club o discoteca gestionada de forma completamente sin efectivo: cada invitado recibe una tarjeta (chip/RFID/NFC) al entrar, que sirve como cuenta durante toda su estancia. Todo —entrada, bebidas, guardarropa, pedidos de mesa— se carga en la tarjeta y solo se liquida al salir (o mediante recarga de saldo prepago). La API cubre el ciclo de vida completo de una tarjeta: entrar → cargar → recargar → liquidar → salir.
Todos los endpoints están bajo el prefijo común api/v1/disco (tres controladores comparten este
prefijo: invitados/tarjetas, estadísticas, configuración — además de la API de hardware del
torniquete bajo api/v1/disco/turnstile). Como en todas las áreas: JWT Bearer, envoltorio de
respuesta { "success": true, "data": { ... } }, campos en camelCase, IDs opacos.
Requiere el módulo Disco
Requiere el módulo Disco; sin una licencia de módulo activa, los endpoints devuelven HTTP
403 (errorCode: "MODULE_NOT_LICENSED").
ID de tarjeta frente a ID interna
Cada registro tiene una id interna (clave de base de datos) y una guestId — este es el
número de tarjeta con el que está grabado físicamente el chip. Todas las rutas {cardId}
esperan la guestId, no la id interna.
Invitados y tarjetas¶
DiscoController — base api/v1/disco:
| Método | Endpoint | Descripción |
|---|---|---|
POST |
/enter |
Dejar entrar al invitado en el torniquete (escaneo de tarjeta en la entrada) |
POST |
/leave |
Hacer el check-out del invitado con pago (escaneo en la salida) |
POST |
/leave-combined |
Liquidar y cerrar juntas varias tarjetas («pareja») en un solo recibo |
POST |
/book |
Cargar artículo en la tarjeta (barra/mostrador) |
POST |
/book/batch |
Cargar varios artículos a la vez en la tarjeta |
POST |
/book/{guestId}/{bonIndex}/void |
Anular una posición cargada |
POST |
/table/book-on-card |
Cargar un pedido de mesa en la tarjeta (sustituye el pago en la mesa) |
POST |
/wardrobe/return/{guestId}/{bonIndex} |
Devolución de guardarropa (elimina el n.º de etiqueta, el importe se mantiene) |
GET |
/guest/{cardId} |
Invitado/tarjeta incl. consumo actual (solo tarjetas abiertas) |
GET |
/guest/{cardId}/status |
Estado de la tarjeta, también si está bloqueada/anulada/liquidada |
GET |
/guest/{cardId}/checkout |
Vista de liquidación: importe pendiente, cobertura del prepago, si puede salir |
GET |
/guests |
Todos los invitados actualmente presentes (con check-in) |
POST |
/guest/{cardId}/topup |
Recargar saldo prepago |
POST |
/guest/{cardId}/transfer |
Transferir tarjeta/saldo a otra tarjeta (cambio de tarjeta) |
POST |
/guest/{cardId}/lock |
Bloquear la tarjeta (ya no se pueden hacer cargos) |
POST |
/guest/{cardId}/unlock |
Desbloquear la tarjeta |
POST |
/guest/{cardId}/storno |
Anular toda la tarjeta y marcarla como impagada |
POST |
/guest/{cardId}/collect-storno |
Cobrar posteriormente en caja una tarjeta anulada |
POST |
/guest/{cardId}/image |
Guardar la foto de la tarjeta |
POST |
/terminal/pay |
Iniciar un pago con tarjeta de autoservicio en el terminal de información |
POST |
/personal/{cardId}/close |
Liquidar una tarjeta de personal (autoconsumo) |
POST |
/personal/close-all |
Liquidar todas las tarjetas de personal abiertas como autoconsumo |
POST |
/close-all |
Cerrar todas las tarjetas abiertas (fin de servicio/cierre de caja) |
Cargar en la tarjeta¶
POST /api/v1/disco/book carga un artículo en una tarjeta abierta (venta de barra/mostrador sin
pago — el pago se realiza solo en leave/checkout).
POST /api/v1/disco/book
Authorization: Bearer eyJ...
Content-Type: application/json
{
"guestId": "crd_10042",
"articleId": "art_cola",
"articleName": "Cola 0,3l",
"count": 2,
"price": 3.50,
"workplaceId": "wp_bar1",
"workplaceName": "Bar 1"
}
Respuesta (200 OK, DiscoGuestResponse):
{
"success": true,
"data": {
"id": "dgu_9f8e7d",
"guestId": "crd_10042",
"gender": 1,
"enterDate": "2026-07-12T22:05:00Z",
"amount": 7.00,
"payedAmount": 0,
"uploadAccount": 20.00,
"iou": 0,
"isOpen": true,
"isLocked": false,
"openBons": [
{ "articleId": "art_cola", "articleName": "Cola 0,3l", "price": 3.50, "count": 2, "total": 7.00,
"workplaceId": "wp_bar1", "workplaceName": "Bar 1", "bookDate": "2026-07-12T23:10:00Z", "isVoided": false }
]
}
}
Estado de la tarjeta¶
GET /api/v1/disco/guest/{cardId}/status devuelve el estado con independencia de si la tarjeta
sigue abierta — a diferencia de GET /guest/{cardId}, que solo encuentra tarjetas abiertas. Así, la
barra, el guardarropa o la caja también reconocen tarjetas bloqueadas o anuladas.
{
"success": true,
"data": {
"found": true,
"isOpen": true,
"isLocked": false,
"stornoed": false,
"guest": { "id": "dgu_9f8e7d", "guestId": "crd_10042", "amount": 7.00, "uploadAccount": 20.00 }
}
}
Consumo actual¶
GET /api/v1/disco/guest/{cardId} devuelve la misma DiscoGuestResponse que arriba, incl. los
cargos abiertos en curso (openBons) y el saldo prepago (uploadAccount). Respuesta
404 GUEST_NOT_FOUND si la tarjeta ya no está abierta.
Liquidación/Check-out¶
GET /api/v1/disco/guest/{cardId}/checkout no modifica nada — es solo una vista previa de lo que
la tarjeta aún debe, lo que cubre el saldo prepago y si puede salir automáticamente (autocobro, caja
de salida móvil, torniquete).
{
"success": true,
"data": {
"cardId": "crd_10042",
"found": true,
"isOpen": true,
"isLocked": false,
"consumption": 7.00,
"netOwed": 7.00,
"prepaid": 20.00,
"amountToPay": 0,
"refund": 13.00,
"canAutoSettle": true,
"status": "ok",
"message": "Karte kann raus — Restguthaben 13.00 €"
}
}
status es uno de ok | pay | locked | closed | notfound.
Mesa en la tarjeta¶
POST /api/v1/disco/table/book-on-card traslada las posiciones abiertas de una mesa a una tarjeta
Disco (modo mesa del Disco) — sustituye el pago en efectivo/tarjeta en la mesa, los límites de la
tarjeta siguen aplicándose y la mesa queda libre.
openBonIds: null transfiere todas las posiciones abiertas; una lista transfiere solo una ronda
«inmediata».
Recargar¶
POST /api/v1/disco/guest/{cardId}/topup recarga el saldo prepago de la tarjeta (uploadAccount) —
el importe se puede elegir libremente y un saldo restante se reembolsa en el checkout.
La respuesta es de nuevo la DiscoGuestResponse actualizada (uploadAccount incrementado).
Riesgo de confusión: saldo de tarjeta Disco frente a saldo de cuenta de cliente
Esto es el prepago de tarjeta de una única estancia en el Disco (caduca/se reembolsa al salir). Un saldo de cuenta de cliente permanente es un concepto propio — véase Cuenta de cliente: saldo y puntos.
Estadísticas¶
DiscoStatsController — base api/v1/disco:
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/stats |
Indicadores en vivo (ingresos, invitados presentes, ocupación por grupo/puesto de trabajo/personal/hora) |
GET |
/search |
Buscar tarjetas/invitados (número de tarjeta, nombre, periodo, sexo, filtro de anulación, paginación) |
GET |
/personal/{ownerId}/activity |
Actividad de una tarjeta de personal (consumo, anulación, rotura) |
GET |
/daylog/{date} |
Registro diario para una fecha |
Ejemplo: GET /api/v1/disco/stats
{
"success": true,
"data": {
"totalGuests": 214,
"currentGuests": 87,
"totalMale": 96,
"totalFemale": 118,
"totalRevenue": 4211.50,
"averageSpend": 19.68,
"byGroup": [ { "groupId": "eg_std", "groupName": "Standard", "count": 180, "revenue": 3500.00 } ],
"byWorkplace": [ { "workplaceId": "wp_bar1", "workplaceName": "Bar 1", "count": 400, "revenue": 2200.00 } ],
"byStaff": [ { "personalId": "per_1", "personalName": "Mo", "count": 120, "revenue": 900.00 } ],
"byHour": [ { "hour": 23, "count": 60, "revenue": 800.00 } ]
}
}
GET /search admite, entre otros, cardId, name, dateFrom/dateTo, gender,
timeFrom/timeTo, workplaceId, enterGroupId, wardrobe, stornoed, offset/limit,
excludeImages.
Configuración¶
DiscoConfigController — base api/v1/disco, CRUD clásico para los grupos de entrada/precio
(«Entergroups» — definen, p. ej., el precio de entrada, el límite de tarjeta, las bebidas gratuitas
y el consumo mínimo):
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/entergroups |
Todos los grupos de entrada |
GET |
/entergroups/{id} |
Un solo grupo de entrada |
POST |
/entergroups |
Crear grupo de entrada |
PUT |
/entergroups/{id} |
Actualizar grupo de entrada |
DELETE |
/entergroups/{id} |
Eliminar grupo de entrada |
Hardware del torniquete¶
DiscoTurnstileController — api/v1/disco/turnstile/{key}/{cardId} (GET/POST).
Sin JWT — la autenticación se realiza mediante una clave API en la URL ({key}), para que
un controlador de torniquete sencillo pueda llamar a la ruta sin flujo de inicio de sesión. 200 = la
tarjeta puede salir, cualquier otro código indica un motivo (bloqueada/abierta/ya liquidada/
desconocida/clave incorrecta).
→ Detalles, configuración y tabla de códigos de estado: Torniquete de discoteca.
Flujo de extremo a extremo¶
Un recorrido típico de invitado, todos los pasos contra crd_10042:
TOKEN="eyJ..."
API="https://ihre-instanz.dikas.de/api/v1"
# 1. Entrada en el torniquete (escaneo de tarjeta en la entrada)
curl -X POST "$API/disco/enter" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "guestId": "crd_10042", "gender": 1, "enterGroupId": "eg_std" }'
# 2. Comprobar el estado (p. ej. reentrada tras una breve salida)
curl "$API/disco/guest/crd_10042/status" -H "Authorization: Bearer $TOKEN"
# 3. Recargar el prepago
curl -X POST "$API/disco/guest/crd_10042/topup" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "amount": 20.00, "paymentMethod": "Bar" }'
# 4. Cargar en la barra (individual o por lotes)
curl -X POST "$API/disco/book" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "guestId": "crd_10042", "articleId": "art_cola", "articleName": "Cola 0,3l", "count": 2, "price": 3.50 }'
# 5. Consultar el consumo actual
curl "$API/disco/guest/crd_10042" -H "Authorization: Bearer $TOKEN"
# 6. Cargar el pedido de mesa en la tarjeta
curl -X POST "$API/disco/table/book-on-card" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "tableId": "tbl_12", "cardId": "crd_10042" }'
# 7. Antes de la salida: vista previa del checkout
curl "$API/disco/guest/crd_10042/checkout" -H "Authorization: Bearer $TOKEN"
# 8. Salir con pago (si aún queda algo pendiente)
curl -X POST "$API/disco/leave" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "cardId": "crd_10042", "payments": [ { "method": "Cash", "amount": 0 } ] }'
Incidencia — la tarjeta no puede pagar en la salida (impago):
# Anular la tarjeta: marcarla como impagada, contabilizar el consumo como merma
curl -X POST "$API/disco/guest/crd_10042/storno" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "reason": "Karte verloren" }'
# Cobrada posteriormente en caja
curl -X POST "$API/disco/guest/crd_10042/collect-storno" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "payments": [ { "method": "Cash", "amount": 7.00 } ] }'