NapNodes

← Guías de Bots de Discord

Bots de Discord · actualizada el 30 de octubre de 2026

Bot de registros para Discord: mensajes borrados y editados, entradas y salidas

Discord no guarda los mensajes borrados: cuando alguien borra uno, desaparece y nadie del servidor puede verlo, tampoco los administradores. Lo único que funciona es un bot que lo copie mientras está a la vista. Aquí lo tienes completo en Python (discord.py) y en JavaScript (discord.js): apunta en un canal privado los mensajes borrados, los editados con el antes y el después, y quién entra y quién se va.

¿Se lo pasas a tu grupo?WhatsApp

Lo que un bot puede registrar (y lo que no)

Cuando se borra un mensaje, Discord avisa a los bots de que se ha borrado, pero no les vuelve a mandar el texto: solo el número del mensaje y el canal. Por eso el bot guarda en memoria los últimos mensajes que ve llegar y, al borrarse uno, lo busca ahí. Lo que se escribió antes de que el bot estuviera en el servidor, o antes de su último reinicio, sale como borrado pero sin el texto.

Con las ediciones pasa lo mismo: el bot sabe lo que dice ahora el mensaje, y el «antes» solo si lo tenía en memoria.

No hay forma de ver los mensajes borrados de antes de poner el bot. Los programas que lo prometen modifican la aplicación de Discord, cosa que sus condiciones no permiten, y solo ven lo que pasó por tu propia pantalla.

Si lo que quieres saber es quién expulsó, baneó o cambió un rol, eso ya lo guarda Discord sin ningún bot, en «Ajustes del servidor» → «Registro de auditoría».

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

Antes del código: el bot y el canal

  1. Crea el bot

    Si aún no tienes uno: en discord.com/developers/applications, «New Application», y en la pestaña «Bot», «Reset Token» para sacar el token. Paso a paso en la guía de Python o en la de JavaScript.

  2. Enciende dos intents privilegiados

    En la misma pestaña «Bot», en «Privileged Gateway Intents», enciende «Message Content Intent» (para leer el texto de los mensajes) y «Server Members Intent» (para saber quién entra y quién sale), y guarda. Si el código los pide y aquí están apagados, el bot no arranca: discord.py dice PrivilegedIntentsRequired y discord.js, «Used disallowed intents».

  3. Haz un canal solo para moderadores

    Crea un canal de texto, por ejemplo #registros, y en «Editar canal» → «Permisos» quítale «Ver canal» a @everyone y dáselo a tu rol de moderación y al bot. Ahí van a salir mensajes que la gente creía borrados: que no lo vea todo el servidor.

  4. Invítalo con estos permisos

    En «OAuth2» → «URL Generator», marca «bot» y los permisos «View Channels», «Send Messages» y «Embed Links» (en español, «Insertar enlaces»: sin él los registros salen vacíos, porque van en embeds). La calculadora de permisos de bot te da el enlace hecho. El bot solo se entera de lo que pasa en los canales que puede ver: de un canal privado sin él, no registrará nada.

  5. Copia el ID del canal

    En «Ajustes de usuario» → «Avanzado», enciende el «Modo desarrollador». Después, clic derecho en #registros → «Copiar ID del canal». Ese número va en el fichero .env, junto al token.

El bot en Python (discord.py)

Junto al .env, requirements.txt y app.py. Hace falta Python 3.10 o más nuevo.

on_raw_message_delete salta con todos los borrados, también con los de mensajes que el bot no recuerda; payload.cached_message es el mensaje si lo tenía en memoria. on_message_edit, en cambio, solo salta con los mensajes que recuerda, que son los que tienen «antes». max_messages=5000 son los mensajes que guarda entre todos los canales (de serie, 1000).

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

import discord
from dotenv import load_dotenv

load_dotenv()  # lee el token y el canal del fichero .env
CANAL_REGISTRO = int(os.environ["CANAL_REGISTRO"])  # el ID del canal de registros

intents = discord.Intents.default()
intents.message_content = True  # leer el texto de los mensajes
intents.members = True  # saber quién entra y quién sale
# los últimos mensajes que recuerda el bot; de los más viejos no sabrá el texto
client = discord.Client(intents=intents, max_messages=5000)


def recortar(texto: str | None) -> str:
    texto = texto or "(sin texto)"
    return texto if len(texto) <= 1000 else texto[:1000] + "…"


async def registrar(guild: discord.Guild | None, embed: discord.Embed):
    # solo en el canal de registros de ese mismo servidor
    canal = client.get_channel(CANAL_REGISTRO)
    if canal and guild and canal.guild == guild:
        await canal.send(embed=embed)


