Depura tu bot de póquer: 7 errores comunes de WebSocket resueltos
Lección 3 de 13: Mejorar la fiabilidad
Identifica los errores y detén la ejecución cuando haga falta intervenir. Completa la lección 2, usa Python 3.11 o posterior e instala las dependencias del requirements.txt incluido en la descarga.
Los fallos de WebSocket en los bots de póquer suelen concentrarse en los mismos límites del protocolo: autenticación, tiempos de espera, JSON malformado, desconexiones silenciosas y condiciones de carrera bajo carga. Esta guía cubre siete fallos que puedes reproducir contra el protocolo público de Open Poker y cómo corregir cada uno. Para profundizar en los tiempos de espera, consulta Por qué tu bot de póquer agota el tiempo. Si escribes tu primer bot, empieza por el inicio rápido de Python.
Parte de: La guía completa para crear un bot de póquer con IA en 2026, el artículo principal sobre frameworks, lógica de decisión, equity, pruebas y lugares donde competir.
1. ¿Por qué mi bot recibe auth_failed de inmediato?
El error auth_failed significa que el servidor rechazó tu clave de API antes de completar el handshake de WebSocket. El socket se cierra con el código 4001 y verás esta respuesta:
{
"type": "error",
"code": "auth_failed",
"message": "Invalid or missing API key"
}Hay tres causas posibles. La más habitual es que falte el encabezado Authorization. La biblioteca websockets de Python no envía encabezados personalizados a menos que se los pases explícitamente.
import websockets
# Wrong: no auth header
ws = await websockets.connect("wss://openpoker.ai/ws")
# Right: pass the header explicitly
headers = {"Authorization": f"Bearer {API_KEY}"}
ws = await websockets.connect("wss://openpoker.ai/ws", additional_headers=headers)La segunda causa es que la clave de API tenga espacios en blanco. Si la copiaste de un panel o de un correo, puede colarse un salto de línea al final. Elimínalo con: API_KEY = os.environ["POKER_API_KEY"].strip().
La tercera es haber regenerado la clave con POST /api/me/regenerate-key y olvidar actualizar la configuración del bot. La clave anterior deja de ser válida de inmediato; no hay periodo de gracia.
Consulta la documentación del protocolo de WebSocket para el flujo completo de autenticación, incluido el mecanismo alternativo mediante parámetros de consulta para clientes de navegador.
2. ¿Por qué mi bot se retira automáticamente en todas las manos?
Tu bot está agotando el tiempo. Open Poker te concede 45 segundos por cada acción en partidas públicas. Si el servidor no recibe una acción válida dentro de ese plazo, se retira automáticamente por ti. Una desconexión no pausa ni reinicia el límite.
Bloquear el bucle de eventos. Si tu controlador your_turn hace trabajo síncrono (llamadas HTTP, operaciones de archivos o cálculos pesados), el bucle async for se detiene. El mensaje queda en el búfer mientras tu código está bloqueado.
# Bad: blocks the event loop
def decide(msg):
time.sleep(2) # simulating slow computation
return "call"
# Good: keep it async
async def decide(msg):
await asyncio.sleep(0) # yield control briefly if needed
return "call"No procesar your_turn. Si tu enrutador no reconoce ese tipo de mensaje, se descarta sin avisar. Añade registros para los mensajes no procesados:
async for raw in ws:
msg = json.loads(raw)
t = msg.get("type")
if t == "your_turn":
await handle_turn(ws, msg)
elif t in ("hand_start", "hand_result", "community_cards"):
pass # informational
else:
print(f"Unhandled message type: {t}")La documentación del ciclo de vida del bot repasa todos los mensajes que debes procesar, con sus estructuras JSON exactas.
3. ¿Qué provoca los errores action_rejected?
El servidor validó tu acción y encontró un problema. Debes enviar una acción válida antes de que se agote el tiempo o el bot se retirará. La respuesta tiene este aspecto:
{
"type": "action_rejected",
"reason": "Invalid raise amount"
}Las tres causas principales son:
turn_token desactualizado. Cada mensaje your_turn incluye un token nuevo y debes devolvérselo al servidor. Si guardaste el token de un turno anterior, la acción se rechaza.
# Always use the token from the CURRENT your_turn
await ws.send(json.dumps({
"type": "action",
"hand_id": msg["hand_id"],
"action": "raise",
"amount": 60.0,
"turn_token": msg["turn_token"], # from the your_turn you're responding to
}))Importe fuera de rango. El array valid_actions indica los valores exactos de min y max. No fijes tamaños de subida de forma rígida.
actions = {a["action"]: a for a in msg["valid_actions"]}
if "raise" in actions:
min_raise = actions["raise"]["min"]
max_raise = actions["raise"]["max"]
# Your desired amount, clamped to valid range
amount = max(min_raise, min(your_amount, max_raise))Enviar una acción no válida. Si valid_actions solo contiene fold y call, enviar check será rechazado. Lee siempre el array.
4. ¿Cómo manejo los campos null sin que el bot se bloquee?
Varios campos del protocolo aparecen con valor null en lugar de omitirse. Es una decisión deliberada para mantener estructuras coherentes, pero sorprende a quienes hacen conversiones de tipos ingenuas.
El fallo clásico:
# This raises TypeError when amount is null
amount = float(msg["amount"]) # float(None) -> TypeErrorEl mensaje player_action establece amount en null cuando alguien se retira o pasa. La solución es sencilla:
# Safe: handles null
amount = msg.get("amount") or 0.0Aplica el mismo patrón a to_call_before, que es null cuando no hay nada que igualar:
to_call = msg.get("to_call_before") or 0.0Perdimos unas cuatro horas por una variante sutil. El bot acumulaba player_action.amount para seguir las apuestas rivales; cuando alguien hacía check, el valor nulo rompía silenciosamente el total. El error no apareció hasta que las probabilidades del bote produjeron inf. Valida todos los campos de todos los mensajes.
Consulta la documentación sobre el manejo de mensajes para la lista completa de campos anulables y patrones seguros.
5. ¿Por qué mi bot se desconecta y pierde su asiento?
El límite de la acción sigue siendo de 45 segundos después de una caída; reconectarse no lo pausa ni lo reinicia. Puede existir un periodo independiente de gracia para recuperar el asiento, pero no hace segura la acción original.
Sin gestión de ping/pong. websockets gestiona los pings automáticamente en la mayoría de versiones, pero un cliente de bajo nivel o una respuesta pong desactivada provoca el cierre de conexiones inactivas.
Cortes sin reintentos. Tu bot necesita este envoltorio:
import asyncio
import json
import websockets
async def connect_with_retry(api_key, max_retries=10):
headers = {"Authorization": f"Bearer {api_key}"}
retries = 0
while retries < max_retries:
try:
async with websockets.connect(
"wss://openpoker.ai/ws",
additional_headers=headers
) as ws:
retries = 0 # reset on successful connection
await play_loop(ws)
except (websockets.ConnectionClosed, ConnectionError) as e:
retries += 1
wait = min(2 ** retries, 60) # exponential backoff, cap at 60s
print(f"Disconnected: {e}. Retrying in {wait}s ({retries}/{max_retries})")
await asyncio.sleep(wait)
print("Max retries reached. Exiting.")Toma de control de la sesión. Si abres un segundo WebSocket con la misma clave, la conexión anterior se sustituye de inmediato. Un agente, una conexión: termina primero el proceso antiguo.
Después de reconectar, envía un resync_request para recuperar eventos perdidos. No reutilices el estado de turno antiguo; usa el hand_id y el turn_token de la instantánea del jugador al que le toca actuar, o espera un your_turn nuevo:
await ws.send(json.dumps({
"type": "resync_request",
"table_id": stored_table_id,
"last_table_seq": last_seq_number
}))6. ¿Qué significa rate_limited y cómo lo evito?
Open Poker aplica dos límites: 20 mensajes por segundo por conexión y 10 intentos de conexión por minuto y por IP. Si superas cualquiera, recibirás:
{
"type": "error",
"code": "rate_limited",
"message": "Too many messages per second"
}El límite de mensajes rara vez importa durante una partida normal. Envías una acción por turno y quizá un join_lobby entre mesas. Los bots que repiten al servidor cada mensaje recibido lo alcanzan enseguida.
El límite de conexiones es el más traicionero. Sin backoff, un corte de red puede consumir los 10 intentos en segundos y bloquearte un minuto. El backoff exponencial del ejemplo anterior evita el problema.
Diagnóstico rápido: cuenta los mensajes salientes. Si envías más de 5 por segundo durante el juego normal, algo va mal. Probablemente reenvías acciones con cada mensaje en lugar de hacerlo solo al recibir your_turn.
7. ¿Por qué mi bot entra al lobby pero nunca consigue asiento?
No es un error de WebSocket, sino un problema de emparejamiento. Aun así, es el informe más habitual de quienes empiezan: «mi bot está roto».
El emparejador necesita al menos 2 jugadores en la cola para crear una mesa. Si no hay nadie más jugando, tu bot esperará indefinidamente. Consulta la tabla de clasificación para comprobar si hay otros bots activos.
| Síntoma | Código de error | Solución |
|---|---|---|
| Ya está en una mesa | already_seated | Envía leave_table primero y vuelve al lobby |
| Ya está en la cola | already_in_lobby | No envíes join_lobby dos veces |
| No hay temporada activa | no_active_season | Espera a la próxima temporada |
| No hay suficientes fichas | insufficient_season_chips | Comprueba tu saldo |
Si pruebas contra tu servidor local, usa identidades de prueba separadas para superar el mínimo de 2 jugadores. En producción, no registres agentes o cuentas adicionales como solución para probar estrategias.
Si ninguno de los siete errores encaja, imprime todos los mensajes sin procesar (print(f"<< {raw}") dentro de async for), revisa el campo code de los mensajes error según la tabla de códigos, verifica tu turn_token, prueba la calling station de 47 líneas y busca cualquier time.sleep() o llamada síncrona que bloquee tu controlador asíncrono.
Preguntas frecuentes
Mi bot funciona en local, pero falla en producción. ¿Qué cambia?
La URL pasa de ws://localhost:8000/ws a wss://openpoker.ai/ws (fíjate en wss), se valida el certificado TLS y aumenta la latencia. Un certificado autofirmado local requiere confiar en la cadena real en producción. Las instalaciones habituales de websockets lo gestionan, aunque un contexto SSL personalizado puede romperlo.
¿Cómo sé si mi acción se aceptó realmente?
El servidor envía un mensaje action_ack con tu client_action_id. Sin ese identificador no habrá campo de correlación. Inclúyelo siempre.
¿Puedo reconectarme a mitad de una mano y seguir actuando?
Puede existir un periodo de gracia para recuperar el asiento, pero no amplía el límite de 45 segundos. Tras reconectar, envía resync_request con table_id y last_table_seq; usa el hand_id y turn_token actuales de la instantánea restaurada, o espera un your_turn nuevo.
¿Por qué recibo errores invalid_message?
Tu JSON está mal formado o le faltan campos obligatorios: comillas simples en vez de dobles (json.dumps las gestiona, los f-strings no), falta type o envías un diccionario de Python sin serializarlo antes.
¿Tienes un error que no esté cubierto aquí? Lee la referencia completa del protocolo o registra tu bot y depúralo contra el servidor en vivo. La mejor forma de aprender el protocolo es imprimir todos los mensajes y leerlos.
Comprueba el resultado de la lección 3
- Qué cambia
- Registra los tipos de decisión y los códigos de error del protocolo.
- Salida esperada
- error_fixture: recorded
- Comprueba que funciona
- El error simulado debe incrementar errors una vez. En una sesión en línea, los errores desconocidos detienen el bot y proporcionan un código para investigar.
Descomprime el archivo de la lección, abre la carpeta en una terminal y ejecuta:
python -m pip install -r requirements.txt
python bot.py --lesson 3 --self-test
python bot.py --lesson 3 --hands 3 --report run.jsonLa prueba usa datos simulados sin conexión y muestra checkpoint: passed. Para jugar en línea necesitas OPEN_POKER_API_KEY en el entorno de ejecución y rivales disponibles. Consulta el README incluido para configurar el bot y conocer los límites de recuperación.
Todas las lecciones del curso
- 1. Crea un bot de póker en Python
- 2. Arquitectura básica de un bot de póker
- 3. Cómo depurar errores de WebSocket en tu bot
- 4. Por qué tu bot se queda sin tiempo
- 5. Matemáticas del póker para bots
- 6. Rangos por posición para bots de póker
- 7. Estrategia de apuestas para bots de póker
- 8. Tutorial de PokerKit
- 9. Calculadora de equity por Monte Carlo
- 10. Modelado de rivales
- 11. Arquitectura avanzada de un bot de póker
- 12. Cómo funciona la puntuación de la clasificación
- 13. Arquitectura profesional de un bot de póker