NapNodes

← Guías de Bots de Discord

Bots de Discord · actualizada el 22 de octubre de 2026

Comandos con barra en tu bot de Discord: opciones, subcomandos y permisos

Un /hola que contesta es el primer paso; un bot de verdad necesita comandos que piden datos (/aviso texto: …), que se agrupan (/musica poner, /musica parar), que solo usan los moderadores y que no fallan cuando tardan. Aquí va cada pieza, en discord.js y en discord.py, con el mismo ejemplo en los dos.

¿Se lo pasas a tu grupo?WhatsApp

Antes de empezar

Partimos de un bot que ya arranca y responde a /hola: si aún no lo tienes, es un cuarto de hora con la guía de discord.py o la de discord.js. Los ejemplos de abajo usan discord.js 14 (Node.js 20 o más nuevo) y discord.py 2 (Python 3.10 o más nuevo).

Cada comando tiene un nombre de 1 a 32 caracteres, en minúsculas y sin espacios (sí con guiones), y una descripción de 1 a 100. Un bot puede tener hasta 100 comandos con barra globales y cada comando hasta 25 opciones; si te faltan, agrúpalos en subcomandos.

¿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.

Opciones: que el comando pida datos

Una opción es un hueco que Discord rellena con el tipo correcto: texto, número entero, número con decimales, sí/no, un usuario, un canal o un rol. Discord no deja enviar el comando sin las obligatorias ni con un número fuera del rango que marques, así que tu código no tiene que comprobarlo. Con las elecciones (choices, hasta 25), el usuario elige de una lista en vez de escribir.

El ejemplo: /dado, con el número de caras (de 2 a 100, 6 si no pone nada), y /aviso, con un texto obligatorio y un color a elegir.

index.js (discord.js)
const { SlashCommandBuilder } = require("discord.js");

const comandos = [
  new SlashCommandBuilder()
    .setName("dado")
    .setDescription("Tira un dado")
    .addIntegerOption((o) =>
      o.setName("caras").setDescription("Número de caras").setMinValue(2).setMaxValue(100)),
  new SlashCommandBuilder()
    .setName("aviso")
    .setDescription("Publica un aviso")
    .addStringOption((o) => o.setName("texto").setDescription("Lo que dice").setRequired(true))
    .addStringOption((o) =>
      o.setName("color").setDescription("Color del aviso").addChoices(
        { name: "Rojo", value: "rojo" },
        { name: "Verde", value: "verde" },
      )),
];

// En el InteractionCreate:
if (i.commandName === "dado") {
  const caras = i.options.getInteger("caras") ?? 6;
  await i.reply(`🎲 Ha salido un ${1 + Math.floor(Math.random() * caras)}`);
}
if (i.commandName === "aviso") {
  const texto = i.options.getString("texto", true);
  const color = i.options.getString("color") ?? "verde";
  await i.reply(`${color === "rojo" ? "🔴" : "🟢"} ${texto}`);
}
app.py (discord.py)
import random

import discord
from discord import app_commands


@tree.command(name="dado", description="Tira un dado")
@app_commands.describe(caras="Número de caras")
async def dado(interaction: discord.Interaction, caras: app_commands.Range[int, 2, 100] = 6):
    await interaction.response.send_message(f"🎲 Ha salido un {random.randint(1, caras)}")


@tree.command(name="aviso", description="Publica un aviso")
@app_commands.describe(texto="Lo que dice", color="Color del aviso")
@app_commands.choices(color=[
    app_commands.Choice(name="Rojo", value="rojo"),
    app_commands.Choice(name="Verde", value="verde"),
])
async def aviso(interaction: discord.Interaction, texto: str, color: str = "verde"):
    emoji = "🔴" if color == "rojo" else "🟢"
    await interaction.response.send_message(f"{emoji} {texto}")

Subcomandos: /musica poner, /musica parar

Cuando varios comandos van de lo mismo, en vez de /poner-musica y /parar-musica se hace un comando con subcomandos: en Discord salen juntos al escribir /musica, y cada subcomando tiene sus propias opciones. Un comando con subcomandos ya no se puede usar solo: /musica a secas no existe.

index.js (discord.js)
new SlashCommandBuilder()
  .setName("musica")
  .setDescription("Controla la música")
  .addSubcommand((s) =>
    s.setName("poner").setDescription("Pone una canción")
      .addStringOption((o) => o.setName("cancion").setDescription("Nombre o enlace").setRequired(true)))
  .addSubcommand((s) => s.setName("parar").setDescription("Para la música"));

