BotNetwork

Comandos de barra en discord.js v14: registrar, manejar y diferir

Los comandos de barra son la forma principal en que los usuarios hablan con un bot de Discord. No necesitan el intent de Message Content, validan la entrada por ti y aparecen en el selector de comandos.

Publicado 4 min de lectura

Lo que necesitas

  • Node.js 18 o más reciente (el mínimo que indica discord.js al momento de escribir; la línea v14 iba en la 14.27 cuando se escribió esto).
  • Una aplicación en el Discord Developer Portal con usuario bot, su token y su ID de aplicación.
  • El bot invitado con los scopes bot y applications.commands. Nuestra calculadora de permisos arma la URL de invitación.
npm install discord.js

Guarda el token en una variable de entorno, nunca en el código. En local, node --env-file=.env lo carga. Mira cómo proteger los tokens de tu bot.

Define los comandos

Un comando de barra son datos: un nombre (minúsculas, 1-32 caracteres), una descripción (1-100 caracteres) y opciones. SlashCommandBuilder genera ese JSON.

// commands.js
import { SlashCommandBuilder } from "discord.js";

export const commands = [
  new SlashCommandBuilder()
    .setName("ping")
    .setDescription("Comprueba que el bot está vivo"),
  new SlashCommandBuilder()
    .setName("recordar")
    .setDescription("Crea un recordatorio")
    .addIntegerOption(o => o.setName("minutos").setDescription("En cuántos minutos").setRequired(true).setMinValue(1).setMaxValue(1440))
    .addStringOption(o => o.setName("texto").setDescription("Qué recordarte").setRequired(true).setMaxLength(200)),
];

Discord aplica tipos, campos obligatorios y mínimos/máximos antes de que corra tu código, lo que elimina toda una clase de errores de parseo. Si prefieres un editor visual, el constructor de comandos de Discord genera el mismo JSON.

Registra: primero por servidor, luego global

Los comandos se registran con la API REST, no en cada arranque. Ejecuta un script pequeño cuando cambien las definiciones.

// deploy-commands.js
import { REST, Routes } from "discord.js";
import { commands } from "./commands.js";

const rest = new REST().setToken(process.env.DISCORD_TOKEN);
const body = commands.map(c => c.toJSON());
const appId = process.env.APP_ID;

if (process.env.DEV_GUILD_ID) {
  await rest.put(Routes.applicationGuildCommands(appId, process.env.DEV_GUILD_ID), { body });
} else {
  await rest.put(Routes.applicationCommands(appId), { body });
}
console.log(`Registrados ${body.length} comandos`);
  • Comandos de servidor (guild): aplican a un servidor y son la forma más rápida de iterar en desarrollo.
  • Comandos globales: aplican en todo lugar donde esté instalada la app. Úsalos en producción.
  • PUT reemplaza la lista completa, así que un comando que quitas del arreglo se borra. Normalmente es lo que quieres.

Si registras en ambos lugares, cada comando sale dos veces en tu servidor de pruebas. Vacía la lista del servidor (body: []) al pasar a global.

Maneja la interacción

// index.js
import { Client, Events, GatewayIntentBits, MessageFlags } from "discord.js";

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, c => console.log(`Listo como ${c.user.tag}`));

client.on(Events.InteractionCreate, async interaction => {
  if (!interaction.isChatInputCommand()) return;
  try {
    if (interaction.commandName === "ping") {
      await interaction.reply({ content: "Pong", flags: MessageFlags.Ephemeral });
    } else if (interaction.commandName === "recordar") {
      const minutos = interaction.options.getInteger("minutos", true);
      const texto = interaction.options.getString("texto", true);
      await interaction.reply(`Listo, te lo recuerdo en ${minutos} min: ${texto}`);
    }
  } catch (err) {
    console.error(err);
    const msg = { content: "Algo salió mal.", flags: MessageFlags.Ephemeral };
    if (interaction.deferred || interaction.replied) await interaction.followUp(msg);
    else await interaction.reply(msg);
  }
});

client.login(process.env.DISCORD_TOKEN);

Solo hace falta el intent Guilds. Los comandos de barra no requieren el intent privilegiado de Message Content; mira los intents del gateway de Discord explicados.

La regla de los 3 segundos y diferir

Debes enviar una respuesta inicial en 3 segundos, o el usuario verá "La aplicación no ha respondido". Después, el token de la interacción sigue válido 15 minutos para ediciones y mensajes adicionales. Para algo lento (una API, una consulta a base de datos, un modelo de IA), difiere primero:

await interaction.deferReply();            // muestra "El bot está pensando..."
const respuesta = await trabajoLento();
await interaction.editReply(respuesta);

Pasa { flags: MessageFlags.Ephemeral } a deferReply si la respuesta final debe ser privada. No puedes cambiar la visibilidad después de diferir.

Autocompletado

Para opciones con muchos valores posibles, activa .setAutocomplete(true) en una opción de texto y responde a las interacciones isAutocomplete() con hasta 25 opciones:

if (interaction.isAutocomplete()) {
  const escrito = interaction.options.getFocused().toLowerCase();
  const coinciden = CIUDADES.filter(c => c.toLowerCase().startsWith(escrito)).slice(0, 25);
  return interaction.respond(coinciden.map(c => ({ name: c, value: c })));
}

Las respuestas de autocompletado también deben llegar dentro de los 3 segundos, así que la búsqueda debe ser rápida (en memoria o en caché).

Estructura cuando el bot crece

Pon cada comando en su propio archivo que exporte data y execute, cárgalos en una Collection por nombre y busca interaction.commandName ahí. La guía oficial muestra este patrón. Restringe los comandos de administración con .setDefaultMemberPermissions() y .setContexts() en lugar de revisar roles a mano.

Preguntas frecuentes

¿Por qué no aparecen mis comandos de barra?

Casi siempre es una de estas: nunca se registraron (ejecuta el script), se registraron en otro ID de servidor, el bot se invitó sin el scope applications.commands o el cliente tiene una lista vieja (reinicia la app de Discord). Nuestra lista de diagnóstico cubre el resto.

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

No. El registro es persistente. Repítelo solo cuando cambien nombres, descripciones u opciones. Registrar en cada arranque gasta llamadas a la API con límite de tasa.

¿Uso reply o followUp?

reply (o deferReply) es la única respuesta inicial obligatoria. followUp envía mensajes extra después y editReply cambia el primero.

Fuentes