Bot de Discord en TypeScript (discord.js): de cero a encendido siempre
TypeScript es JavaScript con tipos: te avisa antes de arrancar de que «caras» puede venir vacío o de que has escrito mal un nombre, en vez de enterarte cuando el bot ya ha fallado en tu servidor. discord.js trae sus tipos de serie. Este bot tiene /hola y un /dado con opción, y al final está cómo dejarlo encendido sin tu ordenador.
1. Crea el bot en Discord
Crea la aplicación
Entra en discord.com/developers/applications, pulsa «New Application» y ponle nombre. Es tu bot.
Saca el token
En la pestaña «Bot», pulsa «Reset Token» y copia el token. Es la contraseña del bot: quien lo tenga lo controla, así que no lo compartas ni lo subas a GitHub.
Invítalo a tu servidor
En «OAuth2» → «URL Generator», marca «bot» y «applications.commands», elige los permisos (para empezar, «Send Messages») y abre el enlace que sale. Si quieres ver en castellano qué deja hacer cada permiso, saca el enlace con la calculadora de permisos de bot.
¿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.
2. El proyecto: package.json y tsconfig.json
Con Node.js 20 o más nuevo, crea una carpeta con estos dos ficheros y una subcarpeta src. En dependencies va lo que usa el bot al funcionar (discord.js y dotenv); en devDependencies, lo que solo hace falta para programarlo: TypeScript (el compilador, tsc), los tipos de Node.js y tsx, que ejecuta el .ts directamente mientras lo pruebas. Después, «npm install».
El tsconfig.json dice que el código está en src y que el JavaScript compilado va a dist. «strict» enciende todas las comprobaciones (es lo que hace útil TypeScript) y «skipLibCheck» se salta revisar los tipos de las librerías, que es lo que más tarda.
{
"name": "mi-bot",
"version": "1.0.0",
"private": true,
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts"
},
"dependencies": {
"discord.js": "^14.16.0",
"dotenv": "^16.4.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"tsx": "^4.19.0",
"typescript": "^5.6.0"
}
}{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"skipLibCheck": true
},
"include": ["src"]
}DISCORD_TOKEN=pega-aquí-tu-token3. El código, con tipos
Es el mismo bot que el de JavaScript, con dos diferencias que se notan: si falta el token, se para con un mensaje claro en vez de un error raro de Discord; y getInteger devuelve number | null, así que TypeScript no te deja usar «caras» sin decir qué pasa cuando nadie la escribe (el «?? 6»).
El ChatInputCommandInteraction de la función dado es el tipo de un comando con barra: el editor te enseña todo lo que tiene (options, user, reply…) mientras escribes.
import "dotenv/config"; // lee el token del fichero .env
import { Client, Events, GatewayIntentBits, SlashCommandBuilder, type ChatInputCommandInteraction } from "discord.js";
const token = process.env.DISCORD_TOKEN;
if (!token) throw new Error("Falta DISCORD_TOKEN en el fichero .env");
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const comandos = [
new SlashCommandBuilder().setName("hola").setDescription("Te saluda"),
new SlashCommandBuilder()
.setName("dado")
.setDescription("Tira un dado")
.addIntegerOption((o) => o.setName("caras").setDescription("Cuántas caras (6 si no dices nada)").setMinValue(2).setMaxValue(100)),
];
async function dado(i: ChatInputCommandInteraction): Promise<void> {
const caras = i.options.getInteger("caras") ?? 6; // number | null: TypeScript te obliga a pensar en el «no ha dicho nada»
await i.reply(`🎲 Ha salido un ${1 + Math.floor(Math.random() * caras)} (dado de ${caras})`);
}
client.once(Events.ClientReady, async (c) => {
await c.application.commands.set(comandos.map((x) => x.toJSON())); // publica /hola y /dado
console.log(`Conectado como ${c.user.tag}`);
});
client.on(Events.InteractionCreate, async (i) => {
if (!i.isChatInputCommand()) return;
if (i.commandName === "hola") await i.reply(`¡Hola, ${i.user}!`);
if (i.commandName === "dado") await dado(i);
});
client.login(token);4. Pruébalo y compílalo
Para probarlo mientras lo escribes: «npm run dev». tsx lo arranca sin compilar y lo reinicia solo cada vez que guardas. Cuando diga «Conectado como…», escribe /hola o /dado en tu servidor (los comandos con barra pueden tardar unos minutos en salir la primera vez; si no salen, qué mirar).
Para dejarlo listo: «npm run build». tsc revisa los tipos (si algo no cuadra, dice el fichero y la línea, y no compila) y escribe el JavaScript en dist/index.js. Eso es lo que se ejecuta de verdad, con «npm start».
5. Déjalo encendido siempre (gratis)
Mientras el programa esté abierto en tu ordenador, el bot funciona; al apagarlo, se desconecta. Para tenerlo siempre encendido hace falta alojarlo en un servidor, y ahí lo fiable es ejecutar el JavaScript ya compilado, no el .ts.
Por qué compilar en tu ordenador: tsc gasta memoria. Compilando este mismo bot, en nuestra prueba tsc llegó a unos 250 MB, casi todo lo que tiene el bot gratis (256 MB). Compilar en cada arranque, con tsc o con ts-node, lo deja al límite y hace que tarde más en encender; el bot ya compilado gasta mucho menos.
Compila
«npm run build» en tu ordenador. Te queda dist/index.js.
Crea el bot y sube los ficheros
Crea el bot gratis en NapNodes (nace en Node.js, no hay que cambiar el lenguaje). En «Archivos», sube package.json y .env; con «Nueva carpeta» crea dist y sube dentro index.js. La carpeta node_modules no se sube: las librerías de package.json se instalan solas al arrancar.
Cambia el fichero de inicio
En «Configuración», en «Fichero de inicio», pon dist/index.js en lugar de index.js. Así se lanza con Node.js tal cual: si pones un fichero acabado en .ts, el arranque intenta lanzarlo con ts-node, que es justo lo que queremos evitar.
Enciende
Pulsa «Encender». La «Consola» enseña lo que escribe tu bot: tiene que salir «Conectado como…». Si se conecta pero no contesta, qué mirar según el caso.
Si tu código está en GitHub
Pon la dirección del repositorio en «Configuración» → «Repositorio de GitHub» y se baja solo; con «Bajar los cambios del repositorio al encender», cada reinicio trae lo último. El .env no va en el repositorio (añádelo a .gitignore): súbelo aparte en «Archivos».
Como el servidor no compila, el repositorio tiene que llevar dist ya compilado: quita dist del .gitignore y haz «npm run build» antes de cada commit. Si prefieres que se compile en el servidor, añade "prepare": "tsc" a los scripts de package.json (npm lo ejecuta tras instalar las librerías, en cada arranque), pero hazlo en un plan con más memoria que el gratis, por lo que gasta tsc.
Cuánta memoria necesita
Un bot de comandos como este, ya compilado, va de sobra en el bot gratis de 256 MB, que solo hay que renovar con un clic cada semana. Si crece (muchos servidores, caché grande, música) o no quieres acordarte de renovarlo, el plan Mini tiene 512 MB y el Plus 1 GB, encendidos siempre sin renovar nada (precios de los bots); la calculadora de RAM te dice cuál.
Preguntas frecuentes
¿Se puede hacer un bot de Discord en TypeScript?
Sí. discord.js está escrito pensando en TypeScript y trae sus tipos, así que solo hace falta instalar typescript y @types/node, un tsconfig.json y compilar con tsc. El bot que se ejecuta es el JavaScript compilado.
¿ts-node o compilar con tsc?
Para programar, tsx o ts-node son cómodos porque ejecutan el .ts sin compilar. Para tenerlo alojado, compila con tsc y ejecuta el JavaScript: arranca antes y gasta menos memoria, porque no compila en cada arranque.
¿Por qué mi bot en TypeScript no arranca en el hosting?
Casi siempre porque el hosting intenta ejecutar el .ts o un dist que no existe. Compila con «npm run build», sube dist/index.js y pon ese fichero como fichero de inicio. Si la consola dice «Falta DISCORD_TOKEN», falta el .env.
¿Qué diferencia hay entre dependencies y devDependencies?
En dependencies va lo que el bot necesita para funcionar (discord.js, dotenv); en devDependencies, lo que solo usas para programarlo (typescript, tsx, los tipos). El bot compilado no necesita TypeScript para funcionar.
¿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 Discord encendido siempre y gratis: alternativas a Replit
Por qué tu bot de Discord se apaga en Replit o Render, por qué el truco del keep_alive falla y las opciones gratis que sí lo dejan encendido.
- Hosting para tu bot de Discord: encendido siempre, gratis o de pago
Cómo tener tu bot de Discord encendido las 24 horas: Node.js o Python, subir el código o traerlo de GitHub, dónde poner el token y cuánta memoria necesita.
- Comandos con barra en tu bot de Discord: opciones, subcomandos y permisos
Comandos con barra (slash commands) en discord.js y discord.py: opciones, subcomandos, permisos, respuestas que tardan, autocompletado y por qué no salen.