Saltar al contenido principal

Crear un Bot de Telegram con IA en Node.js (Tutorial Completo 2026)

Crea un bot de Telegram con inteligencia artificial usando Node.js, la API de OpenAI o Claude y grammy. Respuestas automáticas, comandos, memoria de conversación y deploy gratuito.

Fran Cobos 8 min de lectura 1575 palabras

Tabla de contenidos

Los bots de Telegram son sorprendentemente útiles: asistente personal, bot de soporte para tu producto, automatizaciones… Y con IA integrada son mucho más potentes que los bots de comandos de antes.

En este tutorial construyes un bot completo desde cero en menos de una hora.

Lo que vas a construir

  • Bot de Telegram que responde a mensajes con IA (OpenAI o Claude)
  • Memoria de conversación por usuario (recuerda el contexto)
  • Comandos: /start, /clear (limpiar historial), /help
  • Límite de mensajes para no arruinarte con la API
  • Deploy gratuito en Railway

Requisitos previos

  • Node.js 18+ instalado
  • Cuenta en Telegram
  • API Key de OpenAI o Anthropic (o DeepSeek que es más barata)

Paso 1: Crear el bot en Telegram

Abre Telegram y habla con @BotFather:

/newbot

BotFather te pedirá:

  1. Un nombre para el bot (ej: “Mi Asistente IA”)
  2. Un username (debe terminar en bot, ej: mi_asistente_ia_bot)

Te dará un token como este: 7291834756:AAFxyz123...

Guárdalo, es tu clave para controlar el bot.


Paso 2: Configurar el proyecto

mkdir telegram-bot-ia
cd telegram-bot-ia
npm init -y
npm install grammy openai dotenv

grammy es la mejor librería para bots de Telegram en 2026 (más moderno que node-telegram-bot-api).

Crea la estructura:

telegram-bot-ia/
├── src/
│   ├── bot.js
│   ├── ai.js
│   └── memoria.js
├── .env
├── .env.example
└── package.json

Crea el .env:

# .env
TELEGRAM_TOKEN=7291834756:AAFxyz123...
OPENAI_API_KEY=sk-...

# Opcional: limitar uso
MAX_MENSAJES_POR_USUARIO=50
MAX_TOKENS_RESPUESTA=500

Crea el .env.example (para el repo, sin secrets):

TELEGRAM_TOKEN=tu_token_aqui
OPENAI_API_KEY=tu_api_key_aqui

Añade al .gitignore:

node_modules/
.env

Paso 3: El módulo de IA

// src/ai.js
import OpenAI from 'openai'

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
})

const SISTEMA_PROMPT = `Eres un asistente útil y conciso. 
Respondes siempre en español.
Tus respuestas son directas y al punto.
Si no sabes algo, lo dices honestamente.`

/**
 * @param {Array<{role: string, content: string}>} historial
 * @returns {Promise<string>}
 */
export async function preguntarIA(historial) {
  const mensajes = [
    { role: 'system', content: SISTEMA_PROMPT },
    ...historial
  ]

  const respuesta = await openai.chat.completions.create({
    model: 'gpt-4o-mini',  // barato y rápido
    messages: mensajes,
    max_tokens: parseInt(process.env.MAX_TOKENS_RESPUESTA || '500'),
    temperature: 0.7,
  })

  return respuesta.choices[0].message.content
}

Si prefieres usar Claude (Anthropic):

// src/ai.js (versión Claude)
import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY
})

export async function preguntarIA(historial) {
  const mensajes = historial.map(m => ({
    role: m.role === 'user' ? 'user' : 'assistant',
    content: m.content
  }))

  const respuesta = await client.messages.create({
    model: 'claude-haiku-4-5',  // el más barato de Claude
    max_tokens: parseInt(process.env.MAX_TOKENS_RESPUESTA || '500'),
    system: SISTEMA_PROMPT,
    messages: mensajes,
  })

  return respuesta.content[0].text
}

O DeepSeek (compatible con la API de OpenAI):

// src/ai.js (versión DeepSeek — más barata)
import OpenAI from 'openai'

const openai = new OpenAI({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: 'https://api.deepseek.com'
})

export async function preguntarIA(historial) {
  const mensajes = [
    { role: 'system', content: SISTEMA_PROMPT },
    ...historial
  ]

  const respuesta = await openai.chat.completions.create({
    model: 'deepseek-chat',
    messages: mensajes,
    max_tokens: 500,
  })

  return respuesta.choices[0].message.content
}

Paso 4: La memoria de conversación

// src/memoria.js

