Un bot de Discord puede responder comandos, moderar mensajes, asignar roles, publicar avisos y conectar un servidor con APIs externas. Python es una buena opción porque la biblioteca discord.py ofrece una interfaz asíncrona clara para eventos, comandos y componentes interactivos. El proyecto requiere crear una aplicación en el portal de Discord, proteger el token y diseñar permisos mínimos.
Antes de comenzar, ayuda entender asyncio y las coroutines, cómo guardar secretos en variables de entorno, cómo configurar logging y cómo manejar excepciones.
Crear la aplicación en Discord
Abre el portal oficial para desarrolladores de Discord, crea una aplicación y añade un bot. El token funciona como una contraseña: quien lo posee puede controlar el bot. No lo publiques en GitHub, capturas de pantalla ni archivos compartidos. Si se filtra, regénéralo inmediatamente.
Instalar discord.py
python -m venv .venv
.venv\Scripts\activate
python -m pip install -U discord.py python-dotenvEn macOS o Linux activa con source .venv/bin/activate. La documentación oficial de discord.py detalla eventos, comandos, intents, vistas y tareas en segundo plano.
Guardar el token
Crea un archivo .env durante el desarrollo:
DISCORD_TOKEN=tu_token_aquiAñádelo a .gitignore:
.env
.venv/
__pycache__/Carga la variable:
import os
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
if not TOKEN:
raise RuntimeError("Falta DISCORD_TOKEN")En producción utiliza el sistema de secretos del proveedor de alojamiento.
Configurar intents
Los intents indican qué eventos recibirá el bot:
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)El intent de contenido de mensajes puede ser privilegiado y debe habilitarse también en el portal. Solicita únicamente los intents necesarios. Para comandos slash, muchas funciones no necesitan leer todos los mensajes.
Primer evento
@bot.event
async def on_ready():
print(f"Conectado como {bot.user}")El evento puede ejecutarse más de una vez después de reconexiones. No crees tareas duplicadas dentro de on_ready sin comprobar su estado.
Crear comandos tradicionales
@bot.command()
async def hola(ctx):
await ctx.send(f"Hola, {ctx.author.mention}")El decorador registra el comando !hola. Como la operación de red es asíncrona, debes utilizar await.
Comando con argumentos
@bot.command()
async def sumar(ctx, a: float, b: float):
await ctx.send(f"Resultado: {a + b}")discord.py convierte los argumentos según las anotaciones. Si el usuario escribe valores inválidos, se genera un error de conversión que puedes manejar de forma centralizada.
Manejar errores de comandos
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
return
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send("Faltan argumentos para ejecutar el comando.")
return
if isinstance(error, commands.BadArgument):
await ctx.send("Uno de los valores no tiene el formato esperado.")
return
await ctx.send("Ocurrió un error inesperado.")
raise errorNo muestres tokens, rutas internas ni tracebacks completos en el canal. Registra los detalles en el servidor.
Comandos slash
Los comandos de aplicación ofrecen autocompletado y descripciones:
@bot.tree.command(name="saludar", description="Envía un saludo")
async def saludar(interaction: discord.Interaction):
await interaction.response.send_message(
f"Hola, {interaction.user.mention}!"
)Sincroniza los comandos:
@bot.event
async def setup_hook():
await bot.tree.sync()Durante el desarrollo puedes sincronizar en un servidor específico para que los cambios aparezcan más rápido.
Enviar embeds
@bot.command()
async def info(ctx):
embed = discord.Embed(
title="Información del servidor",
description="Comandos y enlaces útiles",
)
embed.add_field(name="Miembros", value=ctx.guild.member_count)
await ctx.send(embed=embed)Valida cualquier texto recibido de usuarios antes de colocarlo en enlaces o menciones.
Comprobar permisos
@bot.command()
@commands.has_permissions(manage_messages=True)
async def limpiar(ctx, cantidad: int = 10):
cantidad = max(1, min(cantidad, 100))
eliminados = await ctx.channel.purge(limit=cantidad + 1)
await ctx.send(
f"Se eliminaron {len(eliminados) - 1} mensajes.",
delete_after=5
)Los permisos se comprueban en Discord y en el código. El bot también necesita autorización para eliminar mensajes en ese canal.
Evitar abusos con cooldowns
@bot.command()
@commands.cooldown(1, 10, commands.BucketType.user)
async def dado(ctx):
import random
await ctx.send(str(random.randint(1, 6)))Los límites por usuario impiden spam y reducen uso de APIs externas. Para tareas costosas, añade cuotas persistentes.
Tareas periódicas
from discord.ext import tasks
@tasks.loop(minutes=30)
async def publicar_estado():
canal = bot.get_channel(123456789012345678)
if canal:
await canal.send("El servicio continúa activo.")
@publicar_estado.before_loop
async def esperar_bot():
await bot.wait_until_ready()Inicia la tarea una sola vez, por ejemplo en setup_hook. Maneja errores para que una excepción no detenga permanentemente el bucle.
Organizar el proyecto con Cogs
bot_discord/
├── bot.py
├── cogs/
│ ├── moderacion.py
│ └── utilidades.py
├── .env
├── .gitignore
└── requirements.txtUn Cog agrupa comandos y eventos relacionados. Esto evita que un archivo crezca indefinidamente y facilita probar cada módulo.
Ejemplo de Cog
from discord.ext import commands
class Utilidades(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command()
async def ping(self, ctx):
await ctx.send(f"{round(self.bot.latency * 1000)} ms")
async def setup(bot):
await bot.add_cog(Utilidades(bot))Carga la extensión durante la inicialización:
await bot.load_extension("cogs.utilidades")Usar una base de datos
Para guardar configuraciones, advertencias o puntuaciones, SQLite puede ser suficiente en un único proceso. Para varios procesos o servidores, utiliza una base de datos compartida. No realices consultas bloqueantes largas dentro del bucle asíncrono; utiliza una biblioteca asíncrona o ejecuta trabajo bloqueante en otro hilo.
Logging
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
handlers=[logging.FileHandler("bot.log", encoding="utf-8")]
)Registra inicios, reconexiones, errores y acciones administrativas. Evita almacenar mensajes privados completos o datos personales innecesarios.
Ejecutar el bot
bot.run(TOKEN, log_handler=None)Para despliegue continuo, utiliza un servicio que reinicie el proceso tras un fallo. Configura señales de apagado para cerrar conexiones y guardar datos pendientes.
Seguridad
Concede al bot el mínimo de permisos. Evita “Administrator” salvo que sea estrictamente necesario. Valida identificadores de canales y servidores, limita comandos destructivos y exige roles. Nunca ejecutes como código Python el texto enviado por usuarios. También debes respetar las reglas de Discord y la privacidad de los miembros.
Errores frecuentes
Los problemas más comunes son token inválido, intent no habilitado, permisos insuficientes, olvidar await, bloquear el event loop con time.sleep, sincronizar comandos en cada reconexión y publicar el archivo .env. Usa asyncio.sleep dentro de funciones asíncronas y revisa los logs.
Conclusión
Un bot de Discord confiable necesita más que responder un comando. Debe proteger el token, solicitar intents mínimos, controlar permisos, limitar abusos y registrar errores. discord.py permite comenzar con comandos simples y evolucionar hacia slash commands, Cogs, tareas programadas y bases de datos. Construye cada función por separado, prueba en un servidor privado y despliega únicamente cuando los permisos y el manejo de errores estén claros.






