Categoría: Bots de Discord
Crear un bot de Discord#11¡Crea un panel web con Flask! (ejemplo en Python)
Esta entrada es una traducción del original en coreano.
Índice10 secciones
¡Hola! Soy Mojangcam 😎
En el #10 convertimos nuestro bot en un bot global con soporte multilingüe (i18n), ¿verdad? Había anunciado que lo siguiente serían los cooldowns y la gestión de permisos, pero antes quise traer un tema por el que muchos de ustedes han preguntado. ¡Se trata de crear un panel web (Web Dashboard)! (El episodio de cooldowns también llega pronto, así que no te lo pierdas)
Cuando dejas tu bot funcionando en un servidor, tarde o temprano te preguntas: "¿Seguirá vivo el bot...?" En lugar de abrir Discord cada vez para comprobarlo, ¿no sería mucho más cómodo tener una página en la que solo escribes una dirección en el navegador y ves de un vistazo el estado del bot, el número de servidores y el ping? Hoy vamos a construir esa pequeña torre de control con Flask, un framework web de Python.

🧠 ¿Por qué Flask?
Flask es el micro framework web más conocido de Python. Fiel a su nombre de "micro", con unas pocas líneas de código ya tienes un servidor web funcionando, así que es perfecto para un panel que acompañe a tu bot.
- Comprueba si el bot está encendido directamente desde el navegador (¡incluso desde el móvil!)
- Monitorea en tiempo real el número de servidores, de usuarios y la velocidad de respuesta
- Más adelante puedes ampliarlo todo lo que quieras con estadísticas de comandos, un visor de logs y más
Solo hay un concepto que debes conocer. discord.py es un programa que funciona de forma asíncrona (asyncio), mientras que Flask es un servidor web síncrono. Para ejecutar los dos en un mismo archivo necesitas un truco: ejecutar Flask en un hilo (Thread) aparte. ¡Hoy vamos a aprender a hacerlo paso a paso!
📌 Lo que necesitas
- Python 3.9 o superior (Flask 3.1 requiere 3.9+)
- discord.py 2.x (este artículo usa la 2.7.1)
- Flask 3.1.x (este artículo usa la 3.1.3)
- El token del bot + un servidor de pruebas al que hayas invitado al bot (consulta el #1)
pip install -U discord.py flask📌 Paso 1 - Levanta un servidor Flask por sí solo
Antes de unirlo con el bot, veamos cómo es Flask con un ejemplo mínimo de 5 líneas.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "¡El panel del bot se está preparando!"
app.run(host="0.0.0.0", port=5000)Vamos a verlo parte por parte 💡
Flask(__name__): crea el objeto de la aplicación web.__name__significa "este archivo es el punto de referencia de la aplicación".@app.route("/"): es una marca que indica "ejecuta la función de abajo cuando el navegador entre en la dirección/(la página principal)". Funciona igual que el decorador de los slash commands, ¿verdad?host="0.0.0.0": permite conexiones no solo desde tu computadora, sino también desde otros dispositivos (como tu móvil) de la misma red. Si solo lo vas a ver tú, puedes cambiarlo a127.0.0.1.port=5000: la dirección para conectarte seráhttp://localhost:5000.
Ejecútalo y abre http://localhost:5000 en el navegador para ver el mensaje. Cuando lo hayas comprobado, detenlo con Ctrl+C y pasa al siguiente paso.
📌 Paso 2 - Ejecuta el bot y Flask juntos en un mismo archivo
Esta es la parte clave de hoy. Tanto bot.run() como app.run() son funciones que acaparan todo el programa (son bloqueantes), así que si las escribes una detrás de otra solo se ejecuta una de las dos. La solución es separar Flask en un hilo daemon.
import threading
def run_flask():
app.run(host="0.0.0.0", port=5000, use_reloader=False)
threading.Thread(target=run_flask, daemon=True).start() # Flask en un hilo aparte
bot.run("Pon aquí el token de tu bot") # El bot en el hilo principalVamos a verlo parte por parte 💡
threading.Thread(...): es una línea de trabajo aparte que permite que el servidor Flask funcione al mismo tiempo que el bot.daemon=True: hace que Flask se cierre junto con el programa principal (el bot). Sin esto, Flask sigue funcionando aunque detengas el bot y la terminal no se cierra.use_reloader=False: desactiva el reinicio automático de Flask. Esta función solo funciona en el hilo principal, así que si la dejas activada da error dentro del hilo. (¡Mira el error frecuente n.º 1!)- Cuidado con el orden :
bot.run()tiene que quedarse siempre en el hilo principal por el manejo de señales. Por eso es Flask el que pasamos a un hilo.
📌 Paso 3 - Crea una API de estado del bot + la página del panel
Ahora vamos a crear una API que devuelve la información del bot en JSON y una página HTML que la recibe cada 5 segundos y la muestra.
from flask import jsonify
@app.route("/api/stats")
def stats():
return jsonify({
"online": bot.is_ready(),
"name": str(bot.user) if bot.user else "Conectando...",
"servers": len(bot.guilds),
"members": sum(g.member_count or 0 for g in bot.guilds),
"ping_ms": round(bot.latency * 1000) if bot.is_ready() else None,
})Vamos a verlo parte por parte 💡
jsonify(...): convierte un diccionario de Python en una respuesta JSON que el navegador puede leer.bot.is_ready(): indica si el bot ya inició sesión por completo en Discord. Lo usamos para el indicador de en línea/desconectado del panel.bot.guilds: es la lista de servidores en los que está el bot. Conlen()cuentas cuántos servidores hay.g.member_count: es el número de miembros de cada servidor. Se puede leer con los intents predeterminados (no hace falta el intent de members aparte).bot.latency: es el retraso de respuesta con Discord. Es un número decimal en segundos, así que lo multiplicamos ×1000 para pasarlo a ms. Antes de iniciar sesión no tiene valor (nan), así que primero lo filtramos conis_ready().
La pantalla del panel (HTML) está incluida en el código completo. Es una estructura sencilla que llama a /api/stats con fetch() cada 5 segundos y actualiza los números.
📌 Código completo (puedes copiar y pegar)
# discord.py 2.7.x / Flask 3.1.x / Python 3.9+
import threading
import discord
from discord.ext import commands
from flask import Flask, jsonify, render_template_string
intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)
app = Flask(__name__)
PAGE = """
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Panel del bot</title>
<style>
body { font-family: sans-serif; background: #313338; color: #fff;
display: flex; flex-direction: column; align-items: center; }
h1 { margin-top: 40px; }
.cards { display: flex; gap: 16px; flex-wrap: wrap; justify-content: center; }
.card { background: #2b2d31; border-radius: 12px; padding: 24px 32px;
text-align: center; min-width: 140px; }
.card .num { font-size: 32px; font-weight: bold; color: #5865f2; }
.on { color: #23a55a; }
.off { color: #f23f43; }
</style>
</head>
<body>
<h1>🤖 <span id="name">Cargando...</span></h1>
<p id="status" class="off">● Comprobando</p>
<div class="cards">
<div class="card"><div class="num" id="servers">-</div>Servidores</div>
<div class="card"><div class="num" id="members">-</div>Usuarios</div>
<div class="card"><div class="num" id="ping">-</div>Ping (ms)</div>
</div>
<script>
async function refresh() {
const res = await fetch("/api/stats");
const d = await res.json();
document.getElementById("name").textContent = d.name;
const s = document.getElementById("status");
s.textContent = d.online ? "● En línea" : "● Desconectado";
s.className = d.online ? "on" : "off";
document.getElementById("servers").textContent = d.servers;
document.getElementById("members").textContent = d.members;
document.getElementById("ping").textContent = d.ping_ms ?? "-";
}
refresh();
setInterval(refresh, 5000); // Se actualiza solo cada 5 segundos
</script>
</body>
</html>
"""
@app.route("/")
def dashboard():
return render_template_string(PAGE)
@app.route("/api/stats")
def stats():
return jsonify({
"online": bot.is_ready(),
"name": str(bot.user) if bot.user else "Conectando...",
"servers": len(bot.guilds),
"members": sum(g.member_count or 0 for g in bot.guilds),
"ping_ms": round(bot.latency * 1000) if bot.is_ready() else None,
})
def run_flask():
app.run(host="0.0.0.0", port=5000, use_reloader=False)
@bot.event
async def on_ready():
print(f"¡{bot.user} ha iniciado sesión!")
print("Panel: http://localhost:5000")
threading.Thread(target=run_flask, daemon=True).start()
bot.run("Pon aquí el token de tu bot")📌 Resultado
Al ejecutar el bot, la terminal muestra el mensaje de inicio de sesión y la dirección del panel. Abre http://localhost:5000 en el navegador y...
- Verás el nombre del bot y una luz verde de ● En línea
- El número de servidores / número de usuarios / ping (ms) aparecen en tarjetas y se actualizan solos cada 5 segundos
- Si apagas el bot (cuando solo queda el panel funcionando), cambia a un ● Desconectado en rojo
- ¡Si entras a
http://내컴퓨터IP:5000(la IP de tu computadora) desde un móvil conectado al mismo Wi-Fi, también puedes verlo en el móvil!


