Proyecto: Bot de Telegram con Python - Guía Paso a Paso

👤 Admin 📅 25 de octubre, 2025 ⏱ 19 min 🏷 Proyectos Python

¿Qué es un Bot de Telegram?

Un bot de Telegram es un programa automatizado que puede interactuar con usuarios a través de la plataforma de Telegram. Puedes crear bots para automatizar tareas, proporcionar información, juegos y mucho más.

Crear tu Bot en Telegram

Paso 1: Hablar con BotFather

1. Abre Telegram y busca @BotFather
2. Envía el comando /newbot
3. Elige un nombre para tu bot
4. Elige un username (debe terminar en 'bot')
5. BotFather te dará un TOKEN - guárdalo en un lugar seguro

Instalación de la Biblioteca

pip install python-telegram-bot==20.7

Bot Básico - Hola Mundo

from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes

# Tu token del BotFather
TOKEN = 'TU_TOKEN_AQUI'

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Comando /start"""
    await update.message.reply_text('¡Hola! Soy tu bot de Telegram 🤖')

async def ayuda(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Comando /ayuda"""
    texto_ayuda = """
    Comandos disponibles:
    /start - Iniciar el bot
    /ayuda - Mostrar esta ayuda
    /info - Información del bot
    """
    await update.message.reply_text(texto_ayuda)

def main():
    # Crear aplicación
    app = Application.builder().token(TOKEN).build()
    
    # Agregar handlers
    app.add_handler(CommandHandler('start', start))
    app.add_handler(CommandHandler('ayuda', ayuda))
    
    # Iniciar bot
    print('Bot iniciado...')
    app.run_polling()

if __name__ == '__main__':
    main()

Manejo de Mensajes

from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes

TOKEN = 'TU_TOKEN_AQUI'

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    usuario = update.effective_user
    await update.message.reply_text(
        f'¡Hola {usuario.first_name}! 👋\n'
        f'Envíame cualquier mensaje y te responderé.'
    )

async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Repite el mensaje del usuario"""
    mensaje = update.message.text
    await update.message.reply_text(f'Dijiste: {mensaje}')

async def mayusculas(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Convierte texto a mayúsculas"""
    if context.args:
        texto = ' '.join(context.args)
        await update.message.reply_text(texto.upper())
    else:
        await update.message.reply_text('Uso: /mayusculas tu texto aquí')

def main():
    app = Application.builder().token(TOKEN).build()
    
    app.add_handler(CommandHandler('start', start))
    app.add_handler(CommandHandler('mayusculas', mayusculas))
    
    # Handler para mensajes de texto (no comandos)
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
    
    print('Bot iniciado...')
    app.run_polling()

if __name__ == '__main__':
    main()

Teclados Personalizados

Teclado de Respuesta (Reply Keyboard)

from telegram import Update, ReplyKeyboardMarkup, KeyboardButton
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes

async def menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Mostrar menú con teclado"""
    teclado = [
        [KeyboardButton('🎲 Dado'), KeyboardButton('💬 Chiste')],
        [KeyboardButton('📊 Estadísticas'), KeyboardButton('ℹ️ Info')],
        [KeyboardButton('🔙 Volver')]
    ]
    
    reply_markup = ReplyKeyboardMarkup(teclado, resize_keyboard=True)
    
    await update.message.reply_text(
        '🤖 Menú Principal\nElige una opción:',
        reply_markup=reply_markup
    )

async def manejar_botones(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Manejar presiones de botones"""
    texto = update.message.text
    
    if texto == '🎲 Dado':
        import random
        numero = random.randint(1, 6)
        await update.message.reply_text(f'🎲 Salió: {numero}')
    
    elif texto == '💬 Chiste':
        chistes = [
            '¿Por qué los programadores prefieren el modo oscuro? Porque la luz atrae bugs! 🐛',
            '¿Cuántos programadores se necesitan para cambiar una bombilla? Ninguno, es un problema de hardware. 💡'
        ]
        import random
        await update.message.reply_text(random.choice(chistes))
    
    elif texto == 'ℹ️ Info':
        await update.message.reply_text('Bot creado con Python 🐍')

def main():
    app = Application.builder().token(TOKEN).build()
    
    app.add_handler(CommandHandler('menu', menu))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, manejar_botones))
    
    app.run_polling()

if __name__ == '__main__':
    main()

Teclado Inline (Botones con Callbacks)

from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes

