Publica un panel con un botón para ponerse o quitarse un rol de color. El bot decide en el servidor qué rol puede conceder: el botón nunca acepta un ID de rol elegido por el usuario. El mensaje continúa funcionando después de reiniciar el proceso porque las pulsaciones se atienden en el manejador global.
Última revisión: 2 de octubre de 2026. Probado localmente con: Node.js 24.15.0 y discord.js 14.27.0, componentes, imports y flujo con un servidor simulado. La asignación real de roles requiere probarlo en tu servidor Discord; no se ejecutó contra una cuenta real durante esta revisión.
Prepara un rol permitido
Crea una aplicación propia en el Developer Portal. Instálala con Guild Install y scopes bot y applications.commands. Dale View Channel, Send Messages y Manage Roles; no necesita Administrator. Deja vacío Interactions Endpoint URL, ya que recibiremos las interacciones por Gateway.
Crea un rol Comunidad sin permisos y arrástralo debajo del rol más alto del bot. Este ejemplo solo acepta un rol sin permisos base, que no sea administrado por una integración ni sea @everyone. Revisa además que ese rol no desbloquee canales privados mediante sobrescrituras: cero permisos base no implica cero acceso adicional en todos los canales.
En una carpeta independiente ejecuta npm init -y y npm install --save-exact [email protected]. Los archivos usarán extensión .mjs. Rellena este .env con tus datos; CLIENT_ID es Application ID y los otros IDs se copian con el modo desarrollador de Discord:
DISCORD_TOKEN=
CLIENT_ID=
GUILD_ID=
ROLE_ID=Excluye .env y node_modules/ con .gitignore. No publiques el token. Si se filtra, regénéralo desde Bot y actualiza tu entorno. Usa una aplicación y un servidor de pruebas propios para el registro que viene a continuación.
Registra el panel de administración
Guarda deploy.mjs:
import { REST, Routes, SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
const { DISCORD_TOKEN, CLIENT_ID, GUILD_ID } = process.env;
if (![DISCORD_TOKEN, CLIENT_ID, GUILD_ID].every(Boolean)) throw new Error('Falta configurar .env');
const command = new SlashCommandBuilder()
.setName('panel-rol')
.setDescription('Publica el botón del rol Comunidad')
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild);
await new REST({ version: '10' }).setToken(DISCORD_TOKEN)
.put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), { body: [command.toJSON()] });
console.log('Panel registrado');El registro sustituye los comandos de esta aplicación en ese servidor. Si integras el panel en otro bot, reúne todas sus definiciones en el mismo despliegue. El permiso predeterminado limita quién ve el comando, pero también lo comprobaremos al ejecutarlo.
El botón y la regla del servidor
Guarda handler.mjs. Primero añade la construcción del panel y la comprobación del administrador:
import {
ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags, PermissionFlagsBits,
} from 'discord.js';
const buttonId = 'silencers:community';
const busy = new Set();
export async function handle(i, config) {
if (!i.inGuild() || i.guildId !== config.guildId) return;
if (i.isChatInputCommand() && i.commandName === 'panel-rol') {
if (!i.memberPermissions?.has(PermissionFlagsBits.ManageGuild)) {
await i.reply({ content: 'Necesitas Manage Server.', flags: MessageFlags.Ephemeral });
return;
}
const row = new ActionRowBuilder().addComponents(new ButtonBuilder()
.setCustomId(buttonId).setLabel('Activar / quitar Comunidad').setStyle(ButtonStyle.Primary));
await i.reply({ content: 'Elige si quieres el rol Comunidad.', components: [row], allowedMentions: { parse: [] } });
return;
}El panel es público; la confirmación del botón será privada. Continúa en el mismo archivo, sin cerrar todavía la función:
if (!i.isButton() || i.customId !== buttonId) return;
const key = `${i.guildId}:${i.user.id}`;
if (busy.has(key)) {
await i.reply({ content: 'Tu cambio anterior sigue en curso.', flags: MessageFlags.Ephemeral });
return;
}
busy.add(key);
try {
await i.deferReply({ flags: MessageFlags.Ephemeral });
const role = await i.guild.roles.fetch(config.roleId);
const bot = await i.guild.members.fetchMe({ force: true });
if (!role || role.id === i.guildId || role.managed || role.permissions.bitfield !== 0n ||
!bot.permissions.has(PermissionFlagsBits.ManageRoles) ||
role.comparePositionTo(bot.roles.highest) >= 0) {
await i.editReply('El rol no cumple la configuración permitida. Consulta al administrador.');
return;
}
const member = await i.guild.members.fetch({ user: i.user.id, force: true });
if (member.roles.cache.has(role.id)) {
await member.roles.remove(role, 'Botón Comunidad solicitado por el miembro');
await i.editReply('Rol Comunidad retirado.');
} else {
await member.roles.add(role, 'Botón Comunidad solicitado por el miembro');
await i.editReply('Rol Comunidad añadido.');
}
} finally {
busy.delete(key);
}
}Se consulta el miembro concreto por REST para decidir con datos actuales. No se solicita la lista completa de miembros ni se necesita el intent privilegiado GuildMembers para esta consulta individual. La jerarquía sigue siendo obligatoria aunque el bot tenga Manage Roles. El bloqueo evita dos cambios simultáneos de una persona dentro de este proceso; si ejecutas varias réplicas necesitarás coordinarlo fuera de memoria.

Arranca el bot
Guarda bot.mjs:
import { Client, Events, GatewayIntentBits, MessageFlags } from 'discord.js';
import { handle } from './handler.mjs';
const { DISCORD_TOKEN, GUILD_ID, ROLE_ID } = process.env;
if (![DISCORD_TOKEN, GUILD_ID, ROLE_ID].every(Boolean)) throw new Error('Falta configurar .env');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, ready => console.log(`Conectado: ${ready.user.tag}`));
client.on(Events.InteractionCreate, async i => {
try {
await handle(i, { guildId: GUILD_ID, roleId: ROLE_ID });
} catch (error) {
console.error('Cambio fallido:', error.code ?? error.name);
try {
if (i.deferred) await i.editReply('No se pudo completar el cambio. Revisa permisos y jerarquía.');
else if (!i.replied) await i.reply({ content: 'No se pudo completar la acción.', flags: MessageFlags.Ephemeral });
} catch { /* La interacción puede haber caducado. */ }
}
});
await client.login(DISCORD_TOKEN);Ejecuta node --env-file=.env deploy.mjs una vez y luego node --env-file=.env bot.mjs. Un administrador utiliza /panel-rol. Prueba el botón con un miembro normal: la primera pulsación debe añadir el rol y la segunda retirarlo. Reinicia el bot y pulsa el mensaje anterior; seguirá funcionando porque no depende de un collector temporal.
Si aparece Missing Permissions, comprueba Manage Roles y la posición de ambos roles. Si cambiaron el rol para darle permisos, este ejemplo lo rechazará deliberadamente. Si Discord muestra «La aplicación no respondió», revisa que el proceso esté conectado y llegue a deferReply dentro de los tres segundos. No borres el panel para intentar corregir un error de jerarquía.
Fuentes y documentación
Proyecto y desarrollador: discord.js, equipo discord.js y colaboradores. Sitio y documentación: discord.js.org. Descarga oficial: npm 14.27.0. Licencia: Apache 2.0. Node.js, equipo Node.js, licencia MIT y licencias de componentes. Los ejemplos y gráficos son originales de The Silencers; no se adjuntan librerías ni tokens.






Comments 0
Join the conversation with your account.
Sign inLoading comments…