@client.event
async def on_raw_message_delete(payload: discord.RawMessageDeleteEvent):
    if payload.channel_id == CANAL_REGISTRO or payload.guild_id is None:
        return
    msg = payload.cached_message  # solo si el bot lo vio llegar
    if msg and msg.author.bot:
        return
    embed = discord.Embed(title="Mensaje borrado", color=0xE74C3C, timestamp=discord.utils.utcnow())
    embed.add_field(name="Canal", value=f"<#{payload.channel_id}>")
    if msg:
        embed.description = recortar(msg.content)
        embed.add_field(name="Autor", value=msg.author.mention)
        if msg.attachments:
            nombres = ", ".join(a.filename for a in msg.attachments)
            embed.add_field(name="Adjuntos", value=recortar(nombres), inline=False)
    else:
        embed.description = "Era de antes de que el bot lo viera: Discord no da su texto."
    await registrar(client.get_guild(payload.guild_id), embed)


@client.event
async def on_message_edit(before: discord.Message, after: discord.Message):
    # también salta cuando Discord añade la vista previa de un enlace: sin cambio de texto, nada
    if after.guild is None or after.author.bot or before.content == after.content:
        return
    embed = discord.Embed(title="Mensaje editado", url=after.jump_url, color=0xF1C40F, timestamp=discord.utils.utcnow())
    embed.add_field(name="Autor", value=after.author.mention)
    embed.add_field(name="Canal", value=after.channel.mention)
    embed.add_field(name="Antes", value=recortar(before.content), inline=False)
    embed.add_field(name="Después", value=recortar(after.content), inline=False)
    await registrar(after.guild, embed)


@client.event
async def on_member_join(member: discord.Member):
    embed = discord.Embed(title="Ha entrado", description=f"{member.mention} ({member})", color=0x2ECC71, timestamp=discord.utils.utcnow())
    embed.add_field(name="Cuenta creada", value=discord.utils.format_dt(member.created_at, "R"))
    await registrar(member.guild, embed)


@client.event
async def on_member_remove(member: discord.Member):
    embed = discord.Embed(title="Se ha ido", description=f"{member.mention} ({member})", color=0x95A5A6, timestamp=discord.utils.utcnow())
    await registrar(member.guild, embed)


@client.event
async def on_ready():
    print(f"Conectado como {client.user}")


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

El bot en JavaScript (discord.js)

Junto al .env, package.json e index.js; instala con npm install.

Los partials hacen que discord.js avise también de los mensajes que no recuerda (llegan a medias, con partial en true y sin texto) y de quien se va sin estar en su memoria. La memoria de discord.js va por canal: de serie, 200 mensajes en cada uno; aquí, 1000.

.env
DISCORD_TOKEN=pega-aquí-tu-token
CANAL_REGISTRO=123456789012345678
package.json
{
  "name": "bot-registros",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "discord.js": "^14.27.0",
    "dotenv": "^16.4.0"
  }
}
index.js
require("dotenv").config(); // lee el token y el canal del fichero .env
const { Client, EmbedBuilder, Events, GatewayIntentBits, Options, Partials } = require("discord.js");

const CANAL_REGISTRO = process.env.CANAL_REGISTRO; // el ID del canal de registros

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent, // leer el texto de los mensajes
    GatewayIntentBits.GuildMembers, // saber quién entra y quién sale
  ],
  // sin esto, de un mensaje que el bot no recuerda no llega ni el aviso
  partials: [Partials.Message, Partials.GuildMember],
  // los mensajes que recuerda por canal (de serie, 200)
  makeCache: Options.cacheWithLimits({ ...Options.DefaultMakeCacheSettings, MessageManager: 1000 }),
});

const recortar = (texto) => (!texto ? "(sin texto)" : texto.length <= 1000 ? texto : texto.slice(0, 1000) + "…");

async function registrar(guild, embed) {
  // solo en el canal de registros de ese mismo servidor
  const canal = await client.channels.fetch(CANAL_REGISTRO).catch(() => null);
  if (canal && guild && canal.guildId === guild.id) await canal.send({ embeds: [embed] });
}

client.on(Events.MessageDelete, async (msg) => {
  if (!msg.guild || msg.channelId === CANAL_REGISTRO || msg.author?.bot) return;
  const embed = new EmbedBuilder()
    .setTitle("Mensaje borrado")
    .setColor("#e74c3c")
    .setTimestamp()
    .addFields({ name: "Canal", value: `<#${msg.channelId}>`, inline: true });
  if (msg.partial) {
    embed.setDescription("Era de antes de que el bot lo viera: Discord no da su texto."); // el bot no lo vio llegar
  } else {
    embed.setDescription(recortar(msg.content)).addFields({ name: "Autor", value: `${msg.author}`, inline: true });
    if (msg.attachments.size) {
      embed.addFields({ name: "Adjuntos", value: recortar(msg.attachments.map((a) => a.name).join(", ")) });
    }
  }
  await registrar(msg.guild, embed);
});

client.on(Events.MessageUpdate, async (antes, despues) => {
  if (!despues.guild || !despues.author || despues.author.bot) return;
  // también salta cuando Discord añade la vista previa de un enlace: sin cambio de texto, nada
  if (!antes.partial && antes.content === despues.content) return;
  const embed = new EmbedBuilder()
    .setTitle("Mensaje editado")
    .setURL(despues.url)
    .setColor("#f1c40f")
    .setTimestamp()
    .addFields(
      { name: "Autor", value: `${despues.author}`, inline: true },
      { name: "Canal", value: `<#${despues.channelId}>`, inline: true },
      { name: "Antes", value: antes.partial ? "(el bot no lo recordaba)" : recortar(antes.content) },
      { name: "Después", value: recortar(despues.content) },
    );
  await registrar(despues.guild, embed);
});