// Almacena el historial por usuario en memoria RAM
// Se pierde al reiniciar, pero es suficiente para empezar
const historiales = new Map()

const MAX_MENSAJES = 20 // Mantener solo los últimos 20 mensajes por usuario

/**
 * Obtiene el historial de un usuario
 * @param {number} userId
 * @returns {Array<{role: string, content: string}>}
 */
export function obtenerHistorial(userId) {
  if (!historiales.has(userId)) {
    historiales.set(userId, [])
  }
  return historiales.get(userId)
}

/**
 * Añade un mensaje al historial
 * @param {number} userId 
 * @param {'user' | 'assistant'} rol 
 * @param {string} contenido 
 */
export function añadirMensaje(userId, rol, contenido) {
  const historial = obtenerHistorial(userId)
  historial.push({ role: rol, content: contenido })
  
  // Mantener solo los últimos N mensajes (ventana deslizante)
  if (historial.length > MAX_MENSAJES) {
    historial.splice(0, historial.length - MAX_MENSAJES)
  }
}

/**
 * Limpia el historial de un usuario
 * @param {number} userId
 */
export function limpiarHistorial(userId) {
  historiales.set(userId, [])
}

/**
 * Cuenta los mensajes del usuario en las últimas 24h
 * Para limitar uso — versión simple en memoria
 */
const contadoresDiarios = new Map()

export function puedeEnviarMensaje(userId) {
  const max = parseInt(process.env.MAX_MENSAJES_POR_USUARIO || '50')
  const ahora = Date.now()
  const dia = 24 * 60 * 60 * 1000
  
  if (!contadoresDiarios.has(userId)) {
    contadoresDiarios.set(userId, { count: 0, resetAt: ahora + dia })
  }
  
  const contador = contadoresDiarios.get(userId)
  
  // Resetear si pasó un día
  if (ahora > contador.resetAt) {
    contador.count = 0
    contador.resetAt = ahora + dia
  }
  
  if (contador.count >= max) return false
  
  contador.count++
  return true
}

Paso 5: El bot principal

// src/bot.js
import 'dotenv/config'
import { Bot } from 'grammy'
import { preguntarIA } from './ai.js'
import { obtenerHistorial, añadirMensaje, limpiarHistorial, puedeEnviarMensaje } from './memoria.js'

const bot = new Bot(process.env.TELEGRAM_TOKEN)

// /start — Mensaje de bienvenida
bot.command('start', async (ctx) => {
  const nombre = ctx.from?.first_name || 'amigo'
  await ctx.reply(
    `¡Hola, ${nombre}! 👋\n\n` +
    `Soy un asistente con IA. Escríbeme cualquier cosa y te respondo.\n\n` +
    `Comandos disponibles:\n` +
    `/clear — Borrar historial de conversación\n` +
    `/help — Ver esta ayuda`
  )
})

// /help — Ayuda
bot.command('help', async (ctx) => {
  await ctx.reply(
    `*Cómo usarme:*\n\n` +
    `Escríbeme cualquier mensaje y te respondo con IA.\n` +
    `Recuerdo el contexto de nuestra conversación.\n\n` +
    `*Comandos:*\n` +
    `/clear — Borra nuestra conversación y empezamos de cero\n` +
    `/start — Mensaje de bienvenida`,
    { parse_mode: 'Markdown' }
  )
})

// /clear — Limpiar historial
bot.command('clear', async (ctx) => {
  limpiarHistorial(ctx.from.id)
  await ctx.reply('Historial borrado. ¡Empecemos de cero! 🧹')
})

// Mensajes de texto — la lógica principal
bot.on('message:text', async (ctx) => {
  const userId = ctx.from.id
  const textoUsuario = ctx.message.text

  // Ignorar comandos que no manejamos
  if (textoUsuario.startsWith('/')) return

  // Verificar límite de mensajes
  if (!puedeEnviarMensaje(userId)) {
    await ctx.reply(
      '⚠️ Has alcanzado el límite diario de mensajes. Vuelve mañana.'
    )
    return
  }

  // Mostrar "escribiendo..." mientras procesa
  await ctx.replyWithChatAction('typing')

  try {
    // Añadir mensaje del usuario al historial
    añadirMensaje(userId, 'user', textoUsuario)
    
    // Obtener historial completo y preguntar a la IA
    const historial = obtenerHistorial(userId)
    const respuesta = await preguntarIA(historial)
    
    // Guardar respuesta en historial
    añadirMensaje(userId, 'assistant', respuesta)
    
    // Enviar respuesta
    await ctx.reply(respuesta, { parse_mode: 'Markdown' })
    
  } catch (error) {
    console.error('Error al llamar a la IA:', error)
    
    // Quitar el último mensaje del historial si falló
    const historial = obtenerHistorial(userId)
    historial.pop()
    
    await ctx.reply(
      '❌ Hubo un error al procesar tu mensaje. Inténtalo de nuevo.'
    )
  }
})