async def opciones(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Mostrar opciones con botones inline"""
    teclado = [
        [
            InlineKeyboardButton('✅ Opción 1', callback_data='opcion_1'),
            InlineKeyboardButton('✅ Opción 2', callback_data='opcion_2')
        ],
        [InlineKeyboardButton('❌ Cancelar', callback_data='cancelar')]
    ]
    
    reply_markup = InlineKeyboardMarkup(teclado)
    
    await update.message.reply_text(
        '¿Qué opción prefieres?',
        reply_markup=reply_markup
    )

async def boton_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Manejar callbacks de botones inline"""
    query = update.callback_query
    await query.answer()  # Responder al callback
    
    if query.data == 'opcion_1':
        await query.edit_message_text('Elegiste la Opción 1 ✅')
    elif query.data == 'opcion_2':
        await query.edit_message_text('Elegiste la Opción 2 ✅')
    elif query.data == 'cancelar':
        await query.edit_message_text('Operación cancelada ❌')

def main():
    app = Application.builder().token(TOKEN).build()
    
    app.add_handler(CommandHandler('opciones', opciones))
    app.add_handler(CallbackQueryHandler(boton_callback))
    
    app.run_polling()

if __name__ == '__main__':
    main()

Bot Completo con Múltiples Funciones

from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import (
    Application,
    CommandHandler,
    MessageHandler,
    CallbackQueryHandler,
    filters,
    ContextTypes
)
import random
from datetime import datetime

TOKEN = 'TU_TOKEN_AQUI'

# ===== COMANDOS =====

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    usuario = update.effective_user
    mensaje = f"""
    🤖 ¡Hola {usuario.first_name}!
    
    Bienvenido a ByteNinja Bot.
    
    Comandos disponibles:
    /ayuda - Ver todos los comandos
    /info - Información del usuario
    /dado - Tirar un dado
    /moneda - Lanzar una moneda
    /clima - Consultar clima
    /calculadora - Calculadora simple
    """
    await update.message.reply_text(mensaje)

async def info(update: Update, context: ContextTypes.DEFAULT_TYPE):
    usuario = update.effective_user
    info_texto = f"""
    📊 Tu Información:
    
    👤 Nombre: {usuario.first_name}
    🆔 ID: {usuario.id}
    📛 Username: @{usuario.username or 'No disponible'}
    🔗 Link: {usuario.link}
    """
    await update.message.reply_text(info_texto)

async def dado(update: Update, context: ContextTypes.DEFAULT_TYPE):
    numero = random.randint(1, 6)
    await update.message.reply_dice()
    await update.message.reply_text(f'🎲 Salió: {numero}')

async def moneda(update: Update, context: ContextTypes.DEFAULT_TYPE):
    resultado = random.choice(['Cara', 'Cruz'])
    emoji = '👑' if resultado == 'Cara' else '🪙'
    await update.message.reply_text(f'{emoji} Resultado: {resultado}')

async def calculadora(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args or len(context.args) < 3:
        await update.message.reply_text(
            'Uso: /calculadora numero operador numero\n'
            'Ejemplo: /calculadora 10 + 5\n'
            'Operadores: +, -, *, /'
        )
        return
    
    try:
        num1 = float(context.args[0])
        operador = context.args[1]
        num2 = float(context.args[2])
        
        if operador == '+':
            resultado = num1 + num2
        elif operador == '-':
            resultado = num1 - num2
        elif operador == '*':
            resultado = num1 * num2
        elif operador == '/':
            if num2 == 0:
                await update.message.reply_text('❌ No se puede dividir por cero')
                return
            resultado = num1 / num2
        else:
            await update.message.reply_text('❌ Operador inválido')
            return
        
        await update.message.reply_text(f'✅ Resultado: {resultado}')
    
    except ValueError:
        await update.message.reply_text('❌ Números inválidos')

async def recordatorio(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args:
        await update.message.reply_text(
            'Uso: /recordatorio segundos mensaje\n'
            'Ejemplo: /recordatorio 10 Recordatorio de prueba'
        )
        return
    
    try:
        segundos = int(context.args[0])
        mensaje = ' '.join(context.args[1:])
        
        await update.message.reply_text(
            f'⏰ Te recordaré en {segundos} segundos'
        )
        
        # Programar recordatorio
        context.job_queue.run_once(
            lambda context: context.bot.send_message(
                chat_id=update.effective_chat.id,
                text=f'🔔 Recordatorio: {mensaje}'
            ),
            segundos
        )
    
    except (ValueError, IndexError):
        await update.message.reply_text('❌ Formato inválido')

async def encuesta(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_poll(
        question='¿Cuál es tu lenguaje de programación favorito?',
        options=['Python 🐍', 'JavaScript 📜', 'Java ☕', 'C++ ⚙️'],
        is_anonymous=False,
        allows_multiple_answers=False
    )

# ===== MANEJO DE ARCHIVOS =====

async def manejar_foto(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text('📷 ¡Recibí tu foto! Gracias por compartir.')
    
    # Obtener info de la foto
    photo = update.message.photo[-1]  # Mejor calidad
    file_size = photo.file_size / 1024  # KB
    
    await update.message.reply_text(
        f'ℹ️ Tamaño del archivo: {file_size:.2f} KB'
    )

async def manejar_documento(update: Update, context: ContextTypes.DEFAULT_TYPE):
    documento = update.message.document
    await update.message.reply_text(
        f'📄 Recibí el documento: {documento.file_name}\n'
        f'📊 Tamaño: {documento.file_size / 1024:.2f} KB'
    )

# ===== JUEGOS =====

async def adivina_numero(update: Update, context: ContextTypes.DEFAULT_TYPE):
    numero_secreto = random.randint(1, 100)
    context.user_data['numero_secreto'] = numero_secreto
    context.user_data['intentos'] = 0
    
    await update.message.reply_text(
        '🎮 ¡Juego iniciado!\n'
        'Adivina el número entre 1 y 100\n'
        'Escribe tu intento:'
    )

async def verificar_numero(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if 'numero_secreto' not in context.user_data:
        return
    
    try:
        intento = int(update.message.text)
        context.user_data['intentos'] += 1
        numero_secreto = context.user_data['numero_secreto']
        
        if intento == numero_secreto:
            intentos = context.user_data['intentos']
            await update.message.reply_text(
                f'🎉 ¡Correcto! Era {numero_secreto}\n'
                f'Lo adivinaste en {intentos} intentos'
            )
            del context.user_data['numero_secreto']
            del context.user_data['intentos']
        
        elif intento < numero_secreto:
            await update.message.reply_text('📈 Más alto...')
        else:
            await update.message.reply_text('📉 Más bajo...')
    
    except ValueError:
        pass

# ===== MAIN =====

def main():
    app = Application.builder().token(TOKEN).build()
    
    # Comandos
    app.add_handler(CommandHandler('start', start))
    app.add_handler(CommandHandler('info', info))
    app.add_handler(CommandHandler('dado', dado))
    app.add_handler(CommandHandler('moneda', moneda))
    app.add_handler(CommandHandler('calculadora', calculadora))
    app.add_handler(CommandHandler('recordatorio', recordatorio))
    app.add_handler(CommandHandler('encuesta', encuesta))
    app.add_handler(CommandHandler('juego', adivina_numero))
    
    # Archivos
    app.add_handler(MessageHandler(filters.PHOTO, manejar_foto))
    app.add_handler(MessageHandler(filters.Document.ALL, manejar_documento))
    
    # Juego
    app.add_handler(MessageHandler(
        filters.TEXT & ~filters.COMMAND,
        verificar_numero
    ))
    
    print('🤖 Bot iniciado...')
    app.run_polling()

if __name__ == '__main__':
    main()

Configuración y Variables de Entorno

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

TOKEN = os.getenv('TELEGRAM_TOKEN')

if not TOKEN:
    raise ValueError('Token no encontrado en variables de entorno')
# .env
TELEGRAM_TOKEN=tu_token_aqui

Logging y Debugging

import logging

# Configurar logging
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)

logger = logging.getLogger(__name__)

# Usar en handlers
async def comando(update: Update, context: ContextTypes.DEFAULT_TYPE):
    logger.info(f'Usuario {update.effective_user.id} ejecutó comando')
    # ... resto del código

Deploy del Bot

Mantener Bot Corriendo (Linux)

# Usando screen
screen -S telegram_bot
python bot.py
# Ctrl+A+D para detach

# Volver a la sesión
screen -r telegram_bot

Systemd Service (Linux)

# /etc/systemd/system/telegram-bot.service
[Unit]
Description=Telegram Bot
After=network.target

[Service]
Type=simple
User=tu_usuario
WorkingDirectory=/ruta/a/tu/bot
ExecStart=/usr/bin/python3 /ruta/a/tu/bot/bot.py
Restart=always

[Install]
WantedBy=multi-user.target
# Iniciar servicio
sudo systemctl start telegram-bot
sudo systemctl enable telegram-bot
sudo systemctl status telegram-bot

Buenas Prácticas

  • Usa variables de entorno: No hardcodees tokens
  • Maneja errores: Try-except en todos los handlers
  • Logging: Registra eventos importantes
  • Rate limiting: Controla spam de usuarios
  • Valida input: Siempre verifica datos de usuario
  • Documentación: Incluye /ayuda con comandos
  • Feedback: Confirma acciones al usuario

Conclusión

Crear bots de Telegram con Python es simple y poderoso. Puedes automatizar tareas, crear juegos, proporcionar información y mucho más. La biblioteca python-telegram-bot hace que sea fácil manejar comandos, mensajes, teclados personalizados y archivos. Experimenta creando tu propio bot con funcionalidades únicas.