Ejercicio: Practica con Telegram sin cuenta de Meta
Objetivo
Escribir las piezas centrales de un adaptador de Telegram: leer un Update, convertir una respuesta del motor en el cuerpo de sendMessage y proteger el webhook con el secreto.
Cómo se trabaja
La parte A es práctica real: creas tu bot de Telegram y lo ves responder. La parte B son cuatro funciones de JavaScript puro, sin import ni red, que se comprueban en esta página. Trabaja con los mismos formatos de la lección: la entrada del motor es {tipo:'texto', texto} o {tipo:'interactivo', id}, y las respuestas son {tipo:'texto', texto} o {tipo:'botones', texto, botones:[{id, titulo}]}.
💡 Reto extra
Añade construirLista(chatId, respuesta) que convierta una respuesta lista del motor (con secciones y filas) en un inline_keyboard con un botón por fila y el texto titulo - descripcion. Verifica con un callback_data de más de 64 bytes que tu función avisa del problema en lugar de enviarlo.
Pasos
Marca cada paso a medida que lo completes. Tu avance se guarda en este navegador.
💡 Pista 1: Comparar el secreto
Antes de comparar revisa que esperado tenga contenido y que typeof cabecera === "string". Después basta esperado === cabecera (en producción se usa comparación en tiempo constante, como en la lección).
💡 Pista 2: Orden de las ramas en parsearUpdate
Mira primero update.callback_query (con typeof data === "string" y message.chat.id), luego update.message. Dentro de message, si typeof text === "string" es texto; si no, no soportado. Usa ?. para no romperte con objetos incompletos.
💡 Pista 3: El teclado en línea
botones.map((b) => ({ text: b.titulo, callback_data: b.id })) da la fila; envuélvela en otro arreglo para formar inline_keyboard. Para recortar, texto.slice(0, 4096).
🔎 Comprueba tu trabajo
Pega tu código y pulsa Comprobar. La revisión ocurre en tu navegador; nada se envía a ningún sitio. Cada punto en rojo incluye una pista. Pega solo el JavaScript, sin las etiquetas <script>.
✅ Ver una solución posible
Intenta resolverlo antes de mirar. Tu versión puede ser distinta y también correcta.
function secretoValido(esperado, cabecera) {
if (!esperado || typeof cabecera !== "string") return false;
return esperado === cabecera;
}
function parsearUpdate(update) {
if (!update) return null;
const cb = update.callback_query;
if (cb && typeof cb.data === "string" && cb.message?.chat?.id != null) {
return { chatId: cb.message.chat.id, entrada: { tipo: "interactivo", id: cb.data } };
}
const msg = update.message;
if (!msg || msg.chat?.id == null) return null;
if (typeof msg.text === "string") {
const t = msg.text.trim();
const texto = /^\/start(@\w+)?(\s|$)/.test(t) ? "hola" : t;
return { chatId: msg.chat.id, entrada: { tipo: "texto", texto } };
}
return { chatId: msg.chat.id, entrada: { tipo: "no_soportado", subtipo: "otro" } };
}
function construirEnvio(chatId, respuesta) {
const base = { chat_id: chatId, text: respuesta.texto === undefined ? "" : respuesta.texto.slice(0, 4096) };
if (respuesta.tipo === "texto") return base;
if (respuesta.tipo === "botones") {
base.reply_markup = {
inline_keyboard: [respuesta.botones.map((b) => ({ text: b.titulo, callback_data: b.id }))],
};
return base;
}
throw new Error("Tipo de respuesta desconocido: " + respuesta.tipo);
}
function esNuevo(vistos, updateId) {
if (vistos.has(updateId)) return false;
vistos.add(updateId);
return true;
}