// Manejar errores del bot
bot.catch((err) => {
  console.error('Error en el bot:', err)
})

// Arrancar el bot
bot.start()
console.log('🤖 Bot iniciado correctamente')

Añade el type: module al package.json:

{
  "name": "telegram-bot-ia",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node src/bot.js",
    "dev": "node --watch src/bot.js"
  },
  "dependencies": {
    "dotenv": "^16.0.0",
    "grammy": "^1.30.0",
    "openai": "^4.0.0"
  }
}

Paso 6: Probarlo localmente

node src/bot.js
# 🤖 Bot iniciado correctamente

Abre Telegram, busca tu bot por el username y escríbele. Debería responder con IA.


Paso 7: Añadir funcionalidades extra

Comandos personalizados

// Añade esto en bot.js — ejemplo: /resume que resume texto
bot.command('resume', async (ctx) => {
  const texto = ctx.message.text.replace('/resume', '').trim()
  
  if (!texto) {
    await ctx.reply('Uso: /resume [texto a resumir]')
    return
  }
  
  await ctx.replyWithChatAction('typing')
  
  // Usar la IA con un prompt específico (sin historial)
  const respuesta = await preguntarIA([
    { role: 'user', content: `Resume esto en 3 puntos clave: ${texto}` }
  ])
  
  await ctx.reply(`📝 *Resumen:*\n\n${respuesta}`, { parse_mode: 'Markdown' })
})

Responder a imágenes (Vision)

bot.on('message:photo', async (ctx) => {
  await ctx.replyWithChatAction('typing')
  
  // Obtener la foto en mayor resolución
  const foto = ctx.message.photo.at(-1)
  const file = await ctx.api.getFile(foto.file_id)
  const fotoUrl = `https://api.telegram.org/file/bot${process.env.TELEGRAM_TOKEN}/${file.file_path}`
  
  const caption = ctx.message.caption || '¿Qué hay en esta imagen?'
  
  const respuesta = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{
      role: 'user',
      content: [
        { type: 'text', text: caption },
        { type: 'image_url', image_url: { url: fotoUrl } }
      ]
    }],
    max_tokens: 500
  })
  
  await ctx.reply(respuesta.choices[0].message.content)
})

Paso 8: Deploy gratuito en Railway

  1. Sube el código a GitHub (sin el .env)

  2. Ve a railway.app y crea una cuenta

  3. Crea un nuevo proyecto → “Deploy from GitHub repo”

  4. Selecciona tu repositorio

  5. En Variables, añade:

    • TELEGRAM_TOKEN = tu token
    • OPENAI_API_KEY = tu API key
  6. Railway detecta automáticamente Node.js y ejecuta npm start

Tu bot estará online 24/7 de forma gratuita (Railway da $5/mes gratis, suficiente para un bot pequeño).

Alternativas gratuitas:

  • Render: plan gratuito, se duerme tras 15min de inactividad (el bot no nota esto porque usa polling)
  • Fly.io: 3 máquinas gratuitas para siempre
  • Hetzner VPS: €4/mes, total control, no hay gratis pero es lo más barato de pago

Resumen del código final

telegram-bot-ia/
├── src/
│   ├── bot.js        ← Lógica del bot y comandos
│   ├── ai.js         ← Llamadas a OpenAI/Claude/DeepSeek
│   └── memoria.js    ← Historial por usuario + límites
├── .env              ← Secrets (no subir a git)
├── .env.example      ← Template para el repo
├── .gitignore
└── package.json

Con menos de 200 líneas de código tienes un bot de Telegram con IA, memoria de conversación y límites de uso. A partir de aquí puedes añadir:

  • Base de datos con SQLite para persistir el historial
  • Pagos con Stripe para cobrar por el acceso
  • Webhooks en vez de polling para mayor eficiencia
  • Comandos adicionales según tu caso de uso

El código completo está pensado para ser el punto de partida, no el destino final.

Fran Cobos

Fran Cobos

Desarrollador Full Stack especializado en IA aplicada, automatización y desarrollo web. Escribo sobre herramientas, tutoriales y casos reales para programadores.

¿Necesitas desarrollo a medida?

Apps web, IA, módulos ERP — cuéntame tu proyecto.