Cap 2: Entorno de Desarrollo
Requisitos previos
- Python 3.11+
- Token de BotFather (del capítulo anterior)
- API key de Anthropic — obtenerla en console.anthropic.com
Estructura del proyecto
telegram-claude-bot/
├── bot.py # Punto de entrada
├── handlers/
│ ├── __init__.py
│ ├── commands.py # /start, /help, /reset
│ └── messages.py # Mensajes de texto
├── services/
│ ├── __init__.py
│ ├── claude_api.py # Integración Anthropic API
│ └── claude_code.py # Integración Claude Code CLI
├── storage/
│ ├── __init__.py
│ └── history.py # Historial de conversaciones
├── .env # Variables de entorno (no commitear)
├── .env.example # Plantilla pública
└── requirements.txt
Crear entorno virtual
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
pip install --upgrade pip
Instalar dependencias
pip install \
python-telegram-bot[job-queue]==21.* \
anthropic \
python-dotenv
Guardar en requirements.txt:
python-telegram-bot[job-queue]==21.9.0
anthropic>=0.40.0
python-dotenv>=1.0.0
Variables de entorno
Crea .env en la raíz del proyecto:
# .env — NUNCA commitear este archivo
TELEGRAM_BOT_TOKEN=7123456789:AAH...
ANTHROPIC_API_KEY=sk-ant-...
# Opcional: restringir acceso a usuario específico
ALLOWED_USER_ID=123456789
# Modelo de Claude a usar
CLAUDE_MODEL=claude-opus-4-5-20251001
Crea .env.example para el repositorio:
# .env.example
TELEGRAM_BOT_TOKEN=
ANTHROPIC_API_KEY=
ALLOWED_USER_ID=
CLAUDE_MODEL=claude-opus-4-5-20251001
Agrega .env al .gitignore:
echo ".env" >> .gitignore
Cargar configuración
Crea config.py:
import os
from dotenv import load_dotenv
load_dotenv()
TELEGRAM_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
ALLOWED_USER_ID = int(os.getenv("ALLOWED_USER_ID", "0")) or None
CLAUDE_MODEL = os.getenv("CLAUDE_MODEL", "claude-opus-4-5-20251001")
El or None hace que ALLOWED_USER_ID=0 signifique “sin restricción”.
Verificar instalación
Crea test_setup.py para validar que todo funciona:
import asyncio
from telegram import Bot
import anthropic
from config import TELEGRAM_TOKEN, ANTHROPIC_API_KEY, CLAUDE_MODEL
async def test_telegram():
bot = Bot(TELEGRAM_TOKEN)
me = await bot.get_me()
print(f"✅ Telegram OK: @{me.username}")
def test_anthropic():
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
msg = client.messages.create(
model=CLAUDE_MODEL,
max_tokens=10,
messages=[{"role": "user", "content": "di 'ok'"}]
)
print(f"✅ Anthropic OK: {msg.content[0].text}")
asyncio.run(test_telegram())
test_anthropic()
python test_setup.py
# ✅ Telegram OK: @mi_claude_bot
# ✅ Anthropic OK: ok
Conceptos clave de python-telegram-bot v21
Application
El objeto central que gestiona el bot. Conecta handlers, jobs y el loop de polling/webhook:
from telegram.ext import Application
app = Application.builder().token(TOKEN).build()
# ... agregar handlers
await app.run_polling()
Handlers
Procesan tipos específicos de update. Los más usados:
from telegram.ext import (
CommandHandler, # /comandos
MessageHandler, # mensajes de texto/foto/etc
CallbackQueryHandler, # botones inline
filters,
)
Context y Update
Cada handler recibe update (el mensaje entrante) y context (datos del bot y la sesión):
async def handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
user = update.effective_user
chat_id = update.effective_chat.id
text = update.message.text
await update.message.reply_text("respuesta")
ConversationHandler
Para flujos de múltiples pasos (formularios, wizards):
from telegram.ext import ConversationHandler
STEP1, STEP2 = range(2)
conv = ConversationHandler(
entry_points=[CommandHandler("start", start)],
states={
STEP1: [MessageHandler(filters.TEXT, step1_handler)],
STEP2: [MessageHandler(filters.TEXT, step2_handler)],
},
fallbacks=[CommandHandler("cancel", cancel)],
)
Seguridad básica
Siempre valida que el usuario tiene permiso antes de procesar:
# decorador reutilizable
from functools import wraps
from config import ALLOWED_USER_ID
def solo_usuario_permitido(func):
@wraps(func)
async def wrapper(update: Update, context, *args, **kwargs):
if ALLOWED_USER_ID and update.effective_user.id != ALLOWED_USER_ID:
await update.message.reply_text("No autorizado.")
return
return await func(update, context, *args, **kwargs)
return wrapper