📌 Errores frecuentes
- ValueError: signal only works in main thread
Causa: ejecutaste Flask en un hilo con el modo de depuración (reinicio automático) activado o, al revés, enviaste
bot.run()a un hilo. Si cualquiera de los dos no está en el hilo principal, falla al manejar las señales. Solución: incluye siempreapp.run(..., use_reloader=False)y mantén la estructura bot en el hilo principal / Flask en un hilo. - OSError: [Errno 98] Address already in use (Windows: WinError 10048)
Causa: otro programa (o una ejecución anterior que no se cerró del todo) ya está usando el puerto 5000.
Solución: cambia a otro puerto, como
port=5001, o cierra la terminal por completo y vuelve a ejecutarlo. - El panel muestra 0 servidores y 0 usuarios Causa: el bot todavía está iniciando sesión o no lo has invitado a ningún servidor. Solución: espera un momento y recarga la página (también se actualiza sola cada 5 segundos). Si sigue en 0, comprueba que el bot se haya invitado al servidor de pruebas con el enlace de invitación del bot.
📌 Para terminar
Hoy ejecutamos Flask en un hilo aparte para que el bot y el servidor web funcionen al mismo tiempo en un mismo archivo, y creamos un panel web en tiempo real que muestra el estado del bot, el número de servidores y el ping. Si le añades estadísticas de uso de comandos o los logs recientes, ¡puedes convertirlo en una auténtica página de administración!
En el próximo episodio veremos cooldowns (Cooldown) y gestión de permisos para evitar que se abuse de los comandos. ¡No te lo pierdas! 🙌
📚 Serie Crear un bot de Discord
- Crear un bot de Discord - ¡Domina por completo los Slash Commands!
- Crear un bot de Discord #2 - ¡Añade opciones (argumentos) a tus Slash Commands!
- Crear un bot de Discord #3 - ¡Añade subcomandos (Subcommand) a tus Slash Commands!
- Crear un bot de Discord #4 - ¡Envía embeds con Slash Commands!
- Crear un bot de Discord #5 - Implementa interacciones con botones (Button)
- Crear un bot de Discord #6 - Implementa menús desplegables (Select Menu)
- Crear un bot de Discord #7 - Pide texto al usuario con un modal (Modal)
- Crear un bot de Discord #8 - Decora embeds (Embed) con imágenes y campos
- Crear un bot de Discord #9 - Integra la IA (API de Claude y ChatGPT)
- Crear un bot de Discord #10 - Añade soporte multilingüe (i18n)
- Crear un bot de Discord #11 - Crea un panel web con Flask ← ¡estás leyendo este!
Serie · Parte 11 de 32
Crear un bot de Discord
- #1Crear un bot de Discord - ¡Domina por completo los Slash Commands!
- #2¡Añade opciones (argumentos) a tus Slash Commands!
- #3¡Añade subcomandos (Subcommand) a tus Slash Commands!
- #4¡Envía embeds con Slash Commands!
- #5¡Implementa interacciones con botones (Button)!
- #6¡Implementa menús desplegables (Select Menu)!
- #7Pide texto al usuario con un modal (Modal) (ejemplo en Python)
- #8Decora embeds (Embed) con imágenes y campos (ejemplo en Python)
- #9¡Integra la IA (API de Claude y ChatGPT)! (ejemplo en Python)
- #10¡Añade soporte multilingüe (i18n)! (ejemplo en Python)
- #11¡Crea un panel web con Flask! (ejemplo en Python)Estás aquí
- #12¡Gestiona los cooldowns y permisos de tus comandos! (ejemplo en Python)
- #13¡Administra tu servidor automatizando roles y canales! (ejemplo en Python)
- #14¡Guarda tus datos! JSON vs SQLite (ejemplo en Python)
- #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제) (en coreano)
- #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제) (en coreano)
- #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제) (en coreano)
- #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제) (en coreano)
- #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제) (en coreano)
- #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제) (en coreano)
- #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제) (en coreano)
- #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제) (en coreano)
- #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제) (en coreano)
- #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제) (en coreano)
- #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제) (en coreano)
- #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제) (en coreano)
- #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제) (en coreano)
- #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제) (en coreano)
- #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제) (en coreano)
- #30¡Crea un bot de encuestas y votaciones (Poll)! Encuestas nativas + botones con recuento en tiempo real (ejemplo en Python) 🗳️
- #32¡Crea un canal de registros y auditoría (Audit Log) del servidor! Registra automáticamente mensajes borrados y editados, entradas, salidas y expulsiones (ejemplo en Python) 🕵️
- #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉 (en coreano)
