Ejercicio: Marketing responsable por WhatsApp
Objetivo
Construir las tres piezas que protegen a tu minimarket del spam: registrar el opt-in, reconocer la baja y decidir si un cliente puede recibir una promoción hoy.
Cómo se trabaja
La parte A es de diseño: piensas tus textos reales. La parte B son funciones de JavaScript puro que se comprueban aquí. Las horas son números en milisegundos y un cliente es un objeto como este:
{ telefono: "51999000111", optIn: { canal: "qr", fecha: 1000 } | null, baja: null | 5000, promosEnviadas: [1000, 2000] }
Considera un día como 24 * 60 * 60 * 1000 ms y una semana como 7 días. Un envío cuenta para el límite semanal si pasó menos de 7 días.
💡 Reto extra
Añade registrarEnvio(cliente, ahora), que devuelve un cliente nuevo con ahora añadido a promosEnviadas y descarta los envíos de hace más de 30 días para que el registro no crezca sin límite. Pensando en la lección 24, es una forma sencilla de aplicar retención.
Pasos
Marca cada paso a medida que lo completes. Tu avance se guarda en este navegador.
💡 Pista 1: registrarOptIn sin mutar
Usa { ...cliente, optIn: { canal, fecha: ahora }, baja: null }. Antes, comprueba el canal con un array y includes.
💡 Pista 2: Normalizar el texto
String(texto ?? "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9 ]/g, " ").replace(/\s+/g, " ").trim() deja " Stop. " como "stop". Luego compara con un array de palabras usando includes.
💡 Pista 3: Contar envíos recientes
Filtra cliente.promosEnviadas (puede no existir: usa ?? []) con ahora - t < 7 * DIA y compara la longitud con maxPorSemana.
🔎 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 DIA = 24 * 60 * 60 * 1000;
const CANALES = ["casilla", "palabra-clave", "qr", "formulario"];
function registrarOptIn(cliente, canal, ahora) {
if (!CANALES.includes(canal)) throw new Error("Canal de opt-in desconocido: " + canal);
return { ...cliente, optIn: { canal, fecha: ahora }, baja: null };
}
function normalizar(texto) {
return String(texto ?? "")
.normalize("NFD").replace(/[̀-ͯ]/g, "")
.toLowerCase().replace(/[^a-z0-9 ]/g, " ").replace(/\s+/g, " ").trim();
}
function procesarBaja(texto) {
return ["baja", "stop", "cancelar", "no mas"].includes(normalizar(texto));
}
function puedeRecibirPromo(cliente, ahora, maxPorSemana = 2) {
if (!cliente.optIn || cliente.baja) return false;
const recientes = (cliente.promosEnviadas ?? []).filter(t => ahora - t < 7 * DIA);
return recientes.length < maxPorSemana;
}