Files
whatsapp-api-mockup/js/interactivity.js
T

683 lines
25 KiB
JavaScript

/* WAM · interactivity.js — dueño D (núcleo).
Flujos de clic (quick replies, filas de lista, botones de plantilla y de card),
composer libre, indicador "escribiendo…", ticks automáticos y modo
"reproducir demo" paso a paso (con autoPulsar y velocidad). No toca el DOM
al cargar.
Guion frente a sesión: WAM.estado.mensajes es el GUION (persistente, lo que
edita el editor y reproduce la demo). Lo que ocurre al pulsar botones o
escribir en el composer es la SESIÓN (WAM.sesion, solo en memoria): se pinta
con anadirMensajeDOM sin tocar el estado ni localStorage. Cualquier
WAM.render() vuelve al guion: vacía la sesión, cierra la hoja de lista y
cancela flujos y demo en curso. */
window.WAM = window.WAM || {};
(function () {
"use strict";
var CANCELADO = "wam:cancelado";
var TIPOS_VALIDOS = [
"text", "image", "video", "audio", "document", "sticker", "location",
"contact", "template", "buttons", "list", "cta_url", "carousel"
];
var CLAVE_VELOCIDAD = "wam-mockup-v1-velocidad";
var VELOCIDADES = [0.5, 1, 2];
function nada() { /* noop */ }
/* Mensajes de la sesión en curso (efímeros). */
WAM.sesion = [];
var contSesion = 0;
function idSesion() { contSesion++; return "s" + contSesion; }
/* ————————————————— temporizadores cancelables por grupo ————————————————— */
/* Dos grupos: "demo" (reproducción) y "flujo" (clics, composer, ticks).
Cancelar un grupo sube su generación: los pasos pendientes de esa
generación se rechazan con CANCELADO y ya no pueden programar más. */
var grupos = {
demo: { gen: 0, cancelar: [] },
flujo: { gen: 0, cancelar: [] }
};
function grupoDe(nombre) { return grupos[nombre] ? nombre : "flujo"; }
var velocidad = 1;
try {
var guardada = parseFloat(window.localStorage.getItem(CLAVE_VELOCIDAD));
if (VELOCIDADES.indexOf(guardada) !== -1) velocidad = guardada;
} catch (e) { /* sin localStorage */ }
WAM.getVelocidad = function () { return velocidad; };
WAM.setVelocidad = function (v) {
var n = parseFloat(v);
if (VELOCIDADES.indexOf(n) === -1) n = 1;
velocidad = n;
try { window.localStorage.setItem(CLAVE_VELOCIDAD, String(n)); } catch (e) { /* idem */ }
return velocidad;
};
function esperaEn(nombre, ms, gen) {
var g = grupos[grupoDe(nombre)];
return new Promise(function (resolver, rechazar) {
if (gen !== undefined && gen !== g.gen) { rechazar(CANCELADO); return; }
var dur = Math.max(0, (Number(ms) || 0) / velocidad);
var id = setTimeout(function () {
quitar();
if (gen !== undefined && gen !== g.gen) rechazar(CANCELADO);
else resolver();
}, dur);
function cancelar() {
clearTimeout(id);
quitar();
rechazar(CANCELADO);
}
function quitar() {
var i = g.cancelar.indexOf(cancelar);
if (i !== -1) g.cancelar.splice(i, 1);
}
g.cancelar.push(cancelar);
});
}
/* Promise cancelable (SPEC): todo temporizador pasa por aquí.
grupo opcional: "flujo" (por defecto) o "demo". La duración se divide
por la velocidad elegida (0,5x / 1x / 2x). */
WAM.espera = function (ms, grupo) { return esperaEn(grupo, ms); };
/* Contexto de una secuencia: fija grupo y generación al empezar. */
function contexto(nombre) {
var g = grupoDe(nombre);
var gen = grupos[g].gen;
return {
grupo: g,
vivo: function () { return grupos[g].gen === gen; },
esperar: function (ms) { return esperaEn(g, ms, gen); }
};
}
var colas = { demo: Promise.resolve(), flujo: Promise.resolve() };
function cancelarGrupo(nombre) {
var g = grupos[grupoDe(nombre)];
g.gen++;
var copia = g.cancelar.slice();
g.cancelar = [];
for (var i = 0; i < copia.length; i++) copia[i]();
colas[grupoDe(nombre)] = Promise.resolve();
}
function cancelarTodo() {
cancelarGrupo("demo");
cancelarGrupo("flujo");
escritores.clear();
pintarEscribiendo();
}
/* ————————————————— indicador "escribiendo…" (contador de referencias) ————————————————— */
var escritores = new Set();
function pintarEscribiendo() {
if (typeof WAM.setEscribiendo === "function") WAM.setEscribiendo(escritores.size > 0);
}
function empezarEscribir() {
var ficha = {};
escritores.add(ficha);
pintarEscribiendo();
return ficha;
}
function dejarEscribir(ficha) {
if (escritores.delete(ficha)) pintarEscribiendo();
}
async function escribirDurante(ctx, ms) {
var ficha = empezarEscribir();
try { await ctx.esperar(ms); } finally { dejarEscribir(ficha); }
}
/* ————————————————— estado de la demo ————————————————— */
var demoAndando = false;
WAM.demoActiva = function () { return demoAndando; };
function avisarDemo() {
if (typeof WAM.onDemoChange === "function") {
try { WAM.onDemoChange(demoAndando); } catch (e) { /* el hook es opcional */ }
}
}
/* ————————————————— búsqueda de mensajes y filas ————————————————— */
function cuerpoChat() { return document.getElementById("chat-cuerpo"); }
function selectorId(id) {
return '.wa-row[data-id="' + String(id).replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"]';
}
function filaDe(msg) {
var cuerpo = cuerpoChat();
if (!cuerpo || !msg || typeof msg.id !== "string" || !msg.id) return null;
var filas = cuerpo.querySelectorAll(selectorId(msg.id));
return filas.length ? filas[filas.length - 1] : null;
}
function buscarPorId(id) {
if (!id) return null;
var guion = (WAM.estado && WAM.estado.mensajes) || [];
for (var i = 0; i < guion.length; i++) if (guion[i] && guion[i].id === id) return guion[i];
for (var j = WAM.sesion.length - 1; j >= 0; j--) if (WAM.sesion[j] && WAM.sesion[j].id === id) return WAM.sesion[j];
return null;
}
/* Mensaje de la hoja de lista abierta (guion o sesión). */
var hojaMsg = null;
/* Mensaje al que pertenece un elemento clicable del chat o de la hoja.
Se resuelve por el data-id de su fila (inmune a índices obsoletos y
válido para mensajes de sesión); el índice data-flujo queda de respaldo. */
WAM.mensajeDeElemento = function (el) {
if (!el || !el.closest) return null;
var hoja = el.closest("#sheet");
if (hoja) return hojaMsg;
var fila = el.closest(".wa-row[data-id]");
var msg = fila ? buscarPorId(fila.getAttribute("data-id")) : null;
if (msg) return msg;
var indice = parseInt(el.getAttribute("data-flujo"), 10);
var guion = (WAM.estado && WAM.estado.mensajes) || [];
return isNaN(indice) ? null : (guion[indice] || null);
};
/* Abre la hoja de lista de un mensaje (del guion o de la sesión). */
WAM.abrirLista = function (msg) {
if (!msg || typeof WAM.abrirSheet !== "function") return;
hojaMsg = msg;
var guion = (WAM.estado && WAM.estado.mensajes) || [];
var i = guion.indexOf(msg);
WAM.abrirSheet(i >= 0 ? i : msg);
};
/* ————————————————— pintado incremental ————————————————— */
/* grupo / cola (.grupo / .ultimo) los recalcula anadirMensajeDOM según el DOM. */
function pintar(msg) {
if (typeof WAM.anadirMensajeDOM !== "function") return null;
WAM.anadirMensajeDOM(msg);
var fila = filaDe(msg);
if (fila && msg.cita) pintarCita(fila, msg.cita);
return fila;
}
function anadirSesion(msg) {
WAM.sesion.push(msg);
return pintar(msg);
}
function mensajeOut(texto, cita) {
var msg = {
id: idSesion(),
dir: "out",
tipo: "text",
hora: WAM.horaAhora(),
estado: "sent",
texto: String(texto == null ? "" : texto)
};
if (cita) msg.cita = cita;
return msg;
}
/* Copia una respuesta anidada (Mensaje completo) para poder reutilizarla
cada vez que se vuelve a pulsar el mismo botón. */
function mensajeDesde(origen) {
var copia;
try {
copia = JSON.parse(JSON.stringify(
(origen && typeof origen === "object") ? origen : { tipo: "text", texto: String(origen == null ? "" : origen) }
));
} catch (e) {
copia = { tipo: "text", texto: "" };
}
if (!copia || typeof copia !== "object" || Array.isArray(copia)) copia = { tipo: "text", texto: "" };
if (TIPOS_VALIDOS.indexOf(copia.tipo) === -1) copia.tipo = "text";
copia.id = idSesion();
copia.dir = "in";
copia.hora = WAM.horaAhora();
copia.estado = "sent";
delete copia.cita;
return copia;
}
/* ————————————————— cita de la respuesta (quoted reply) ————————————————— */
function vacio(v) { return v === undefined || v === null || String(v).trim() === ""; }
function sinFormato(s) {
return String(s == null ? "" : s)
.replace(/```([\s\S]*?)```/g, "$1")
.replace(/`([^`\n]+)`/g, "$1")
.replace(/\*([^*\n]+)\*/g, "$1")
.replace(/(^|[^\w])_([^_\n]+)_(?=[^\w]|$)/g, "$1$2")
.replace(/(^|[^\w])~([^~\n]+)~(?=[^\w]|$)/g, "$1$2")
.trim();
}
function miniaturaSegura(u) {
if (vacio(u)) return "";
var s = String(u);
if (/^data:video\//i.test(s)) return "";
if (typeof WAM.urlSegura === "function" && !WAM.urlSegura(s, "media")) return "";
return s;
}
/* { nombre, titulo, extracto, miniatura, foto } del mensaje original. */
function citaDe(msg, opciones) {
if (!msg) return null;
var cuenta = (WAM.estado && WAM.estado.cuenta) || {};
var cita = {
nombre: vacio(cuenta.nombre) ? "Chat" : String(cuenta.nombre),
titulo: "",
extracto: "",
miniatura: "",
foto: false
};
var op = opciones || {};
var partes = [];
if (msg.tipo === "list") {
cita.titulo = sinFormato(msg.header);
partes = [msg.body, msg.footer];
} else if (msg.tipo === "carousel") {
var card = (msg.cards || [])[op.card] || {};
partes = [vacio(card.texto) ? msg.body : card.texto];
cita.miniatura = miniaturaSegura(card.media);
if (!cita.miniatura && vacio(card.media) && typeof WAM.svgPlaceholder === "function") {
cita.miniatura = WAM.svgPlaceholder("");
}
cita.foto = true;
} else {
if (msg.headerTipo === "texto") cita.titulo = sinFormato(msg.headerTexto);
partes = [msg.body];
if (msg.headerTipo === "imagen") {
cita.miniatura = vacio(msg.headerMedia)
? (typeof WAM.svgPlaceholder === "function" ? WAM.svgPlaceholder("") : "")
: miniaturaSegura(msg.headerMedia);
cita.foto = true;
} else if (msg.headerTipo === "video") {
cita.foto = true;
}
}
cita.extracto = partes.filter(function (p) { return !vacio(p); }).map(sinFormato).join("\n");
if (!cita.extracto && !cita.titulo) cita.extracto = cita.foto ? "Foto" : "Mensaje";
return cita;
}
var SVG_CAMARA = '<svg class="wa-cita-cam" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">' +
'<path fill="currentColor" d="M5.6 2.5h4.8l1 1.5H13a1.5 1.5 0 0 1 1.5 1.5v6A1.5 1.5 0 0 1 13 12.5H3A1.5 1.5 0 0 1 1.5 11V5.5A1.5 1.5 0 0 1 3 4h1.6l1-1.5Zm2.4 3a2.75 2.75 0 1 0 0 5.5 2.75 2.75 0 0 0 0-5.5Z"/></svg>';
/* Bloque de cita dentro de la burbuja OUT; todo por textContent (sin XSS). */
function pintarCita(fila, cita) {
var burbuja = fila.querySelector(".bubble");
if (!burbuja || burbuja.querySelector(".wa-cita")) return;
var caja = document.createElement("div");
caja.className = "wa-cita";
var txt = document.createElement("div");
txt.className = "wa-cita-txt";
var nombre = document.createElement("div");
nombre.className = "wa-cita-nombre";
nombre.textContent = cita.nombre;
txt.appendChild(nombre);
if (cita.titulo) {
var tit = document.createElement("div");
tit.className = "wa-cita-titulo";
tit.textContent = cita.titulo;
txt.appendChild(tit);
}
if (cita.extracto) {
var ext = document.createElement("div");
ext.className = "wa-cita-extracto";
if (cita.foto) {
var cam = document.createElement("span");
cam.innerHTML = SVG_CAMARA; /* constante propia, sin datos del usuario */
if (cam.firstChild) ext.appendChild(cam.firstChild);
}
ext.appendChild(document.createTextNode(cita.extracto));
txt.appendChild(ext);
}
caja.appendChild(txt);
if (cita.miniatura) {
var img = document.createElement("img");
img.className = "wa-cita-mini";
img.alt = "";
img.draggable = false;
img.src = cita.miniatura;
caja.appendChild(img);
}
burbuja.insertBefore(caja, burbuja.firstChild);
burbuja.classList.add("wa-con-cita");
}
/* ————————————————— ticks automáticos ————————————————— */
/* Ticks sent → delivered → read sobre la burbuja OUT indicada (por data-id,
para que dos envíos solapados avancen cada uno su propio tick). */
function repintarTickDe(msg, estado) {
if (typeof WAM.tick !== "function") return;
var fila = filaDe(msg);
if (!fila || !fila.classList.contains("out")) return;
var tick = fila.querySelector(".tick");
if (tick && tick.parentNode) {
/* WAM.tick devuelve un <svg> completo: se REEMPLAZA el nodo. */
var aux = document.createElement("span");
aux.innerHTML = WAM.tick(estado);
if (aux.firstChild) tick.parentNode.replaceChild(aux.firstChild, tick);
}
}
/* Los mensajes de sesión no se guardan; si se cancelan los ticks, la
burbuja salta a "read" para no quedar congelada en ✓ / ✓✓ gris. */
async function animarTics(msg, ctx) {
try {
await ctx.esperar(800);
msg.estado = "delivered";
repintarTickDe(msg, "delivered");
await ctx.esperar(800);
msg.estado = "read";
repintarTickDe(msg, "read");
} catch (e) {
if (msg.estado !== "read") {
msg.estado = "read";
repintarTickDe(msg, "read");
}
throw e;
}
}
/* ————————————————— flujos ————————————————— */
/* Respuestas encadenadas: typing ~900 ms + burbuja IN por cada una.
En la demo (enDemo) cada respuesta con autoPulsar se pulsa sola. */
async function responder(lista, ctx, enDemo) {
for (var i = 0; i < lista.length; i++) {
await escribirDurante(ctx, 900);
if (!ctx.vivo()) throw CANCELADO;
var r = mensajeDesde(lista[i]);
anadirSesion(r);
if (enDemo) await autoPulsar(r, ctx);
}
}
/* Burbuja OUT con el texto (y cita opcional {nombre, extracto, miniatura}),
ticks auto y respuestas. La burbuja sale al instante; las respuestas de
flujos simultáneos se encolan para no intercalarse. Cancelable. */
WAM.flujo = function (texto, respuestas, cita) {
var lista = Array.isArray(respuestas) ? respuestas : [];
var ctx = contexto("flujo");
var out = mensajeOut(texto, cita || null);
anadirSesion(out);
var tics = animarTics(out, ctx);
tics.catch(nada);
var turno = colas.flujo.then(function () {
if (!ctx.vivo()) throw CANCELADO;
return tics.then(function () { return responder(lista, ctx, false); });
});
colas.flujo = turno.catch(nada);
return turno.catch(nada);
};
/* Envío libre del composer: burbuja OUT con hora actual y ticks automáticos. */
WAM.enviarLibre = function (texto) {
var limpio = String(texto == null ? "" : texto).trim();
if (!limpio) return;
var out = mensajeOut(limpio);
anadirSesion(out);
animarTics(out, contexto("flujo")).catch(nada);
};
/* Resuelve { texto, respuestas, cita, msg, tipo } desde los data-* que
estampa render.js: data-btn (template/buttons), data-sec + data-fila
(lista) o data-card (carousel). Etiquetas vacías = las del render. */
WAM.resolverFlujo = function (el) {
var vacioR = { texto: "", respuestas: [], cita: null, msg: null, tipo: "" };
if (!el || typeof el.getAttribute !== "function") return vacioR;
var msg = WAM.mensajeDeElemento(el);
if (!msg) return vacioR;
var ds = el.dataset || {};
if (ds.sec !== undefined || ds.fila !== undefined) {
var seccion = (msg.secciones || [])[parseInt(ds.sec, 10)];
var fila = ((seccion && seccion.filas) || [])[parseInt(ds.fila, 10)];
if (!fila) return vacioR;
var titulo = vacio(fila.titulo) ? "Opción" : String(fila.titulo);
return {
texto: titulo + (vacio(fila.descripcion) ? "" : "\n" + fila.descripcion),
respuestas: fila.respuestas || [],
cita: citaDe(msg),
msg: msg,
tipo: "fila"
};
}
if (ds.btn !== undefined) {
var n = parseInt(ds.btn, 10);
var boton = (msg.botones || [])[n];
if (!boton) return vacioR;
return {
texto: vacio(boton.texto) ? "Botón " + (n + 1) : String(boton.texto),
respuestas: boton.respuestas || [],
cita: citaDe(msg),
msg: msg,
tipo: "boton"
};
}
if (ds.card !== undefined) {
var c = parseInt(ds.card, 10);
var card = (msg.cards || [])[c];
if (!card) return vacioR;
return {
texto: vacio(card.textoBoton) ? "Ver más" : String(card.textoBoton),
respuestas: card.respuestas || [],
cita: citaDe(msg, { card: c }),
msg: msg,
tipo: "card"
};
}
/* Elemento plano de un mensaje list: es el botón que abre la hoja. */
return vacioR;
};
/* Deja el botón pulsado en gris y sin respuesta a más clics (como iOS). */
WAM.marcarUsado = function (el) {
if (!el || !el.classList) return;
el.classList.add("usado");
el.setAttribute("aria-disabled", "true");
el.setAttribute("tabindex", "-1");
};
/* Guarda la sesión actual como parte del guion (acción explícita). */
WAM.sesionAGuion = function () {
if (!WAM.sesion.length || !WAM.estado) return 0;
var tomar = typeof WAM.crearAlocador === "function" ? WAM.crearAlocador() : WAM.generarId;
var nuevos = WAM.sesion.map(function (m) {
var copia = JSON.parse(JSON.stringify(m));
delete copia.cita;
(function reasignar(o) {
if (!o || typeof o !== "object") return;
if (Array.isArray(o)) { o.forEach(reasignar); return; }
if (typeof o.id === "string") o.id = tomar();
["respuestas", "botones", "secciones", "filas", "cards"].forEach(function (k) { reasignar(o[k]); });
})(copia);
return copia;
});
var n = nuevos.length;
WAM.estado = WAM.normalizar ? WAM.normalizar({
version: WAM.estado.version, tema: WAM.estado.tema, cuenta: WAM.estado.cuenta,
mensajes: WAM.estado.mensajes.concat(nuevos)
}) : WAM.estado;
if (typeof WAM.guardar === "function") WAM.guardar();
if (typeof WAM.render === "function") WAM.render();
if (typeof WAM.renderEditor === "function") WAM.renderEditor();
return n;
};
/* ————————————————— reproducción de la demo ————————————————— */
function pulsacion(el, ctx) {
el.classList.add("wam-pulsando");
return ctx.esperar(260).then(function () { el.classList.remove("wam-pulsando"); },
function (e) { el.classList.remove("wam-pulsando"); throw e; });
}
function centrarCard(el) {
var car = el.closest(".carousel");
var card = el.closest(".card");
if (!car || !card) return;
var delta = card.getBoundingClientRect().left - car.getBoundingClientRect().left;
try { car.scrollTo({ left: car.scrollLeft + delta - 8, behavior: "smooth" }); }
catch (e) { car.scrollLeft += delta - 8; }
}
/* Pulsa sola la opción msg.autoPulsar (índice de botón, card o fila
aplanada de la lista) 1 s después de que aparezca el mensaje. */
async function autoPulsar(msg, ctx) {
if (!msg || ["template", "buttons", "list", "carousel"].indexOf(msg.tipo) === -1) return;
var idx = msg.autoPulsar;
if (idx === null || idx === undefined || idx === "") return;
idx = Number(idx);
if (!isFinite(idx) || idx < 0) return;
var fila = filaDe(msg);
if (!fila) return;
await ctx.esperar(1000);
var el = null;
if (msg.tipo === "list") {
var s, f, cuenta = 0, destino = null;
var secs = msg.secciones || [];
for (s = 0; s < secs.length && !destino; s++) {
var filas = (secs[s] && secs[s].filas) || [];
for (f = 0; f < filas.length; f++) {
if (cuenta === idx) { destino = [s, f]; break; }
cuenta++;
}
}
if (!destino) return;
var abridor = fila.querySelector("[data-flujo]:not([data-btn]):not([data-card])");
if (abridor) await pulsacion(abridor, ctx);
WAM.abrirLista(msg);
await ctx.esperar(900);
var hoja = document.getElementById("sheet");
el = hoja ? hoja.querySelector('[data-sec="' + destino[0] + '"][data-fila="' + destino[1] + '"]') : null;
if (!el) { if (WAM.cerrarSheet) WAM.cerrarSheet(); return; }
await pulsacion(el, ctx);
var selL = WAM.resolverFlujo(el);
if (WAM.cerrarSheet) WAM.cerrarSheet();
await flujoEnDemo(selL, ctx);
return;
}
el = fila.querySelector(msg.tipo === "carousel" ? '[data-card="' + idx + '"]' : '[data-btn="' + idx + '"]');
if (!el) return;
if (el.hidden) {
var plegado = fila.querySelector("[data-ver-todas]");
if (plegado) { plegado.click(); await ctx.esperar(400); }
/* B abre la hoja "Todas las opciones" (render.js): se pulsa su fila */
var enHoja = document.querySelector('#sheet [data-opcion="' + idx + '"]');
if (!enHoja) { if (WAM.cerrarSheet) WAM.cerrarSheet(); return; }
await pulsacion(enHoja, ctx);
if (WAM.cerrarSheet) WAM.cerrarSheet();
}
if (msg.tipo === "carousel") { centrarCard(el); await ctx.esperar(500); }
await pulsacion(el, ctx);
var sel = WAM.resolverFlujo(el);
WAM.marcarUsado(el);
await flujoEnDemo(sel, ctx);
}
async function flujoEnDemo(sel, ctx) {
if (!sel || !sel.msg) return;
var out = mensajeOut(sel.texto, sel.cita);
anadirSesion(out);
await animarTics(out, ctx);
await responder(Array.isArray(sel.respuestas) ? sel.respuestas : [], ctx, true);
}
function vaciarChat() {
var cuerpo = cuerpoChat();
if (!cuerpo) return;
var filas = cuerpo.querySelectorAll(".wa-row");
for (var i = 0; i < filas.length; i++) {
if (filas[i].id !== "chat-escribiendo") cuerpo.removeChild(filas[i]);
}
}
WAM.reproducirDemo = function () {
if (demoAndando) return;
/* aborta flujos, ticks y typing en curso: no se mezclan con la demo */
cancelarTodo();
if (typeof WAM.cerrarSheet === "function") WAM.cerrarSheet();
hojaMsg = null;
WAM.sesion.length = 0;
demoAndando = true;
avisarDemo();
vaciarChat();
var ctx = contexto("demo");
var secuencia = ((WAM.estado && WAM.estado.mensajes) || []).slice();
(async function () {
try {
for (var i = 0; i < secuencia.length; i++) {
var msg = secuencia[i];
if (!msg) continue;
if (msg.dir === "in") await escribirDurante(ctx, 700);
if (!ctx.vivo()) throw CANCELADO;
pintar(msg);
await ctx.esperar(600);
await autoPulsar(msg, ctx);
}
} catch (e) {
/* cancelado: detenerDemo() / render() ya dejaron el chat coherente */
}
if (ctx.vivo()) {
demoAndando = false;
avisarDemo();
}
})();
};
WAM.detenerDemo = function () {
var estaba = demoAndando;
demoAndando = false;
cancelarGrupo("demo"); /* solo el grupo de la demo */
if (estaba) {
/* vuelve al guion completo (y descarta la sesión: ver reiniciarInteraccion) */
if (typeof WAM.render === "function") WAM.render();
avisarDemo();
}
};
/* Vuelve al guion: cancela todo, vacía la sesión y cierra la hoja. */
function reiniciarInteraccion() {
var estaba = demoAndando;
demoAndando = false;
cancelarTodo();
WAM.sesion.length = 0;
hojaMsg = null;
if (typeof WAM.cerrarSheet === "function") WAM.cerrarSheet();
if (estaba) avisarDemo();
}
WAM.reiniciarChat = function () {
if (typeof WAM.render === "function") WAM.render();
};
/* WAM.render() repinta el guion entero: antes se descarta la sesión y se
cancelan demo y flujos para que nada añada filas sobre el repintado
(evita data-id duplicados y la hoja con índices obsoletos). */
if (typeof WAM.render === "function" && !WAM.render.__wamEnvuelto) {
var renderBase = WAM.render;
WAM.render = function () {
reiniciarInteraccion();
return renderBase.apply(this, arguments);
};
WAM.render.__wamEnvuelto = true;
}
})();