// Python Dev
Dos días para una tarea que parecía trivial: la carga asíncrona en bots de Telegram.
Publicado el 30.05.2026
Hay una clase de tareas que parecen quince minutos de trabajo. Luego te pones con ellas y descubres que no es el código: es cómo está diseñada la sistema bajo el capó. Esta es la historia de una de esas tareas.
Contexto
Estaba desarrollando un bot de Telegram con una lógica que a primera vista no parecía complicada: el usuario envía fotografías, el bot las recoge, espera la última y pregunta — «¿Hay más?». Hay restricciones: mínimo 5 fotos, máximo 30. Si hay menos de cinco — callamos, esperamos más. Si llegan más de treinta — paramos y decimos que ya basta. Después el FSM pasa al siguiente estado y comienza el procesamiento principal.
Parecería sencillo: capturas el evento, cuentas las fotos, cuando llega la última envías el botón. Un par de horas de trabajo con lectura de especificaciones y pruebas, como mucho.
Pero algo salió mal y el trabajo se alargó dos días.
Cómo Telegram maneja las fotografías
Lo primero que no tuve en cuenta: Telegram no envía las fotografías una por una si hay varias. Las agrupa en conjuntos, asignando a cada una un media_group_id — y las envía en bloques de 10. Este comportamiento está integrado en la plataforma y no se puede configurar.
Parecería que está bien: capturamos eventos por media_group_id, esperamos silencio, enviamos el botón. Pero aquí viene la segunda sorpresa.
Telegram no avisa de antemano cuántas fotos habrá en el grupo.
Recibes los eventos uno tras otro. Cada siguiente podría ser la última — o no. Si las fotos son grandes, entre eventos aparecen pausas: los servidores de Telegram procesan la subida. Y te enfrentas al clásico problema de incertidumbre: ¿seguir esperando o ya enviar el mensaje?
Primeros intentos
La primera idea fue esperar un timeout fijo tras cada evento. Si hay 1-2 segundos de silencio — ya llegó todo, enviamos el botón. La lógica es clara y se implementa fácil: rastreamos el tiempo de la última foto recibida, lanzamos una comprobación en segundo plano sin bloquear el hilo principal. Pero ya sabes que las fotos pueden ser grandes, las redes inestables y la espera se convierte en una lotería.
Problema con los timestamps
Esto es lo que pasa en la práctica. El usuario envía 25 fotos. El cliente empieza a subirlas. Los servidores de Telegram aceptan las fotos y las procesan — y es justo en el momento del procesamiento en el servidor cuando se registra la marca temporal del evento, no el momento del envío desde el dispositivo.
Eso significa que los eventos sendMediaGroup pueden llegar al bot con retrasos y en un orden impredecible respecto a cómo aparecen en la interfaz del cliente.
Capturamos el último evento, esperamos un segundo, enviamos el mensaje con el botón. Todo lógico. Pero en la interfaz del usuario se ve así: primero aparece nuestro botón, luego — ¡bam! — encima del mismo llegan varias fotos más, que Telegram muestra en la línea de tiempo según la hora de su procesamiento.
El botón se desplaza hacia arriba. El usuario no lo ve y no entiende qué hacer después.
Probé distintos timeouts, rastreé los números de los mensajes entrantes — nada dio un resultado estable. Porque el problema no está en el timeout. El problema es que no controlas el orden en que los mensajes aparecen en la interfaz del cliente: eso lo decide Telegram en su lado.
La solución que funcionó
Al final llegué a una idea simple, que resuelve el problema no previniéndolo sino reaccionando al hecho.
El algoritmo es este:
Después del último evento recibido esperamos 1-2 segundos. Enviamos un mensaje con botón y guardamos su message_id. Si después de eso llega otra foto — borramos el mensaje antiguo con el botón y enviamos uno nuevo. Repetimos hasta que haya un silencio real.
# Así es más o menos en aiogram
last_photo_time = {}
button_message_id = {}
async def handle_photo(message: Message, state: FSMContext):
user_id = message.from_user.id
last_photo_time[user_id] = time.time()
data = await state.get_data()
photos = data.get("photos", [])
photos.append(message.photo[-1].file_id)
await state.update_data(photos=photos)
# Eliminamos el botón anterior, si existía
if user_id in button_message_id:
try:
await message.bot.delete_message(
chat_id=message.chat.id,
message_id=button_message_id[user_id]
)
except Exception:
pass
# Esperamos silencio sin bloquear
asyncio.create_task(check_and_send_button(message, state))
async def check_and_send_button(message: Message, state: FSMContext):
user_id = message.from_user.id
await asyncio.sleep(1.5)
# Si durante este tiempo llegó una nueva foto — salimos, ya se ha iniciado una nueva tarea
if time.time() - last_photo_time[user_id] < 1.4:
return
data = await state.get_data()
photos = data.get("photos", [])
if len(photos) < 5:
return # Silencio, esperamos más
if len(photos) >= 30:
# Decimos que basta, pasamos adelante
await state.set_state(NextState.processing)
return
# Enviamos el botón y guardamos el id
sent = await message.answer(
"¿Son todas las fotos?",
reply_markup=confirmation_keyboard()
)
button_message_id[user_id] = sent.message_id
La idea clave: no intentamos adivinar cuándo llegará la última foto. Simplemente estamos preparados para reenviar el botón si nos equivocamos. El usuario siempre ve el botón actualizado al final del timeline, junto a las últimas fotos. Visualmente, un botón que desaparece no se nota.
Qué es importante aquí desde el punto de vista de la arquitectura
Es un buen ejemplo de que los sistemas asíncronos a menudo no se pueden diseñar basándose en suposiciones sobre el orden de los eventos. Telegram es un sistema asíncrono con su propia lógica de procesamiento en los servidores. Nuestro bot también es un sistema asíncrono. Cuando interactúan, no está garantizado ningún orden de los eventos en ninguna de las dos interfaces.
El primer instinto es predecir el comportamiento y escribir código para ello. El segundo, más resistente, es hacer un sistema que responda correctamente a un estado inesperado.
Borrar y volver a enviar mensajes parece una herramienta burda. Pero fue precisamente esa solución la que resultó fiable, predecible y comprensible para el usuario. Nada de magia con timestamps, ni ajustes de timeouts a ciegas.
A veces la solución simple no es un compromiso. Es la solución correcta.
Tecnologías
Python, aiogram, asyncio. FSM para gestionar los estados del diálogo.
// Python Dev
Другие статьи Python Dev
2026-05-15
n8n: una bonita envoltura que se llevó dos días
Клиент пришёл с идеей: у них есть доступ к API level.travel, сотни Telegram-каналов для турагентов и желание автоматически публиковать выгодные туры по …
2026-05-14
Cómo hacer que un LLM se lleve bien con la memoria: guarda los hechos tú mismo
Los LLM razonan muy bien. Tienen problemas de memoria. Pregunta al asistente de IA sobre algo que mencionaste antes en un diálogo largo, y puede confundirse, …
2026-05-13
Extracción de datos en 2026: ¡no hace falta pasar cada página por un LLM!
Entre los desarrolladores de parsers se ha difundido un enfoque extraño: enviar cada página descargada a una LLM pidiéndole que encuentre los datos necesarios. …
// Python Projects
Проекты Python Dev
2026-05-28
Robot cobrador: llamadas automaticas a deudores
Un sistema automatizado de llamadas de voz para cobro de deudas con integracion con Google Sheets, sintesis de voz, reconocimiento de respuestas y reintentos de …
2026-05-27
Gestion automatica de una red de canales de Telegram para una agencia de viajes
Un sistema de publicacion automatica para 150 canales de Telegram con seleccion de tours y vuelos, generacion de imagenes y publicaciones programadas.
2026-04-29
Protocolo automático de llamada: de la grabación al documento estructurado
Protocolo automático de la llamada: de la grabación al documento estructurado Los equipos distribuidos pasan mucho tiempo en llamadas. Discuten tareas, toman …
// Contact
¿Necesitas ayuda?
Escríbeme y te ayudaré a resolver el problema
Escribir en TelegramОтвечаю в течение рабочего дня (03:00–13:00 GMT)
Или оставьте заявку здесь: