NapNodes

← Guías de Bots de Discord

Bots de Discord · actualizada el 23 de octubre de 2026

Bot de moderación para Discord en Python: avisos, silenciar, expulsar, banear y borrar mensajes

Un bot de moderación hace en un comando lo que a mano lleva varios clics, y además apunta quién ha hecho qué. Este, en discord.py, trae seis comandos con barra: /aviso (con historial que no se pierde al reiniciar), /avisos, /silenciar, /expulsar, /banear y /borrar, y deja constancia de todo en un canal de registro. Unas 150 líneas, sin base de datos y sin intents especiales.

¿Se lo pasas a tu grupo?WhatsApp

Qué hace el bot y qué no

Los comandos solo los ve quien tiene el permiso que hace falta para cada cosa (silenciar, expulsar, banear, gestionar mensajes): no hay que montar roles aparte. Antes de actuar, el bot comprueba la jerarquía de roles, que es lo que más falla: nadie puede moderar a alguien que esté a su altura o por encima, ni el moderador ni el bot.

Lo que no hace es leer el chat buscando palabrotas o spam. Para eso Discord ya trae AutoMod, en «Ajustes del servidor» → «AutoMod»: filtra palabras, enlaces, spam y menciones masivas sin ningún bot, y funciona aunque el tuyo esté apagado. Un bot que lea todos los mensajes necesita el Message Content Intent y es más trabajo para lo mismo. Lo sensato es AutoMod para filtrar y este bot para lo que decide una persona.

