Cap 2: Entorno de Desarrollo

Por: Artiko
pythontelegramanthropicsetupentorno

Requisitos previos

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