WIP: mejoras de fidelidad, editor, lógica y suite e2e (checkpoint tras reinicio)
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
/* =====================================================================
|
||||
js/escenarios.js — Escenarios de arranque por industria (editor, C).
|
||||
Objetos JS planos (sin fetch: funciona por file://). Cada escenario
|
||||
devuelve un estado que se pasa por WAM.normalizar antes de usarse.
|
||||
Todos los negocios, personas, direcciones y cifras son FICTICIOS y
|
||||
de ejemplo: el equipo debe revisar los textos antes de usarlos con
|
||||
un cliente. No toca el DOM al cargar.
|
||||
===================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
window.WAM = window.WAM || {};
|
||||
|
||||
function escXml(s) {
|
||||
return String(s).replace(/[&<>"']/g, function (c) {
|
||||
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
||||
});
|
||||
}
|
||||
|
||||
// Ilustración SVG ligera en línea (sin red): fondo + círculo + rótulo
|
||||
function ilus(texto, fondo, acento, simbolo) {
|
||||
var svg =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="480" height="320" viewBox="0 0 480 320">' +
|
||||
'<rect width="480" height="320" fill="' + fondo + '"/>' +
|
||||
'<circle cx="240" cy="130" r="74" fill="' + acento + '" opacity=".9"/>' +
|
||||
'<text x="240" y="152" text-anchor="middle" font-family="Arial, Helvetica, sans-serif" font-size="62" fill="#fff">' + escXml(simbolo || "") + "</text>" +
|
||||
'<text x="240" y="266" text-anchor="middle" font-family="Arial, Helvetica, sans-serif" font-size="25" fill="#33414a">' + escXml(texto || "") + "</text>" +
|
||||
"</svg>";
|
||||
return "data:image/svg+xml," + encodeURIComponent(svg);
|
||||
}
|
||||
|
||||
function avatar(letra, color) {
|
||||
var svg =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">' +
|
||||
'<circle cx="48" cy="48" r="48" fill="' + color + '"/>' +
|
||||
'<text x="48" y="62" text-anchor="middle" font-family="Arial, Helvetica, sans-serif" font-size="40" font-weight="700" fill="#fff">' + escXml(letra) + "</text>" +
|
||||
"</svg>";
|
||||
return "data:image/svg+xml," + encodeURIComponent(svg);
|
||||
}
|
||||
|
||||
// Atajos de construcción
|
||||
function t(dir, hora, texto, extra) {
|
||||
var m = { dir: dir, tipo: "text", hora: hora, texto: texto };
|
||||
if (extra) Object.keys(extra).forEach(function (k) { m[k] = extra[k]; });
|
||||
return m;
|
||||
}
|
||||
function r(texto) { return { dir: "in", tipo: "text", hora: "", texto: texto }; }
|
||||
function msg(dir, tipo, hora, campos) {
|
||||
var m = { dir: dir, tipo: tipo, hora: hora, estado: dir === "out" ? "read" : "sent" };
|
||||
Object.keys(campos || {}).forEach(function (k) { m[k] = campos[k]; });
|
||||
return m;
|
||||
}
|
||||
function estado(cuenta, mensajes) {
|
||||
return { version: 1, tema: "claro", cuenta: cuenta, mensajes: mensajes };
|
||||
}
|
||||
|
||||
var ESCENARIOS = [
|
||||
{
|
||||
id: "blanco",
|
||||
nombre: "En blanco",
|
||||
descripcion: "Solo la cuenta, sin mensajes. Para construir desde cero.",
|
||||
datos: function () {
|
||||
return estado({ nombre: "Mi negocio", avatar: "", subtitulo: "en línea", verificado: false }, []);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "restaurante",
|
||||
nombre: "Restaurante · reservas",
|
||||
descripcion: "Reserva de mesa con plantilla de confirmación, menú en lista y ubicación.",
|
||||
datos: function () {
|
||||
return estado({ nombre: "La Terraza del Puerto", avatar: avatar("T", "#b5523b"), subtitulo: "en línea", verificado: true }, [
|
||||
t("out", "13:02", "Hola, ¿tienen mesa para este sábado?"),
|
||||
msg("in", "buttons", "13:02", {
|
||||
body: "¡Hola! 👋 Con gusto te ayudo con tu reserva. ¿Para cuántas personas sería?",
|
||||
footer: "La Terraza del Puerto",
|
||||
botones: [
|
||||
{ texto: "2 personas", respuestas: [r("Perfecto, mesa para 2. ¿Prefieres comida (14:00) o cena (21:00)?")] },
|
||||
{ texto: "3 a 5 personas", respuestas: [r("Genial. Para grupos de 3 a 5 tenemos terraza o salón. Te comparto el menú mientras eliges.")] },
|
||||
{ texto: "Más de 5", respuestas: [r("Para grupos grandes te atiende nuestro equipo de eventos. Te escriben en unos minutos.")] }
|
||||
]
|
||||
}),
|
||||
t("out", "13:03", "Somos 2, para cenar"),
|
||||
msg("in", "template", "13:03", {
|
||||
headerTipo: "imagen",
|
||||
headerMedia: ilus("Tu mesa está lista", "#f6e7df", "#b5523b", "✓"),
|
||||
body: "Hola Ana, tu mesa para *2 personas* quedó reservada el sábado a las *21:00*. ¿Confirmas la reserva?",
|
||||
footer: "Tolerancia de 15 minutos",
|
||||
botones: [
|
||||
{ texto: "Confirmar", respuestas: [r("¡Listo! Reserva confirmada ✨ Te esperamos el sábado a las 21:00.")] },
|
||||
{ texto: "Cambiar horario", respuestas: [r("Claro. ¿A qué hora te acomoda mejor? Tenemos 20:00, 21:30 y 22:00.")] }
|
||||
]
|
||||
}),
|
||||
msg("in", "list", "13:04", {
|
||||
header: "Menú de la casa",
|
||||
body: "Mientras tanto, ¿quieres ver alguna sección del menú?",
|
||||
footer: "Precios con impuestos incluidos",
|
||||
boton: "Ver menú",
|
||||
secciones: [
|
||||
{ titulo: "Entradas", filas: [
|
||||
{ titulo: "Ceviche del día", descripcion: "Pesca local con cítricos", respuestas: [r("El ceviche del día cambia según la pesca. Te lo recomendamos 🐟")] },
|
||||
{ titulo: "Pan de la casa", descripcion: "Masa madre y mantequilla", respuestas: [r("Nuestro pan se hornea cada tarde 🥖")] }
|
||||
] },
|
||||
{ titulo: "Platos fuertes", filas: [
|
||||
{ titulo: "Pescado a la talla", descripcion: "Para compartir", respuestas: [r("Buena elección para 2 personas. Se prepara en unos 25 minutos.")] },
|
||||
{ titulo: "Pasta de mariscos", descripcion: "Receta de la casa", respuestas: [r("La pasta de mariscos es de las más pedidas 🍝")] }
|
||||
] }
|
||||
]
|
||||
}),
|
||||
msg("in", "location", "13:05", {
|
||||
latitud: "19.4326", longitud: "-99.1332",
|
||||
nombre: "La Terraza del Puerto", direccion: "Av. del Malecón 120, Centro"
|
||||
})
|
||||
]);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "ecommerce",
|
||||
nombre: "E-commerce · pedido",
|
||||
descripcion: "Confirmación y seguimiento de pedido, catálogo en carrusel y enlace de rastreo.",
|
||||
datos: function () {
|
||||
return estado({ nombre: "Tienda Brisa", avatar: avatar("B", "#2f6fb5"), subtitulo: "Cuenta de empresa", verificado: true }, [
|
||||
msg("in", "template", "09:15", {
|
||||
headerTipo: "texto",
|
||||
headerTexto: "Pedido #10482 confirmado",
|
||||
body: "¡Gracias por tu compra, Carlos! 🎉 Tu pedido ya está en preparación. Te avisaremos por aquí cuando salga a reparto.",
|
||||
footer: "Tienda Brisa · Atención a clientes",
|
||||
botones: [
|
||||
{ texto: "Ver detalle", respuestas: [r("Tu pedido incluye: 1 × Sudadera Brisa (M) y 1 × Gorra clásica. Total: $1,250.")] },
|
||||
{ texto: "Cambiar dirección", respuestas: [r("Sin problema. Envíame la nueva dirección completa con código postal.")] }
|
||||
]
|
||||
}),
|
||||
t("out", "09:20", "¿Cuándo llega?"),
|
||||
msg("in", "cta_url", "09:20", {
|
||||
headerMedia: ilus("En camino", "#e3eef9", "#2f6fb5", "➜"),
|
||||
body: "Tu pedido salió a reparto 🚚 La entrega estimada es *mañana entre 10:00 y 14:00*.",
|
||||
footer: "Actualizado hace 5 minutos",
|
||||
textoBoton: "Rastrear pedido",
|
||||
url: "https://example.com/rastreo"
|
||||
}),
|
||||
msg("in", "carousel", "09:21", {
|
||||
body: "Mientras llega, esto combina con tu compra:",
|
||||
cards: [
|
||||
{ media: ilus("Chamarra ligera", "#e8f0f7", "#2f6fb5", "1"), texto: "Chamarra ligera · impermeable y plegable.", textoBoton: "Me interesa", url: "", respuestas: [r("¡Buena elección! Te aparto una en talla M por 24 h.")] },
|
||||
{ media: ilus("Mochila urbana", "#eef3ea", "#4f8a3c", "2"), texto: "Mochila urbana · 20 L con compartimento para laptop.", textoBoton: "Me interesa", url: "", respuestas: [r("La mochila tiene 10 % de descuento esta semana.")] },
|
||||
{ media: ilus("Termo 750 ml", "#f7efe3", "#c07a2c", "3"), texto: "Termo de acero · 750 ml, frío 24 h.", textoBoton: "Me interesa", url: "", respuestas: [r("El termo viene en 4 colores. ¿Cuál prefieres?")] }
|
||||
]
|
||||
}),
|
||||
msg("in", "buttons", "09:22", {
|
||||
body: "¿Hay algo más en lo que te pueda ayudar?",
|
||||
botones: [
|
||||
{ texto: "Devoluciones", respuestas: [r("Tienes 30 días para devoluciones sin costo. Solo responde con tu número de pedido.")] },
|
||||
{ texto: "Hablar con alguien", respuestas: [r("Te conecto con una persona del equipo. Tiempo de espera aproximado: 3 minutos.")] },
|
||||
{ texto: "No, gracias", respuestas: [r("¡Gracias a ti! Que disfrutes tu compra 💙")] }
|
||||
]
|
||||
})
|
||||
]);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clinica",
|
||||
nombre: "Clínica · citas",
|
||||
descripcion: "Agenda de citas por especialidad, recordatorio con plantilla y documento de indicaciones.",
|
||||
datos: function () {
|
||||
return estado({ nombre: "Clínica Bienestar", avatar: avatar("C", "#1f8a7a"), subtitulo: "en línea", verificado: true }, [
|
||||
t("out", "08:40", "Hola, quiero agendar una cita"),
|
||||
msg("in", "list", "08:40", {
|
||||
header: "Agenda tu cita",
|
||||
body: "¡Hola! Soy el asistente de Clínica Bienestar. ¿Con qué especialidad necesitas tu cita?",
|
||||
footer: "Horario: lunes a sábado",
|
||||
boton: "Especialidades",
|
||||
secciones: [
|
||||
{ titulo: "Consulta", filas: [
|
||||
{ titulo: "Medicina general", descripcion: "Consulta de 30 minutos", respuestas: [r("Tengo disponibles mañana a las 10:00 y a las 12:30. ¿Cuál prefieres?")] },
|
||||
{ titulo: "Pediatría", descripcion: "Niñas y niños de 0 a 15 años", respuestas: [r("Pediatría tiene espacio el jueves a las 9:00 y a las 16:00.")] }
|
||||
] },
|
||||
{ titulo: "Estudios", filas: [
|
||||
{ titulo: "Laboratorio", descripcion: "Análisis clínicos", respuestas: [r("Para laboratorio no necesitas cita: de 7:00 a 11:00, en ayuno.")] }
|
||||
] }
|
||||
]
|
||||
}),
|
||||
t("out", "08:42", "Mañana a las 10:00"),
|
||||
msg("in", "template", "08:42", {
|
||||
headerTipo: "texto",
|
||||
headerTexto: "Cita confirmada",
|
||||
body: "Hola Laura, tu cita de *Medicina general* quedó agendada para mañana a las *10:00*. Llega 10 minutos antes con una identificación.",
|
||||
footer: "Clínica Bienestar",
|
||||
botones: [
|
||||
{ texto: "Confirmar asistencia", respuestas: [r("¡Gracias! Tu asistencia quedó confirmada ✅")] },
|
||||
{ texto: "Reprogramar", respuestas: [r("Claro. ¿Qué día te acomoda mejor?")] },
|
||||
{ texto: "Cancelar cita", respuestas: [r("Tu cita fue cancelada. Si necesitas otra, escríbenos cuando quieras.")] }
|
||||
]
|
||||
}),
|
||||
msg("in", "document", "08:43", {
|
||||
caption: "Indicaciones para tu consulta",
|
||||
filename: "indicaciones-consulta.pdf"
|
||||
}),
|
||||
msg("in", "location", "08:43", {
|
||||
latitud: "20.6597", longitud: "-103.3496",
|
||||
nombre: "Clínica Bienestar", direccion: "Calle Robles 45, Col. Jardines"
|
||||
})
|
||||
]);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "inmobiliaria",
|
||||
nombre: "Inmobiliaria · visitas",
|
||||
descripcion: "Presentación de propiedades en carrusel, filtros por zona y agenda de visita.",
|
||||
datos: function () {
|
||||
return estado({ nombre: "Horizonte Inmuebles", avatar: avatar("H", "#6b4fa3"), subtitulo: "Cuenta de empresa", verificado: false }, [
|
||||
t("out", "17:10", "Hola, vi un anuncio de departamentos en renta"),
|
||||
msg("in", "buttons", "17:10", {
|
||||
headerTipo: "imagen",
|
||||
headerMedia: ilus("Encuentra tu espacio", "#efe9f7", "#6b4fa3", "⌂"),
|
||||
body: "¡Hola! Gracias por escribirnos. ¿Qué tipo de propiedad buscas?",
|
||||
botones: [
|
||||
{ texto: "Departamento", respuestas: [r("Te muestro los departamentos disponibles esta semana 👇")] },
|
||||
{ texto: "Casa", respuestas: [r("Por ahora tenemos 2 casas en renta. Un asesor te comparte los detalles.")] },
|
||||
{ texto: "Oficina", respuestas: [r("Para oficinas te atiende nuestro equipo comercial. ¿Me compartes tu correo?")] }
|
||||
]
|
||||
}),
|
||||
msg("in", "carousel", "17:11", {
|
||||
body: "Departamentos disponibles:",
|
||||
cards: [
|
||||
{ media: ilus("2 recámaras · 78 m²", "#f1ecf8", "#6b4fa3", "A"), texto: "Zona norte · 2 recámaras, 1 baño, estacionamiento.", textoBoton: "Agendar visita", url: "", respuestas: [r("¿Qué día te gustaría visitarlo? Tenemos martes y jueves por la tarde.")] },
|
||||
{ media: ilus("1 recámara · 52 m²", "#e9f2f1", "#2f8a7e", "B"), texto: "Zona centro · 1 recámara, amueblado, cerca del metro.", textoBoton: "Agendar visita", url: "", respuestas: [r("Este se renta amueblado. ¿Te agendo una visita el sábado a las 11:00?")] },
|
||||
{ media: ilus("3 recámaras · 110 m²", "#f7efe6", "#b0763a", "C"), texto: "Zona sur · 3 recámaras, 2 baños, jardín común.", textoBoton: "Ver fotos", url: "https://example.com/propiedad", respuestas: [] }
|
||||
]
|
||||
}),
|
||||
t("out", "17:13", "El de 2 recámaras, el jueves"),
|
||||
msg("in", "template", "17:13", {
|
||||
body: "Listo, tu visita al departamento de *2 recámaras* quedó agendada el jueves a las *18:00*. Te acompaña Daniel, tu asesor.",
|
||||
footer: "Horizonte Inmuebles",
|
||||
botones: [
|
||||
{ texto: "Confirmar", respuestas: [r("¡Perfecto! Te enviamos la ubicación exacta un día antes.")] },
|
||||
{ texto: "Cambiar hora", respuestas: [r("¿Qué horario te funciona mejor el jueves?")] }
|
||||
]
|
||||
}),
|
||||
msg("in", "contact", "17:14", {
|
||||
nombre: "Asesor de ejemplo", telefono: "+52 55 0000 0000", email: "[email protected]", empresa: "Horizonte Inmuebles"
|
||||
})
|
||||
]);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "soporte",
|
||||
nombre: "Soporte · preguntas frecuentes",
|
||||
descripcion: "Menú de ayuda en lista, respuestas encadenadas y paso a una persona del equipo.",
|
||||
datos: function () {
|
||||
return estado({ nombre: "Conecta Soporte", avatar: avatar("S", "#0f7c9b"), subtitulo: "Responde en minutos", verificado: true }, [
|
||||
t("out", "11:00", "Hola, tengo un problema con mi cuenta"),
|
||||
msg("in", "list", "11:00", {
|
||||
header: "Centro de ayuda",
|
||||
body: "Hola 👋 Soy el asistente virtual. Elige el tema y te ayudo al momento.",
|
||||
footer: "Disponible 24/7",
|
||||
boton: "Ver temas",
|
||||
secciones: [
|
||||
{ titulo: "Cuenta", filas: [
|
||||
{ titulo: "Olvidé mi contraseña", descripcion: "Restablecer el acceso", respuestas: [
|
||||
r("Puedes restablecerla desde *Iniciar sesión › ¿Olvidaste tu contraseña?*"),
|
||||
r("Te llegará un correo con un enlace válido por 30 minutos.")
|
||||
] },
|
||||
{ titulo: "Cambiar mi correo", descripcion: "Actualizar datos de contacto", respuestas: [r("Ve a *Perfil › Datos de contacto* y confirma el cambio desde tu correo actual.")] }
|
||||
] },
|
||||
{ titulo: "Pagos", filas: [
|
||||
{ titulo: "Cobro no reconocido", descripcion: "Revisar un cargo", respuestas: [r("Lo revisamos contigo. Te paso con una persona del equipo de pagos.")] },
|
||||
{ titulo: "Descargar factura", descripcion: "Facturas de los últimos 12 meses", respuestas: [r("Tus facturas están en *Cuenta › Facturación*.")] }
|
||||
] }
|
||||
]
|
||||
}),
|
||||
msg("in", "buttons", "11:02", {
|
||||
body: "¿Te sirvió esta respuesta?",
|
||||
botones: [
|
||||
{ texto: "Sí, gracias", respuestas: [r("¡Qué bueno! Si necesitas algo más, aquí estamos 🙌")] },
|
||||
{ texto: "No, necesito ayuda", respuestas: [r("Te conecto con una persona del equipo. Tiempo de espera aproximado: 5 minutos.")] }
|
||||
]
|
||||
}),
|
||||
msg("in", "cta_url", "11:03", {
|
||||
body: "También puedes consultar las guías paso a paso en nuestro centro de ayuda.",
|
||||
textoBoton: "Abrir guías",
|
||||
url: "https://example.com/ayuda"
|
||||
})
|
||||
]);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "cobranza",
|
||||
nombre: "Cobranza · recordatorio",
|
||||
descripcion: "Recordatorio de pago con plantilla, opciones de pago y comprobante en documento.",
|
||||
datos: function () {
|
||||
return estado({ nombre: "Servicios Claros", avatar: avatar("S", "#3b7d4f"), subtitulo: "Cuenta de empresa", verificado: true }, [
|
||||
msg("in", "template", "10:00", {
|
||||
headerTipo: "texto",
|
||||
headerTexto: "Recordatorio de pago",
|
||||
body: "Hola Marta, te recordamos que tu pago de *$850* vence el *15 de octubre*. Evita recargos pagando a tiempo.",
|
||||
footer: "Si ya pagaste, ignora este mensaje",
|
||||
botones: [
|
||||
{ texto: "Pagar ahora", url: "https://example.com/pago", respuestas: [] },
|
||||
{ texto: "Ya pagué", respuestas: [r("¡Gracias! En cuanto se refleje tu pago te enviamos el comprobante.")] },
|
||||
{ texto: "Necesito una prórroga", respuestas: [r("Entendido. Te comparto las opciones disponibles 👇")] }
|
||||
]
|
||||
}),
|
||||
t("out", "10:05", "Necesito una prórroga"),
|
||||
msg("in", "buttons", "10:05", {
|
||||
body: "Estas son las opciones que tenemos para ti:",
|
||||
footer: "Sujeto a aprobación",
|
||||
botones: [
|
||||
{ texto: "Pagar en 2 partes", respuestas: [r("Listo, dividimos tu saldo en 2 pagos quincenales. Te enviamos el detalle por aquí.")] },
|
||||
{ texto: "Mover la fecha", respuestas: [r("Podemos mover la fecha de vencimiento hasta 7 días. ¿Te funciona?")] },
|
||||
{ texto: "Hablar con asesor", respuestas: [r("Un asesor te contactará hoy en horario de oficina.")] }
|
||||
]
|
||||
}),
|
||||
t("out", "10:07", "Pagar en 2 partes"),
|
||||
msg("in", "document", "10:08", {
|
||||
caption: "Aquí está el detalle de tus 2 pagos",
|
||||
filename: "plan-de-pagos.pdf"
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
WAM.ESCENARIOS = ESCENARIOS;
|
||||
|
||||
// Devuelve un estado CRUDO (sin normalizar) del escenario id, o null
|
||||
WAM.datosEscenario = function (id) {
|
||||
for (var i = 0; i < ESCENARIOS.length; i++) {
|
||||
if (ESCENARIOS[i].id === id) return ESCENARIOS[i].datos();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user