Base de datos para tu bot de Discord: guardar puntos, niveles y ajustes con SQLite
Tu bot cuenta puntos, guarda el canal de bienvenida o los avisos de cada usuario y, al reiniciarlo, todo vuelve a cero. Es porque lo guarda en memoria. Aquí va cómo guardarlo en un fichero que sobrevive a los reinicios, con SQLite, en Python y en JavaScript.
Por qué tu bot lo olvida todo al reiniciarse
Lo que guardas en una variable (un diccionario de puntos en Python, un objeto o un Map en JavaScript) vive en la memoria del programa. Cuando el bot se reinicia, porque lo apagas, porque se actualiza o porque se cae, el programa empieza de cero y esa memoria desaparece.
Para que algo dure hay que escribirlo en el disco: en un fichero que el bot lee al arrancar. Lo más sencillo que funciona bien es SQLite, una base de datos que es un solo fichero, sin instalar ni mantener ningún servidor aparte.
¿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.
JSON, SQLite o una base de datos externa
Un fichero JSON vale para unos pocos ajustes que casi nunca cambian (el prefijo, el canal de registros). Para datos que cambian a cada rato, como puntos o niveles, se queda corto: hay que reescribir el fichero entero en cada cambio, dos comandos a la vez pueden pisarse y, si el bot se corta justo mientras escribe, el fichero puede quedar a medias y no volver a abrirse.
SQLite guarda cada cambio de golpe o no lo guarda (transacciones), busca rápido aunque tengas miles de usuarios y viene con Python (el módulo sqlite3) y con Node.js 22.13 en adelante y 24 (el módulo node:sqlite). Para un bot que está en uno o en cientos de servidores de Discord, es lo que mejor encaja.
Una base de datos externa (MongoDB, PostgreSQL, MySQL en otro servicio) tiene sentido cuando varios programas tienen que leer y escribir lo mismo a la vez, por ejemplo tu bot y una web con el ranking. A cambio, es otra cuenta, otra contraseña que guardar y cada consulta viaja por internet.
En Python, con sqlite3
No hay que instalar nada: sqlite3 viene con Python. Este bot tiene dos comandos, /punto, que te suma un punto, y /puntos, que te dice cuántos llevas. Apágalo y vuelve a encenderlo: los puntos siguen ahí. El requirements.txt y el .env son los de tu primer bot en Python.
sqlite3 es síncrono: mientras hace una consulta, el bot espera. Con consultas pequeñas como estas no se nota; si un día haces consultas pesadas, la librería aiosqlite hace lo mismo sin pararlo.
import os
import sqlite3
from pathlib import Path
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv()
# El fichero, junto a app.py: así da igual desde qué carpeta se arranque.
RUTA = Path(__file__).with_name("datos.db")
db = sqlite3.connect(RUTA)
db.execute("CREATE TABLE IF NOT EXISTS puntos (usuario INTEGER PRIMARY KEY, total INTEGER NOT NULL DEFAULT 0)")
db.commit()
client = discord.Client(intents=discord.Intents.default())
tree = app_commands.CommandTree(client)
@tree.command(name="punto", description="Te suma un punto")
async def punto(interaction: discord.Interaction):
with db: # guarda al salir del bloque; si algo falla, no guarda nada a medias
db.execute(
"INSERT INTO puntos (usuario, total) VALUES (?, 1) "
"ON CONFLICT(usuario) DO UPDATE SET total = total + 1",
(interaction.user.id,),
)
await interaction.response.send_message("➕ Un punto más")
@tree.command(name="puntos", description="Cuántos puntos tienes")
async def puntos(interaction: discord.Interaction):
fila = db.execute("SELECT total FROM puntos WHERE usuario = ?", (interaction.user.id,)).fetchone()
await interaction.response.send_message(f"Tienes {fila[0] if fila else 0} puntos")
@client.event
async def on_ready():
await tree.sync()
print(f"Conectado como {client.user}")
client.run(os.environ["DISCORD_TOKEN"])En JavaScript, con node:sqlite
Desde Node.js 22.13, y en Node.js 24, SQLite viene incluido como node:sqlite, sin instalar nada. Al arrancar, la consola dice que es una función experimental (ExperimentalWarning): es normal y el bot funciona igual. El package.json y el .env son los de tu primer bot en JavaScript.
Guarda el ID de cada usuario como texto: los IDs de Discord son más grandes de lo que JavaScript puede guardar como número sin redondear, y discord.js ya te los da como texto.
¿Tienes que seguir en Node.js 20? Ahí no está node:sqlite: añade la librería better-sqlite3 a package.json; su forma de usarse (prepare, run, get) es casi igual que la de este ejemplo.
require("dotenv").config();
const path = require("node:path");
const { DatabaseSync } = require("node:sqlite"); // viene con Node.js 22.13+ y 24
const { Client, Events, GatewayIntentBits, SlashCommandBuilder } = require("discord.js");
// El fichero, junto a index.js: así da igual desde qué carpeta se arranque.
const db = new DatabaseSync(path.join(__dirname, "datos.db"));
db.exec("CREATE TABLE IF NOT EXISTS puntos (usuario TEXT PRIMARY KEY, total INTEGER NOT NULL DEFAULT 0)");
const sumar = db.prepare(
"INSERT INTO puntos (usuario, total) VALUES (?, 1) ON CONFLICT(usuario) DO UPDATE SET total = total + 1",
);
const leer = db.prepare("SELECT total FROM puntos WHERE usuario = ?");
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const comandos = [
new SlashCommandBuilder().setName("punto").setDescription("Te suma un punto"),
new SlashCommandBuilder().setName("puntos").setDescription("Cuántos puntos tienes"),
];
client.once(Events.ClientReady, async (c) => {
await c.application.commands.set(comandos.map((x) => x.toJSON()));
console.log(`Conectado como ${c.user.tag}`);
});
client.on(Events.InteractionCreate, async (i) => {
if (!i.isChatInputCommand()) return;
if (i.commandName === "punto") {
sumar.run(i.user.id); // el ID como texto: los de Discord no caben en un número de JavaScript
await i.reply("➕ Un punto más");
}
if (i.commandName === "puntos") {
const fila = leer.get(i.user.id);
await i.reply(`Tienes ${fila ? fila.total : 0} puntos`);
}
});
client.login(process.env.DISCORD_TOKEN);Dónde va el fichero, y qué no subir a GitHub
Pon la base de datos junto al código, con la ruta sacada de la carpeta del propio programa (Path(__file__) en Python, __dirname en JavaScript), como en los ejemplos. Con una ruta suelta como «datos.db», el fichero se crea en la carpeta desde la que se arranca el bot, y si un día cambia, el bot empieza con una base de datos nueva y vacía y parece que lo ha perdido todo.
Si tu código está en GitHub, añade datos.db a .gitignore (y también el .env). La base de datos no es código: tiene los IDs de tus usuarios y cambia sola cada vez que alguien usa un comando. Si está en el repositorio, cada actualización del código puede chocar con la que tiene el bot o pisarla.
.env
datos.db
datos.db-*Copias de la base de datos
Copiar el fichero con el bot encendido puede darte una copia a medias si justo estaba escribiendo. O apaga el bot antes de copiarlo, o haz la copia desde el propio programa: en Python, con backup(), que copia bien aunque el bot esté escribiendo; en SQL, con «VACUUM INTO 'copia.db'», que vale también en node:sqlite.
Si ves junto a tu base de datos ficheros que terminan en -wal y -shm, es que usa el modo WAL: forman parte de ella y, si copias a mano, se copian los tres juntos.
import sqlite3
# Copia buena aunque el bot esté encendido y escribiendo.
origen = sqlite3.connect("datos.db")
destino = sqlite3.connect("copia.db")
with destino:
origen.backup(destino)
destino.close()
origen.close()Si tu bot está en NapNodes
El fichero se queda en el disco del bot
Lo que tu bot escribe en su carpeta sigue ahí al apagarlo, al reiniciarlo y al actualizar el código: lo ves en «Archivos». Para una base de datos de un bot sobra disco: el bot gratis tiene 1 GB, el Mini 2,5 GB y el Plus 5 GB, y una base de datos de puntos de miles de usuarios no llega a unos pocos megas.
Si traes el código de GitHub
Con la actualización en cada reinicio de «Configuración», el panel trae los cambios de tu repositorio. Con datos.db en .gitignore, como arriba, tu base de datos no se toca.
Copias
Con un plan, en «Copias de seguridad» haces copias de todo el bot (la base de datos incluida) y se hace sola una «Copia diaria automática» cada madrugada. El bot gratis no tiene copias: apágalo y descarga datos.db desde «Archivos» de vez en cuando.
Antes de reinstalar o de borrar
Descarga datos.db desde «Archivos» antes de «Reinstalar el servidor» (al final de «Configuración») o de borrar el bot. Y si tu bot es el gratis, renuévalo cada semana: sin renovar se apaga, y a los 14 días sin renovar se borra con todos sus ficheros (te avisamos antes).
Guarda solo lo que el bot necesita
Para puntos, niveles o avisos basta con el ID del usuario y del servidor de Discord: no hace falta guardar sus mensajes, ni su nombre (cambia), ni nada más. Cuanto menos guardes, menos tienes que proteger. Y recopilar datos de usuarios para otra cosa que no sea lo que hace tu bot va contra las normas de Discord y contra nuestra Política de Uso Aceptable.
Preguntas frecuentes
¿JSON o SQLite para un bot de Discord?
JSON para unos pocos ajustes que casi no cambian; SQLite para todo lo que cambia a menudo, como puntos, niveles o avisos. SQLite no se queda a medias si el bot se corta mientras escribe y viene con Python y con Node.js 22.13 en adelante.
¿Por qué mi bot pierde los datos al reiniciarse?
Porque los guarda en variables, que viven en memoria y se borran al reiniciar. Hay que escribirlos en un fichero, por ejemplo una base de datos SQLite junto al código.
¿Se borra la base de datos de mi bot al reiniciarlo en NapNodes?
No: los ficheros que el bot escribe en su carpeta se quedan en su disco y los ves en «Archivos». Antes de «Reinstalar el servidor» o de borrar el bot, descárgala.
Me sale «database is locked», ¿qué hago?
Otro programa tiene abierto el mismo fichero y está escribiendo. Lo más habitual es tener el bot encendido dos veces (en tu ordenador y en el hosting a la vez), o una transacción que no se cierra nunca: en Python, guarda con «with db:» o con commit() después de cada cambio.
¿Puedo usar MongoDB con mi bot?
Sí, si tu bot puede conectarse a ese servicio por internet. Compensa cuando varios programas tienen que compartir los datos; para un bot solo, SQLite es más sencillo y no depende de otra cuenta.
¿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.
Sigue leyendo
- Bot de niveles para Discord: XP por mensajes, ranking y roles (Python y JavaScript)
Haz tu propio bot de niveles de Discord en Python o JavaScript: XP por mensajes contra el spam, /nivel, /ranking y roles al subir, guardado en SQLite.
- Bot de roles para Discord: botones para que cada uno elija sus roles (Python y JavaScript)
Haz tu bot de autoroles para Discord en Python o JavaScript: un mensaje con botones para ponerse y quitarse roles, sin base de datos y sin roles por reacción.
- Bot de verificación para Discord en Python: un botón y una pregunta antes de entrar
Haz tu bot de verificación de Discord con discord.py: botón «Verificarme», una pregunta contra bots y un rol que abre el servidor. Código completo y gratis.