Ejercicio: Diseñar la conversación antes de programar
Objetivo
Convertir las reglas de WhatsApp para mensajes interactivos en funciones puras que tu bot usará siempre antes de enviar un menú: recortar títulos, validar botones y listas, y generar la versión de texto de un menú.
Cómo se trabaja
La parte A es de papel: diseñas la conversación de un negocio antes de tocar código. La parte B son cuatro funciones de JavaScript puro que se comprueban en esta página. Los límites están en la lección (3 botones, 20 caracteres por título de botón, 10 filas, 24 caracteres por título de fila). Meta puede cambiarlos: por eso conviene guardarlos en constantes que se modifican en un solo lugar.
💡 Reto extra
Escribe botonesOLista(opciones): si hay 3 opciones o menos devuelve "botones", si hay entre 4 y 10 devuelve "lista" y si hay más devuelve "texto". Es la decisión que tomará tu bot cuando un menú crezca.
Pasos
Marca cada paso a medida que lo completes. Tu avance se guarda en este navegador.
💡 Pista 1: Recortar sin pasarte
Si el texto es más largo que max, toma texto.slice(0, max - 1) y añade "…": así el total es max. Si el recorte deja un espacio al final, quítalo con trimEnd() (el resultado mide max o un carácter menos, y la prueba lo admite).
💡 Pista 2: Acumular errores
Crea const errores = [], ve añadiendo textos con push y al final devuelve { ok: errores.length === 0, errores }. Si el array de entrada no existe o está vacío, devuelve el error enseguida con return.
💡 Pista 3: Detectar ids repetidos
Un Set ayuda: const vistos = new Set(); si vistos.has(b.id) ya había uno igual, anota el error; si no, vistos.add(b.id).
💡 Pista 4: Numerar opciones
opciones.map((o, i) => (i + 1) + ". " + o).join("\n") hace casi todo el trabajo.
🔎 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.
const MAX_BOTONES = 3;
const MAX_TITULO_BOTON = 20;
const MAX_FILAS = 10;
const MAX_TITULO_FILA = 24;
function recortarTitulo(texto, max) {
const limpio = String(texto ?? "").trim();
if (limpio.length <= max) return limpio;
return limpio.slice(0, max - 1).trimEnd() + "…";
}
function validarBotones(botones) {
const errores = [];
if (!Array.isArray(botones) || botones.length === 0) {
return { ok: false, errores: ["Debe haber al menos 1 botón"] };
}
if (botones.length > MAX_BOTONES) errores.push("Máximo " + MAX_BOTONES + " botones");
const vistos = new Set();
for (const b of botones) {
const id = b && b.id;
const titulo = b && b.titulo;
if (!id) errores.push("Cada botón necesita un id");
else if (vistos.has(id)) errores.push("Id repetido: " + id);
else vistos.add(id);
if (!titulo || String(titulo).trim() === "") errores.push("Cada botón necesita un título");
else if (String(titulo).length > MAX_TITULO_BOTON) {
errores.push('El título "' + titulo + '" supera ' + MAX_TITULO_BOTON + " caracteres");
}
}
return { ok: errores.length === 0, errores };
}
function validarLista(filas) {
const errores = [];
if (!Array.isArray(filas) || filas.length === 0) {
return { ok: false, errores: ["Debe haber al menos 1 fila"] };
}
if (filas.length > MAX_FILAS) errores.push("Máximo " + MAX_FILAS + " filas");
for (const f of filas) {
if (f && f.titulo && String(f.titulo).length > MAX_TITULO_FILA) {
errores.push('El título "' + f.titulo + '" supera ' + MAX_TITULO_FILA + " caracteres");
}
}
return { ok: errores.length === 0, errores };
}
function menuATexto(opciones) {
return (opciones ?? []).map((o, i) => (i + 1) + ". " + o).join("\n");
}