// En el InteractionCreate:
if (i.commandName === "musica") {
  const sub = i.options.getSubcommand(); // "poner" o "parar"
}
app.py (discord.py)
musica = app_commands.Group(name="musica", description="Controla la música")


@musica.command(name="poner", description="Pone una canción")
async def poner(interaction: discord.Interaction, cancion: str):
    await interaction.response.send_message(f"Poniendo {cancion}")


@musica.command(name="parar", description="Para la música")
async def parar(interaction: discord.Interaction):
    await interaction.response.send_message("Música parada")


tree.add_command(musica)

Comandos solo para moderadores

Lo seguro es decirle a Discord qué permiso hace falta para ver el comando: a quien no lo tiene, ni le aparece. En el ejemplo, /aviso solo para quien puede gestionar mensajes. Los administradores de cada servidor pueden cambiarlo después en «Ajustes del servidor» → «Integraciones» → tu bot, sin tocar tu código (por ejemplo, dárselo a un rol concreto).

Ese permiso decide quién ve el comando, no lo que puede hacer tu bot: si el comando banea, el bot tiene que tener «Banear miembros» (se marca al invitarlo, en «OAuth2» → «URL Generator») y su rol estar por encima del de la persona.

index.js (discord.js)
const { PermissionFlagsBits } = require("discord.js");

new SlashCommandBuilder()
  .setName("aviso")
  .setDescription("Publica un aviso")
  .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages);
app.py (discord.py)
@tree.command(name="aviso", description="Publica un aviso")
@app_commands.default_permissions(manage_messages=True)
async def aviso(interaction: discord.Interaction, texto: str):
    ...

Respuestas que tardan, y respuestas que solo ve quien pregunta

Discord espera la primera respuesta en 3 segundos. Si tu comando consulta una web, una base de datos o un servidor de juego, puede pasarse, y el usuario ve «La aplicación no respondió» aunque tu bot acabe bien. La solución es avisar primero de que estás en ello (sale «… está pensando») y contestar después: desde ese aviso tienes 15 minutos.

Para los mensajes que solo interesan a quien escribe el comando (un error, una confirmación, datos de su cuenta), usa una respuesta efímera: solo la ve él, con «Solo tú puedes ver esto», y no ensucia el canal.

index.js (discord.js)
const { MessageFlags } = require("discord.js");

await i.deferReply(); // «… está pensando»
const datos = await algoQueTarda();
await i.editReply(`Listo: ${datos}`);

// Solo la ve quien escribió el comando:
await i.reply({ content: "No tienes ningún aviso pendiente.", flags: MessageFlags.Ephemeral });
app.py (discord.py)
await interaction.response.defer()  # «… está pensando»
datos = await algo_que_tarda()
await interaction.followup.send(f"Listo: {datos}")

# Solo la ve quien escribió el comando:
await interaction.response.send_message("No tienes ningún aviso pendiente.", ephemeral=True)

Autocompletado: sugerencias mientras escribe

Las elecciones sirven para listas cortas y fijas. Si la lista es larga o cambia (canciones, jugadores, artículos de una tienda), activa el autocompletado: mientras el usuario escribe, Discord le pregunta a tu bot y este contesta con hasta 25 sugerencias que encajan con lo escrito. Ojo: el usuario puede ignorarlas y escribir otra cosa, así que compruébalo al ejecutar el comando.

index.js (discord.js)
const FRUTAS = ["manzana", "melón", "naranja", "pera", "plátano"];

// Al definir la opción: .addStringOption((o) => o.setName("fruta").setDescription("Cuál").setAutocomplete(true))

client.on(Events.InteractionCreate, async (i) => {
  if (i.isAutocomplete()) {
    const escrito = i.options.getFocused().toLowerCase();
    const encajan = FRUTAS.filter((f) => f.startsWith(escrito)).slice(0, 25);
    await i.respond(encajan.map((f) => ({ name: f, value: f })));
    return;
  }
  // … aquí, los comandos
});
app.py (discord.py)
FRUTAS = ["manzana", "melón", "naranja", "pera", "plátano"]


@tree.command(name="fruta", description="Elige una fruta")
async def fruta(interaction: discord.Interaction, fruta: str):
    await interaction.response.send_message(f"Has elegido {fruta}")


@fruta.autocomplete("fruta")
async def sugerir_fruta(interaction: discord.Interaction, actual: str):
    encajan = [f for f in FRUTAS if f.startswith(actual.lower())][:25]
    return [app_commands.Choice(name=f, value=f) for f in encajan]

Globales o de un servidor: cuándo se ven los cambios

