Crea la aplicación y el bot
En el Developer Portal elige New Application. Copia el Application ID de General Information. En la pestaña Bot presiona Reset Token y copia el token una sola vez; Discord no lo vuelve a mostrar.
Invita el bot a tu servidor de prueba
En OAuth2, usa el generador de URL con los scopes
botyapplications.commandsy solo los permisos necesarios (para este bot, Send Messages). Abre la URL y elige tu servidor de prueba.Activa el Modo desarrollador en Discord (Configuración, Avanzado) y haz clic derecho en el servidor para copiar su ID.
Crea el proyecto
mkdir mi-bot-discord && cd mi-bot-discord npm init -y npm pkg set type=module npm install discord.js printf 'DISCORD_TOKEN=\nCLIENT_ID=\nGUILD_ID=\n' > .env printf '.env\nnode_modules\n' > .gitignoreCompleta
.envcon el token, el Application ID y el ID del servidor.Registra los comandos slash
register.jsimport { REST, Routes, SlashCommandBuilder } from "discord.js"; const { DISCORD_TOKEN, CLIENT_ID, GUILD_ID } = process.env; const commands = [ new SlashCommandBuilder().setName("ping").setDescription("Check that the bot is alive"), new SlashCommandBuilder().setName("echo").setDescription("Repeat a text") .addStringOption((o) => o.setName("text").setDescription("What to repeat").setRequired(true).setMaxLength(500)), ].map((c) => c.toJSON()); const rest = new REST().setToken(DISCORD_TOKEN); // Guild commands update instantly; global commands can take longer to appear. const route = GUILD_ID ? Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID) : Routes.applicationCommands(CLIENT_ID); const data = await rest.put(route, { body: commands }); console.log(`registered ${data.length} commands${GUILD_ID ? " in guild " + GUILD_ID : " globally"}`);node --env-file=.env register.jsResponde los comandos
index.jsimport { Client, Events, GatewayIntentBits, MessageFlags } from "discord.js"; // Slash commands only need the Guilds intent: no privileged intents required. const client = new Client({ intents: [GatewayIntentBits.Guilds] }); client.once(Events.ClientReady, (c) => console.log(`logged in as ${c.user.tag}`)); client.on(Events.InteractionCreate, async (interaction) => { if (!interaction.isChatInputCommand()) return; try { if (interaction.commandName === "ping") { await interaction.reply(`pong (${client.ws.ping} ms)`); } else if (interaction.commandName === "echo") { const text = interaction.options.getString("text", true); // allowedMentions stops the bot from pinging @everyone on behalf of a user await interaction.reply({ content: text, allowedMentions: { parse: [] } }); } } catch (err) { console.error("interaction failed:", err.message); if (!interaction.replied) await interaction.reply({ content: "Something went wrong.", flags: MessageFlags.Ephemeral }).catch(() => {}); } }); process.once("SIGTERM", () => client.destroy().finally(() => process.exit(0))); await client.login(process.env.DISCORD_TOKEN);Debes responder una interacción en menos de 3 segundos. Para trabajo lento llama primero a
interaction.deferReply()y edita la respuesta después.Ejecuta y prueba
node --env-file=.env index.jsFigura 2. Salida esperada (ilustración). Escribe
/pingen tu servidor. El bot solo está en línea mientras el proceso corre, así que el siguiente paso es desplegarlo en un hosting siempre encendido.
Solución de problemas
- No aparecen los comandos: revisa que la invitación incluyera applications.commands y que GUILD_ID sea el servidor correcto. Los comandos globales (sin GUILD_ID) pueden tardar.
- Used disallowed intents: pediste un intent privilegiado (Message Content, Members, Presence) que no está activado en la pestaña Bot. Los bots con comandos slash no los necesitan.
- La interacción falló: la respuesta tardó más de 3 segundos o lanzó un error. Revisa la consola y usa deferReply para trabajo lento.
Dónde correrlo 24/7: el mejor hosting gratuito para bots de Discord, tutorial de Railway, VPS con systemd. Documentación oficial: guía de discord.js, application commands.