¿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. Crea el bot en Discord

  1. Crea la aplicación y saca el token

    En discord.com/developers/applications, «New Application»; en la pestaña «Bot», «Reset Token» y copia el token. Es la contraseña del bot: no lo compartas ni lo subas a GitHub. Si es tu primer bot, el proceso paso a paso está en tu primer bot en Python.

  2. Invítalo con los permisos de moderar

    En «OAuth2» → «URL Generator», marca «bot» y «applications.commands», y en los permisos «Send Messages», «Embed Links», «Read Message History», «Manage Messages», «Moderate Members», «Kick Members» y «Ban Members». Con la calculadora de permisos de bot los ves en castellano y te da el enlace. Los «Privileged Gateway Intents» se quedan apagados.

  3. Pon su rol por encima de los miembros normales

    En «Ajustes del servidor» → «Roles», arrastra el rol del bot por encima de los roles de quienes vaya a moderar, y por debajo del de los moderadores. Un bot no puede silenciar, expulsar ni banear a alguien cuyo rol más alto esté a su altura o por encima, aunque tenga el permiso.

  4. (Opcional) Crea el canal de registro

    Un canal privado, solo para moderadores (por ejemplo #registro-moderación), donde el bot apunte cada acción. Activa el «Modo desarrollador» en tus ajustes de Discord → «Avanzado», haz clic derecho en el canal y «Copiar ID del canal»: ese número va en el .env.

2. El código

Tres ficheros en una carpeta. El token y el id del canal de registro van en .env, sin comillas (si no quieres canal de registro, borra esa línea). Hace falta Python 3.10 o más nuevo. Los avisos se guardan en avisos.json, que el bot crea solo junto a app.py.

requirements.txt
discord.py
python-dotenv
.env
DISCORD_TOKEN=pega-aquí-tu-token
CANAL_REGISTRO=123456789012345678
app.py
import datetime
import json
import os
from pathlib import Path

import discord
from discord import app_commands
from dotenv import load_dotenv

load_dotenv()  # lee el token y el canal de registro del fichero .env

# Basta con los intents por defecto: los comandos con barra llegan como interacciones.
client = discord.Client(intents=discord.Intents.default())
tree = app_commands.CommandTree(client)

# Los avisos se guardan en un fichero junto al bot: no se pierden al reiniciar.
AVISOS = Path("avisos.json")
CANAL_REGISTRO = int(os.getenv("CANAL_REGISTRO") or 0)


def leer_avisos() -> dict:
    return json.loads(AVISOS.read_text("utf-8")) if AVISOS.exists() else {}


def guardar_avisos(datos: dict) -> None:
    AVISOS.write_text(json.dumps(datos, ensure_ascii=False, indent=2), "utf-8")


def no_se_puede(interaction: discord.Interaction, miembro: discord.Member) -> str | None:
    """Discord no deja moderar a quien está a tu altura o por encima: lo miramos antes."""
    guild = interaction.guild
    if miembro == interaction.user:
        return "No puedes usarlo contigo."
    if miembro.id == guild.owner_id:
        return "Al dueño del servidor no se le puede moderar."
    if miembro.top_role >= guild.me.top_role:
        return f"El rol de {miembro.display_name} está a mi altura o por encima: sube el mío en «Ajustes del servidor» → «Roles»."
    if interaction.user.id != guild.owner_id and miembro.top_role >= interaction.user.top_role:
        return f"El rol de {miembro.display_name} está a tu altura o por encima."
    return None


async def registrar(interaction: discord.Interaction, accion: str, miembro: discord.Member, motivo: str) -> None:
    """Apunta la acción en el canal de registro, si hay uno en el .env."""
    canal = client.get_channel(CANAL_REGISTRO)
    if canal is None:
        return
    tarjeta = discord.Embed(title=accion, color=0x5B4AE8, timestamp=discord.utils.utcnow())
    tarjeta.add_field(name="Miembro", value=f"{miembro.mention} ({miembro})")
    tarjeta.add_field(name="Por", value=interaction.user.mention)
    tarjeta.add_field(name="Motivo", value=motivo, inline=False)
    await canal.send(embed=tarjeta)


async def avisar_por_privado(miembro: discord.Member, texto: str) -> None:
    # Antes de expulsar o banear: después ya no se le puede escribir.
    try:
        await miembro.send(texto)
    except discord.HTTPException:
        pass  # tiene los mensajes privados cerrados


@tree.command(name="aviso", description="Da un aviso a un miembro (queda apuntado)")
@app_commands.guild_only()
@app_commands.default_permissions(moderate_members=True)
@app_commands.describe(miembro="A quién", motivo="Por qué")
async def aviso(interaction: discord.Interaction, miembro: discord.Member, motivo: str):
    if error := no_se_puede(interaction, miembro):
        await interaction.response.send_message(error, ephemeral=True)
        return
    datos = leer_avisos()
    lista = datos.setdefault(str(interaction.guild.id), {}).setdefault(str(miembro.id), [])
    lista.append({"motivo": motivo, "por": interaction.user.id, "fecha": discord.utils.utcnow().isoformat()})
    guardar_avisos(datos)
    await interaction.response.send_message(f"⚠️ {miembro.mention}, tienes un aviso: {motivo} (llevas {len(lista)}).")
    await registrar(interaction, f"Aviso n.º {len(lista)}", miembro, motivo)


@tree.command(name="avisos", description="Los avisos que lleva un miembro")
@app_commands.guild_only()
@app_commands.default_permissions(moderate_members=True)
async def avisos(interaction: discord.Interaction, miembro: discord.Member):
    lista = leer_avisos().get(str(interaction.guild.id), {}).get(str(miembro.id), [])
    if not lista:
        await interaction.response.send_message(f"{miembro.display_name} no tiene avisos.", ephemeral=True)
        return
    lineas = [f"{n}. {a['fecha'][:10]} — {a['motivo']} (<@{a['por']}>)" for n, a in enumerate(lista[-15:], 1)]
    await interaction.response.send_message(f"Avisos de {miembro.display_name} ({len(lista)}):\n" + "\n".join(lineas), ephemeral=True)


@tree.command(name="silenciar", description="Aísla temporalmente a un miembro: no puede escribir ni hablar")
@app_commands.guild_only()
@app_commands.default_permissions(moderate_members=True)
@app_commands.describe(minutos="De 1 minuto a 28 días (40320 minutos)")
async def silenciar(interaction: discord.Interaction, miembro: discord.Member,
                    minutos: app_commands.Range[int, 1, 40320], motivo: str = "Sin motivo"):
    if error := no_se_puede(interaction, miembro):
        await interaction.response.send_message(error, ephemeral=True)
        return
    await miembro.timeout(datetime.timedelta(minutes=minutos), reason=motivo)
    await interaction.response.send_message(f"🔇 {miembro.mention} silenciado {minutos} min: {motivo}")
    await registrar(interaction, f"Silenciado {minutos} min", miembro, motivo)


@tree.command(name="expulsar", description="Echa a un miembro (puede volver con otra invitación)")
@app_commands.guild_only()
@app_commands.default_permissions(kick_members=True)
async def expulsar(interaction: discord.Interaction, miembro: discord.Member, motivo: str = "Sin motivo"):
    if error := no_se_puede(interaction, miembro):
        await interaction.response.send_message(error, ephemeral=True)
        return
    await avisar_por_privado(miembro, f"Te han expulsado de {interaction.guild.name}: {motivo}")
    await miembro.kick(reason=motivo)
    await interaction.response.send_message(f"👢 {miembro} expulsado: {motivo}")
    await registrar(interaction, "Expulsado", miembro, motivo)


@tree.command(name="banear", description="Echa a un miembro y no le deja volver")
@app_commands.guild_only()
@app_commands.default_permissions(ban_members=True)
@app_commands.describe(dias_mensajes="Borrar sus mensajes de los últimos días (0 a 7)")
async def banear(interaction: discord.Interaction, miembro: discord.Member, motivo: str = "Sin motivo",
                 dias_mensajes: app_commands.Range[int, 0, 7] = 0):
    if error := no_se_puede(interaction, miembro):
        await interaction.response.send_message(error, ephemeral=True)
        return
    await avisar_por_privado(miembro, f"Te han baneado de {interaction.guild.name}: {motivo}")
    await interaction.guild.ban(miembro, reason=motivo, delete_message_seconds=dias_mensajes * 86400)
    await interaction.response.send_message(f"🔨 {miembro} baneado: {motivo}")
    await registrar(interaction, "Baneado", miembro, motivo)


@tree.command(name="borrar", description="Borra los últimos mensajes de este canal")
@app_commands.guild_only()
@app_commands.default_permissions(manage_messages=True)
async def borrar(interaction: discord.Interaction, cantidad: app_commands.Range[int, 1, 100]):
    # Borrar muchos tarda: primero le decimos a Discord que espere.
    await interaction.response.defer(ephemeral=True)
    borrados = await interaction.channel.purge(limit=cantidad)
    await interaction.followup.send(f"Borrados {len(borrados)} mensajes.", ephemeral=True)


@tree.error
async def al_fallar(interaction: discord.Interaction, error: app_commands.AppCommandError):
    original = getattr(error, "original", error)
    if isinstance(original, discord.Forbidden):
        texto = "Me falta un permiso para eso, o el rol de esa persona está por encima del mío."
    else:
        texto = "Algo ha fallado: mira la consola del bot."
        print(f"Error: {original!r}")
    if interaction.response.is_done():
        await interaction.followup.send(texto, ephemeral=True)
    else:
        await interaction.response.send_message(texto, ephemeral=True)


@client.event
async def on_ready():
    await tree.sync()  # publica los seis comandos
    print(f"Conectado como {client.user}")


client.run(os.environ["DISCORD_TOKEN"])

3. Pruébalo

Quién ve cada comando se cambia sin tocar el código en «Ajustes del servidor» → «Integraciones» → tu bot: por ejemplo, dejar /banear solo al rol de administradores y /aviso también a los ayudantes.

  1. Arranca el bot

    En una terminal, en la carpeta: pip install -r requirements.txt y python app.py. Cuando diga «Conectado como…», ya está. Los comandos pueden tardar unos minutos en salir la primera vez.

  2. Prueba con una cuenta de prueba

    Invita a una segunda cuenta tuya (o a un amigo que se preste) sin roles. Con tu cuenta, /aviso a esa cuenta: sale el aviso en el canal y, si pusiste CANAL_REGISTRO, la tarjeta en el registro. /avisos te enseña el historial solo a ti.

  3. Silencia un minuto

    /silenciar con 1 minuto: la otra cuenta no puede escribir ni hablar en voz hasta que pase. Para quitarlo antes, clic derecho en el miembro y quítale el aislamiento temporal («Remove Timeout» en el Discord en inglés).

  4. Limpia el canal

    /borrar 10 borra los diez últimos mensajes del canal donde lo escribes. Solo lo ve quien puede gestionar mensajes.

Si algo no va

«El rol de … está a mi altura o por encima»: el rol del bot tiene que estar más arriba que el rol más alto de la persona a moderar. Súbelo en «Ajustes del servidor» → «Roles». Por eso no se puede moderar a otro administrador ni al dueño del servidor, ni con el bot ni a mano.

«Me falta un permiso para eso»: al invitarlo no se marcó alguno de los permisos de la lista, o un canal concreto se lo quita. Sácale el enlace otra vez con los permisos de arriba (no hace falta echarlo) o mira los permisos de ese canal.

/borrar no borra mensajes antiguos de golpe: Discord solo deja borrar en bloque los de los últimos 14 días; los más viejos, discord.py los va borrando de uno en uno, así que tarda más. Si no salen los comandos, qué mirar cuando no aparecen; si el bot está en línea y no contesta, mi bot no responde.

4. Déjalo encendido siempre (gratis)

Un bot de moderación que solo funciona con tu ordenador encendido no sirve de mucho: los problemas suelen llegar de noche. Hay que alojarlo en un servidor.

En NapNodes: crea el bot gratis (nace en Node.js; para este, en «Configuración» → «Lenguaje», «Pasar a Python»), sube los tres ficheros en «Archivos» 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. avisos.json aparece en «Archivos» junto a app.py: de ahí lo puedes descargar o editar.

El bot gratis tiene 256 MB, de sobra para un bot de moderación, y se renueva con un clic cada semana: si se te pasa, se apaga hasta que lo enciendes (y a los 14 días sin renovar se borra, con aviso antes; guárdate avisos.json). Para no tener que acordarte, el plan Mini (512 MB) está encendido siempre sin renovar nada (precios de los bots). Todo el montaje, en la guía de hosting para tu bot.

Ideas para ampliarlo

Castigos automáticos: tras el tercer aviso, silenciar un día (en /aviso, si len(lista) llega a 3, llama a miembro.timeout). Un /quitar-aviso que borre el último de la lista. Apuntar también en el registro los mensajes que borra AutoMod, con el evento on_automod_action (necesita el intent de ejecución de AutoMod; la herramienta de intents te dice cuál). O juntarlo con el bot de bienvenida y el de roles en un solo bot: los tres caben en el gratis.

Preguntas frecuentes

¿Cómo hago un bot de moderación para Discord?

Con comandos con barra que llaman a las acciones de Discord (aislar temporalmente, expulsar, banear, borrar mensajes) y comprueban antes la jerarquía de roles. El código completo en Python, con avisos y canal de registro, está en esta guía.

¿Qué permisos necesita un bot de moderación?

Moderate Members para silenciar, Kick Members para expulsar, Ban Members para banear y Manage Messages con Read Message History para borrar; además, Send Messages y Embed Links para contestar. Y su rol, por encima del de quienes vaya a moderar.

¿Por qué mi bot no puede banear a alguien?

Casi siempre es la jerarquía: el rol más alto de esa persona está a la altura del rol del bot o por encima. Al dueño del servidor no se le puede banear nunca.

¿Hace falta un bot para filtrar palabrotas en Discord?

No. AutoMod, en «Ajustes del servidor» → «AutoMod», filtra palabras, spam y menciones masivas sin bot. El bot sirve para lo que decide una persona: avisos con historial, silenciar, expulsar y dejarlo apuntado.

¿Se pierden los avisos al reiniciar el bot?

No: se guardan en avisos.json, junto al bot. Si lo cambias de sitio, llévate ese fichero.

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

Herramienta gratis: Permisos de un bot de Discord. Marca los permisos de tu bot en castellano y sal con el número y el enlace de invitación; o pega un número y mira qué permisos da.

Sigue leyendo

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