Saltar al contenido
Chatbots de WhatsApp desde cero
Temario

Ejercicio: Practica con Telegram sin cuenta de Meta

  • ⏱ 45 minutos
  • 📶 Nivel: intermedio

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.

▶ Prueba tu código aquí (opcional)
Consola

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.

    funciones.js
    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;
    }