Cómo hacer un bot de tickets para Discord en Python (discord.py)
Un bot de tickets pone un botón en un canal: quien lo pulsa recibe un canal privado que solo ven él y tu equipo, y se borra al cerrarlo. Es lo que usan casi todos los servidores de Discord con comunidad para el soporte, las denuncias o las solicitudes de staff. Aquí lo haces tú en Python, en un solo fichero, sin anuncios ni funciones de pago.
Qué hace este bot
Un administrador escribe /panel-tickets en el canal que quiera y el bot publica un mensaje con el botón «Abrir ticket». Al pulsarlo, el bot crea un canal ticket-nombre dentro de la categoría Tickets, que solo ven esa persona, el rol de tu equipo y el propio bot. Dentro hay un botón «Cerrar ticket» que borra el canal a los 5 segundos.
Cada persona solo puede tener un ticket abierto: si vuelve a pulsar, el bot le enseña el que ya tiene. Y los botones siguen funcionando aunque el bot se reinicie, que es justo lo que falla en la mayoría de los ejemplos que circulan.
¿Prefieres no montarlo tú? Hosting de bots de Discord desde 1,50 € al mes, sin permanencia y con devolución íntegra en 72 horas. O crea un bot de Discord gratis, sin tarjeta.
1. Prepara el bot y tu servidor de Discord
Crea la aplicación y saca el token
En discord.com/developers/applications, «New Application»; en la pestaña «Bot», «Reset Token» y cópialo. Si es tu primer bot, la guía del bot de Discord en Python lo explica con más calma.
Invítalo con los permisos justos
En «OAuth2» → «URL Generator» marca «bot» y «applications.commands» y, en permisos, «Manage Channels», «View Channels», «Send Messages», «Embed Links», «Attach Files» y «Read Message History». Sin «Manage Channels» no puede crear ni borrar los canales de los tickets.
Crea el rol del equipo
En tu servidor de Discord, crea un rol llamado Soporte (o cambia el nombre en el código) y dáselo a quien vaya a atender los tickets. Los administradores ven todos los canales de todas formas. Para que al rol le llegue el aviso de cada ticket nuevo, activa en sus ajustes «Permitir que cualquiera @mencione este rol».
2. El código
Tres ficheros en una carpeta: requirements.txt con las librerías, .env con el token (sin comillas) y app.py con el bot. Arriba de app.py se cambian el nombre de la categoría y el del rol.
discord.py
python-dotenvDISCORD_TOKEN=pega-aquí-tu-tokenimport asyncio
import os
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv() # lee el token del fichero .env
CATEGORIA = "Tickets" # la categoría donde se abren (se crea sola)
ROL_EQUIPO = "Soporte" # el rol que atiende los tickets: créalo en tu servidor
class Cerrar(discord.ui.View):
def __init__(self):
super().__init__(timeout=None) # sin caducidad: sigue valiendo tras reiniciar el bot
@discord.ui.button(label="Cerrar ticket", emoji="🔒", style=discord.ButtonStyle.danger, custom_id="ticket:cerrar")
async def cerrar(self, interaction: discord.Interaction, boton: discord.ui.Button):
await interaction.response.send_message("El ticket se cierra en 5 segundos.")
await asyncio.sleep(5)
await interaction.channel.delete(reason=f"Ticket cerrado por {interaction.user}")
class Abrir(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(label="Abrir ticket", emoji="🎫", style=discord.ButtonStyle.primary, custom_id="ticket:abrir")
async def abrir(self, interaction: discord.Interaction, boton: discord.ui.Button):
await interaction.response.defer(ephemeral=True, thinking=True)
guild = interaction.guild
categoria = discord.utils.get(guild.categories, name=CATEGORIA) or await guild.create_category(CATEGORIA)
# Uno por persona: el tema del canal guarda el id de quien lo abrió.
for canal in categoria.text_channels:
if canal.topic == str(interaction.user.id):
await interaction.followup.send(f"Ya tienes uno abierto: {canal.mention}", ephemeral=True)
return
permisos = {
guild.default_role: discord.PermissionOverwrite(view_channel=False),
interaction.user: discord.PermissionOverwrite(view_channel=True, send_messages=True, attach_files=True),
guild.me: discord.PermissionOverwrite(view_channel=True, send_messages=True, manage_channels=True),
}
equipo = discord.utils.get(guild.roles, name=ROL_EQUIPO)
if equipo:
permisos[equipo] = discord.PermissionOverwrite(view_channel=True, send_messages=True, attach_files=True)
canal = await guild.create_text_channel(
f"ticket-{interaction.user.name}", category=categoria, overwrites=permisos, topic=str(interaction.user.id)
)
aviso = equipo.mention if equipo else ""
await canal.send(f"{interaction.user.mention} {aviso} Cuéntanos qué pasa. Cuando esté resuelto, pulsa el botón.", view=Cerrar())
await interaction.followup.send(f"Ticket abierto: {canal.mention}", ephemeral=True)
class Bot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
self.add_view(Abrir()) # vuelve a escuchar los botones ya publicados
self.add_view(Cerrar())
await self.tree.sync() # publica /panel-tickets
bot = Bot()
@bot.tree.command(name="panel-tickets", description="Publica el botón para abrir tickets en este canal")
@app_commands.default_permissions(manage_guild=True)
@app_commands.guild_only()
async def panel_tickets(interaction: discord.Interaction):
embed = discord.Embed(
title="¿Necesitas ayuda?",
description="Pulsa el botón y se abrirá un canal privado entre tú y el equipo.",
color=0x5B4AE8,
)
await interaction.channel.send(embed=embed, view=Abrir())
await interaction.response.send_message("Listo: el panel de tickets está publicado.", ephemeral=True)
@bot.event
async def on_ready():
print(f"Conectado como {bot.user}")
bot.run(os.environ["DISCORD_TOKEN"])Por qué los botones siguen funcionando tras reiniciar
Un botón de Discord no guarda nada: cuando alguien lo pulsa, Discord avisa al bot con el identificador del botón (custom_id) y el bot decide qué hacer. Si el bot se reinicia y no sabe qué hacer con ese identificador, Discord enseña que la interacción ha fallado.
Por eso las dos vistas llevan timeout=None (no caducan a los 3 minutos, que es lo que hacen por defecto), cada botón tiene un custom_id fijo y setup_hook las registra con add_view al arrancar. Con eso, el mensaje con «Abrir ticket» que publicaste hace un mes sigue funcionando después de cada actualización del bot.
El bot tampoco guarda una lista de tickets abiertos: lo que lo une a cada persona es el tema del canal, donde escribe el id de quien lo abrió. Así no necesita base de datos y nada se pierde al reiniciar.
3. Pruébalo
Con Python 3.10 o más nuevo, en la carpeta: «pip install -r requirements.txt» y «python app.py». Cuando diga «Conectado como…», ve al canal donde quieras el botón (por ejemplo, #soporte) y escribe /panel-tickets. El comando solo lo ven quienes pueden gestionar el servidor; la primera vez puede tardar unos minutos en aparecer.
Pulsa «Abrir ticket» con una cuenta sin rol: debe salir un canal nuevo en la categoría Tickets que esa cuenta ve y el resto no. Si da un error de permisos (Missing Permissions en la consola), al bot le falta «Manage Channels» o alguno de los permisos que se da a sí mismo en el canal.
Para ampliarlo
Varios tipos de ticket: en vez de un botón, pon varios (Soporte, Denuncias, Solicitud de staff) con su propio custom_id, o un menú desplegable (discord.ui.Select), y usa una categoría o un rol distinto para cada uno.
Guardar la conversación antes de borrar: recorre los mensajes con canal.history(oldest_first=True), escríbelos en un .txt y mándalo a un canal de registros con discord.File. Para leer el texto de los mensajes, el bot necesita la intención «Message Content»: actívala en la pestaña «Bot» del portal y añade intents.message_content = True en el código.
Que solo cierre el equipo: en cerrar(), comprueba si interaction.user tiene el rol antes de borrar el canal y, si no, contéstale con un mensaje que solo vea él (ephemeral=True).
4. Déjalo encendido siempre (gratis)
Un bot de tickets tiene que estar conectado cuando alguien pulsa el botón: si lo tienes en tu ordenador y lo apagas, nadie puede abrir tickets. Para tenerlo siempre encendido hay que alojarlo en un servidor.
En NapNodes: crea el bot gratis, pásalo a Python en «Configuración» → «Lenguaje» (nace en Node.js), sube los tres ficheros en «Archivos» (o pon tu repositorio de GitHub, sin el .env) y pulsa «Encender». Las librerías de requirements.txt se instalan solas al arrancar y app.py es el fichero de inicio que viene puesto.
El bot gratis tiene 256 MB, de sobra para este bot aunque tu servidor de Discord sea grande, y solo hay que renovarlo con un clic cada semana. No necesita abrir ningún puerto: es el bot el que se conecta a Discord.
Preguntas frecuentes
¿Cómo hago un sistema de tickets en Discord?
Con un bot que publique un botón y, al pulsarlo, cree un canal privado entre esa persona y tu equipo. Con discord.py son unas 90 líneas en un solo fichero, y lo puedes tener encendido gratis.
¿Por qué el botón de mi bot dice que la interacción ha fallado?
Porque el bot está apagado o, tras reiniciarse, no sabe qué hacer con ese botón. Las vistas tienen que llevar timeout=None, un custom_id fijo en cada botón y registrarse con add_view al arrancar.
¿Qué permisos necesita un bot de tickets?
«Manage Channels» para crear y borrar los canales, y «View Channels», «Send Messages», «Embed Links», «Attach Files» y «Read Message History» para escribir en ellos.
¿Puedo guardar la conversación del ticket antes de cerrarlo?
Sí: recorre canal.history(), escríbela en un .txt y mándala a un canal de registros. Para leer el texto de los mensajes, activa la intención «Message Content» en el portal de desarrolladores.
¿Te ha servido esta guía?
¿Lo montamos?
Hosting de bots de Discord desde 1,50 € al mes, sin sobreventa: tu RAM es solo tuya. Y si quieres empezar sin pagar, tienes un bot de Discord gratis, sin tarjeta.
Sigue leyendo
- Bot de verificación para Discord en Python: un botón y una pregunta antes de entrar
Haz tu bot de verificación de Discord con discord.py: botón «Verificarme», una pregunta contra bots y un rol que abre el servidor. Código completo y gratis.
- Bot de sorteos para Discord en Python: botón para participar, ganadores al azar y repetir
Haz tu bot de sorteos de Discord en Python: /sorteo con botón para participar, cuenta atrás, ganadores al azar y /resortear. Código completo, sin anuncios.
- Bot de bienvenida para Discord: mensaje al entrar y rol automático (Python y JavaScript)
Haz un bot de Discord que salude a quien entra y le dé un rol solo, en Python o JavaScript: el permiso que siempre falta, la jerarquía de roles y las normas.