client.on(Events.GuildMemberAdd, async (m) => {
  const embed = new EmbedBuilder()
    .setTitle("Ha entrado")
    .setDescription(`${m.user} (${m.user.tag})`)
    .setColor("#2ecc71")
    .setTimestamp()
    .addFields({ name: "Cuenta creada", value: `<t:${Math.floor(m.user.createdTimestamp / 1000)}:R>` });
  await registrar(m.guild, embed);
});

client.on(Events.GuildMemberRemove, async (m) => {
  const embed = new EmbedBuilder().setTitle("Se ha ido").setDescription(`${m.user} (${m.user.tag})`).setColor("#95a5a6").setTimestamp();
  await registrar(m.guild, embed);
});

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

client.login(process.env.DISCORD_TOKEN);

Lo que sale en el canal de registros

Cada cosa es un embed con su color: rojo, un mensaje borrado (canal, autor y texto); amarillo, uno editado (autor, canal, antes y después, y el título lleva al mensaje); verde, alguien que entra, con la antigüedad de su cuenta (una cuenta de hace unos minutos suele ser de usar y tirar); gris, alguien que se va.

El texto se corta a 1000 caracteres para no pasar del límite de 1024 de un campo de embed (todos los límites, en la guía de embeds). Los adjuntos se apuntan solo por su nombre: copiarlos obligaría al bot a descargar y volver a subir cada fichero que se manda.

No registra los mensajes de otros bots (ni los suyos) ni los borrados en bloque, como el de un comando de limpiar de un bot de moderación, que llegan en otro evento: on_raw_bulk_message_delete en discord.py y MessageBulkDelete en discord.js.

¿Y quién lo ha borrado?

El aviso de borrado no dice quién lo borró. Si fue su autor, Discord no lo apunta en ningún sitio. Si un moderador borró el mensaje de otro, sale en el «Registro de auditoría», y el bot puede leerlo con el permiso «Ver el registro de auditoría»: guild.audit_logs(action=discord.AuditLogAction.message_delete) en discord.py y guild.fetchAuditLogs({ type: AuditLogEvent.MessageDelete }) en discord.js.

No te fíes del todo: Discord junta varios borrados seguidos del mismo moderador en una sola entrada con un contador, así que no siempre casa con un mensaje concreto.

Avísalo en las normas

Un bot de registros guarda lo que la gente escribe aunque luego lo borre. Dilo en las normas del servidor (por ejemplo, «los mensajes borrados y editados quedan registrados para la moderación»), deja el canal de registros solo para quien modera y no lo uses para otra cosa.

Si además quieres avisos, silenciar y banear con comandos, el bot de moderación apunta cada acción en un canal de registros, que puede ser el mismo.

Tenerlo encendido siempre

Un bot de registros solo apunta lo que pasa mientras está encendido, y al reiniciarse olvida lo que tenía en memoria: lo que se borre con el bot apagado se pierde sin rastro. Por eso este, más que ningún otro, tiene que estar encendido día y noche, no en tu ordenador. Opciones gratis y de pago, comparadas, en dónde alojar tu bot de Discord.

En NapNodes hay un bot gratis de 256 MB que no se apaga por estar vacío; se renueva cada 7 días con «Encender» (sus pegas, en esta guía). Créalo, elige Node.js o Python en «Configuración» → «Lenguaje» y sube tus ficheros en «Archivos» (o pon tu repositorio en «Configuración» → «Repositorio de GitHub»); lo que escriba el bot lo ves en «Consola».

Preguntas frecuentes

¿Se pueden ver los mensajes borrados en Discord?

No: Discord no guarda los mensajes borrados ni deja verlos, tampoco a los administradores. Solo un bot que ya estaba en el servidor puede haberlos copiado antes de que se borraran.

¿Por qué mi bot registra el borrado pero sin el texto?

Porque el mensaje era de antes de que el bot lo viera, o de antes de su último reinicio. Discord no manda el texto al borrar: el bot solo puede sacarlo de su memoria.

¿Qué intents necesita un bot de registros?

Message Content, para leer el texto, y Server Members, para las entradas y salidas. Los dos se encienden en la pestaña «Bot» del portal de desarrolladores y se piden en el código.

¿Puede saber el bot quién borró un mensaje?

Solo si lo borró un moderador, y por el «Registro de auditoría» (con el permiso «Ver el registro de auditoría»). Si lo borra su autor, Discord no lo apunta.

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

Herramienta gratis: Intents de un bot de Discord. Marca lo que hace tu bot o busca el evento que no te llega: los intents, el código para discord.js o discord.py y qué activar en el portal.

Sigue leyendo

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