Los comandos globales salen en todos los servidores donde está el bot; los de un servidor, solo en ese. Mientras programas, regístralos en tu servidor de pruebas: el cambio se ve al momento. Cuando estén listos, pásalos a globales. Si después de registrar no ves un comando o sigues viendo la versión vieja, recarga Discord (Ctrl+R en el ordenador, o cierra y abre la aplicación): guarda la lista en caché.

Registrar no hace falta en cada arranque: solo cuando cambias un comando (nombre, descripción u opciones). Si cambias solo lo que hace por dentro, basta con reiniciar el bot. Y Discord limita cuántas veces al día se pueden crear comandos, así que un bot que se reinicia en bucle y los vuelve a crear puede quedarse un rato sin poder registrarlos. Con set() (discord.js) o sync() (discord.py) se manda la lista entera: lo que ya no está en tu código desaparece de Discord.

index.js (discord.js)
const SERVIDOR_PRUEBAS = "123456789012345678"; // clic derecho en tu servidor → «Copiar ID del servidor»

// Solo en tu servidor de pruebas (al momento):
await c.application.commands.set(comandos.map((x) => x.toJSON()), SERVIDOR_PRUEBAS);
// Globales (en todos los servidores del bot):
await c.application.commands.set(comandos.map((x) => x.toJSON()));
app.py (discord.py)
SERVIDOR_PRUEBAS = discord.Object(id=123456789012345678)  # «Copiar ID del servidor»

# Solo en tu servidor de pruebas (al momento):
tree.copy_global_to(guild=SERVIDOR_PRUEBAS)
await tree.sync(guild=SERVIDOR_PRUEBAS)
# Globales (en todos los servidores del bot):
await tree.sync()

Si no aparecen los comandos

  1. Mira la consola del bot

    Un nombre con mayúsculas o espacios, una descripción vacía o de más de 100 caracteres, o una opción obligatoria después de una opcional hacen que Discord rechace la lista entera, y el error sale al arrancar.

  2. Comprueba que se registran

    Que la línea de set() o sync() se ejecuta de verdad (por ejemplo, que está dentro del «ready» y el bot llega a decir «Conectado como…»).

  3. Recarga Discord

    Ctrl+R en el ordenador; en el móvil, cierra la aplicación del todo y ábrela.

  4. Invítalo otra vez

    Si lo invitaste hace mucho sin marcar «applications.commands», vuelve a usar el enlace de «OAuth2» → «URL Generator» con «bot» y «applications.commands» marcados: no hace falta echarlo antes.

  5. Mira los permisos

    Si el comando lleva un permiso por defecto, quien no lo tiene no lo ve: pruébalo con una cuenta de administrador o revisa «Ajustes del servidor» → «Integraciones».

Con el bot en NapNodes

Para que los comandos funcionen siempre, el bot tiene que estar encendido aunque apagues tu ordenador. En NapNodes subes el código en «Archivos» (o lo traes de tu repositorio de GitHub desde «Configuración») y, tras cambiar algo, pulsas «Reiniciar»: los errores al registrar los comandos salen en la «Consola». Todo el proceso, en la guía de hosting para tu bot.

El bot gratis tiene 256 MB y se renueva cada semana con un clic; los planes Mini (512 MB) y Plus (1 GB) están encendidos siempre sin renovar nada (precios de los bots).

Preguntas frecuentes

¿Por qué sale «La aplicación no respondió»?

Porque tu bot tardó más de 3 segundos en dar la primera respuesta, o el comando falló antes de responder (mira la consola). Si tiene que esperar a algo, usa deferReply() en discord.js o interaction.response.defer() en discord.py y contesta después: tienes 15 minutos.

¿Por qué no me salen los comandos nuevos?

Casi siempre es la caché de Discord: recárgalo con Ctrl+R. Si sigue sin salir, mira la consola del bot por si Discord rechazó la lista, y registra los comandos en tu servidor de pruebas, que se ven al momento.

¿Cómo hago que un comando solo lo usen los moderadores?

Con un permiso por defecto: setDefaultMemberPermissions() en discord.js o @app_commands.default_permissions() en discord.py. A quien no tiene ese permiso no le aparece, y los administradores pueden ajustarlo en «Ajustes del servidor» → «Integraciones».

¿Cuántos comandos con barra puede tener un bot?

Hasta 100 globales, cada uno con hasta 25 opciones. Si necesitas más, agrúpalos en subcomandos (/musica poner, /musica parar).

¿Tengo que registrar los comandos cada vez que arranca el bot?

No: solo cuando cambias su nombre, descripción u opciones. Si solo cambias lo que hacen por dentro, basta con reiniciar el bot.

¿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

Todas las guías de Bots de Discord, ordenadas →