🏗️ Arquitectura General — NuPromoBot NEXUS
🎯 Objetivo del Sistema
Bot de WhatsApp para Nu Promo que gestiona el ciclo completo: desde primer contacto hasta levantamiento de pedidos, integrado bidirecionalmente con Google Sheets vía GAS como backend intermedio. Preparado para futura migración a Odoo CRM.
Stack: Botpress (Nodos Autónomos + Router) → GAS Webhooks → Google Sheets (12 Hojas) → WhatsApp Cloud API
📐 Capas del Sistema
CAPA 1 — Conversación
Botpress: 11 nodos (N0-N10), FAQ autónomo, calificación 6-3-1, captura de pedidos, escalamiento a humano
CAPA 2 — Backend
Google Apps Script: 18 endpoints, lógica de rotación PCT, validaciones, notificaciones, cron jobs
CAPA 3 — Datos
Google Sheets: 12 hojas (Leads, Cotizaciones, Pedidos, Productos, Asesores, Config, Logs, FAQ, Muestras, Pipeline, Métricas, Plantillas)
CAPA 4 — Comunicación
WhatsApp: Templates F#, mensajes interactivos, notificaciones push a asesores, confirmaciones automáticas
🔄 Flujo Macro del Sistema
👋 WhatsApp Entrante
→
N0: Extractor
→
N1: Router
→
N2-N8: Flujos
→
GAS Webhook
→
📋 Google Sheets
📋 Sheets
→
GAS Respuesta
→
Botpress Continúa
→
N9: Notifica Asesor
→
N10: Cierre/HITL
⚠️ SEGURIDAD NEXUS: Todas las URLs de webhooks y secretos van en Configuration Variables de Botpress (env.GAS_WEBHOOK_URL, env.GAS_SECRET). NUNCA hardcodear en nodos.
🤖 Arquitectura de Nodos Botpress
Convenciones:
Standard = Nodo estándar sin IA
Autonomous = Nodo con IA + Write Access
Router = Nodo decisor de ruta
Expression = Card de condición lógica
Execute = Card de código JS/webhook
Hook = Before/After Incoming
Standard Execute Code
Propósito: Captura datos WhatsApp del usuario al primer mensaje.
Variables que escribe:
user_phone ← event.tags.conversation['whatsapp:userPhone']
user_name ← event.tags.conversation['whatsapp:userName'] || 'Visitante'
conversation_id ← event.conversationId
entry_timestamp ← new Date().toISOString()
source_channel ← 'whatsapp'
Execute Card → GAS:
POST env.GAS_WEBHOOK_URL → action: check_returning_user
Si usuario existe → carga historial, asesor previo, etapa pipeline
✅ Transición: Siempre → N1_ROUTER
Router Autonomous
Propósito: Analiza intención del mensaje y enruta al nodo correcto.
Intenciones detectadas:
🔹 FAQ → N2_FAQ (precios, mínimos, envíos, ubicación, etc.)
🔹 COTIZACION → N3_QUALIFIER (quiere cotizar producto)
🔹 PEDIDO_SEGUIMIENTO → N4_ORDER_TRACKER
🔹 MUESTRA_FISICA → N5_SAMPLES
🔹 HABLAR_ASESOR → N9_HITL_BRIDGE
🔹 CATALOGO → N6_CATALOG
🔹 RECLAMO → N7_COMPLAINTS
🔹 INFO_EMPRESA → N2_FAQ (sub-ruta /info)
Contexto del Router:
"Eres el clasificador de Nu Promo, una agencia de artículos promocionales. Analiza el mensaje y determina la intención. Si el usuario menciona producto específico, precio o cantidad → COTIZACION. Si pregunta info general → FAQ."
Autonomous Execute Code
Propósito: Responde las 15 FAQ pre-programadas con IA contextual.
Lógica:
1. IA clasifica sub-intención → mapea a FAQ_ID (1-15)
2. Execute Card → GAS get_faq_response trae template
3. IA personaliza respuesta con nombre del usuario
4. Si detecta interés comercial → transiciona a N3
Knowledge Base:
Archivo KB con las 15 respuestas + políticas + filosofía servicio al cliente Mini Guía
✅ Transiciones: FAQ pura → Loop N2 | Interés comercial → N3 | Asesor → N9
Autonomous Execute Code
Propósito: Califica lead usando Método 6-3-1 completo de Nu Promo.
Fase 1 — Datos del Cliente (pre-6):
client_name, client_company, client_email, client_phone
Fase 2 — Las 6 Preguntas Clave:
Q1: ¿Qué producto necesitas?
Q2: ¿Cuándo lo necesitas?
Q3: ¿Dónde lo necesitas? (local/foráneo)
Q4: ¿Cuántos necesitas?
Q5: ¿Cuánto quieres invertir? (presupuesto)
Q6: ¿Tienes logotipo o identidad corporativa?
Fase 3 — 3 Preguntas de Valor:
Q7: Cuéntame del evento, ¿de qué se trata?
Q8: ¿A quién va dirigido?
Q9: ¿Qué quieres que sientan al recibirlo?
Fase 4 — 1 Pregunta Estratégica:
Q10: Pregunta contextual de relación (varía por proyecto)
⚡ Al completar → POST GAS create_lead + assign_advisor → N8_QUOTE
Standard Execute Code
Propósito: Consulta estado de pedido por número o teléfono.
Lógica:
1. Pide #pedido o usa user_phone como lookup
2. Execute → GAS get_order_status
3. Retorna: etapa, fecha estimada, asesor asignado
4. Si hay problema → escala a N9_HITL
Estados: Cotizado → Anticipo Recibido → En Producción → Muestra Virtual → En Decorado → Listo → Enviado/Entregado
Autonomous Expression
Propósito: Evalúa elegibilidad de muestra según política.
Reglas de Negocio:
✅ Proyecto ≥ $20,000 MXN
✅ Muestras ≤ 3% del valor cotizado
✅ Lead en etapa ≥ 5 (cotización filtrada)
✅ Muestra con logo → tiene costo (abonable)
Expression Cards:
IF lead_stage >= 5 AND quote_total >= 20000 → Aprueba
IF sample_value > quote_total * 0.03 → Rechaza auto
ELSE → Escala a asesor vía N9
Autonomous Execute Code
Propósito: Ayuda al usuario a explorar catálogo y encontrar productos.
Capacidades:
🔍 Búsqueda por categoría, color, rango de precio
📸 Envía links de producto con imagen desde web
💰 Consulta existencias en tiempo real (si integrado)
📙 Comparte links: nupromo.mx + FlippingBook
GAS Endpoint:
search_products → busca en hoja Productos por keyword/categoría
✅ Si elige producto → auto-transición a N3 con producto pre-cargado
Autonomous Execute Code
Propósito: Captura reclamo con contexto emocional + escala inmediato.
Flujo:
1. IA con tono empático (Mini Guía: "Vamos a resolverlo juntos")
2. Captura: tipo de problema, #pedido, descripción
3. POST GAS create_complaint con prioridad ALTA
4. Notifica asesor + supervisor inmediatamente
5. Auto-transición a N9_HITL con contexto
⚠️ Filosofía: "El cliente NO siempre tiene la razón, pero siempre debe sentirse escuchado"
Standard Execute Code
Propósito: Genera cotización preliminar y la registra.
Flujo:
1. Recibe datos de N3 (producto, cantidad, decorado)
2. Execute → GAS generate_quote
3. GAS calcula: precio unitario × cantidad + decorado
4. Retorna cotización con desglose al usuario
5. Pregunta: ¿Deseas formalizar? → Asigna asesor
Datos cotización:
quote_id, productos, cantidades, precios, subtotal, IVA, total, decorado, tiempo estimado
📝 Mínimo de compra: $5,000 + IVA (combinable). Bot valida antes de generar.
Standard Execute Code
Propósito: Asigna asesor por rotación PCT y transfiere conversación.
Lógica MAGNETIK:
1. POST GAS assign_advisor con datos del lead
2. GAS ejecuta rotación PCT (déficit vs cuota)
3. Retorna: nombre_asesor, phone_asesor, email
4. Bot envía mensaje split al asesor vía WA
5. Bot informa al cliente quién le atenderá
6. Activa HITL en Botpress
Mensaje al asesor:
MSG1: "🔔 Nuevo lead asignado"
MSG2: Resumen 6-3-1 completo
MSG3: Link a Sheets del lead
Autonomous Execute Code
Propósito: Cierra interacción con NPS + programa seguimiento.
Flujo:
1. Cuando asesor cierra HITL → bot retoma
2. "¿Hay algo más en lo que pueda ayudarte?"
3. Si no → Pregunta NPS (1-10)
4. POST GAS close_interaction
5. Programa follow-up automático (3 días)
Frases de cierre (Mini Guía):
"Queremos que te encante el resultado" 🎯
"En Nu Promo construimos relaciones, no solo vendemos productos" 🤝
📊 Mapa de Variables Botpress
👤 Variables de Usuario (Sesión)
user_phone
string
Teléfono WhatsApp del usuario
user_name
string
Nombre WhatsApp o capturado
conversation_id
string
ID de conversación Botpress
entry_timestamp
string
ISO timestamp de entrada
source_channel
string
Canal origen (whatsapp)
is_returning
boolean
Si es usuario recurrente
previous_advisor
string
Asesor previo si existe
lead_id
string
ID del lead en Sheets
🎯 Variables Método 6-3-1
client_name
string
Nombre completo del prospecto
client_company
string
Empresa del prospecto
client_email
string
Correo electrónico
client_phone
string
Teléfono de contacto
q1_producto
string
¿Qué producto necesitas?
q2_cuando
string
¿Cuándo lo necesitas?
q3_donde
enum
local | foráneo | ambos
q4_cantidad
number
¿Cuántos necesitas?
q5_presupuesto
string
¿Cuánto quieres invertir?
q6_logotipo
boolean
¿Tiene logotipo/identidad?
q7_evento
string
Cuéntame del evento
q8_audiencia
string
¿A quién va dirigido?
q9_emocion
string
¿Qué quieres que sientan?
q10_estrategica
string
Pregunta de relación profunda
qualification_phase
number
Fase actual (0-4)
qualification_score
number
Score calculado del lead
📋 Variables de Cotización y Pedido
quote_id
string
ID cotización (NP-YYYYMMDD-###)
quote_products
object[]
Array de productos cotizados
quote_subtotal
number
Subtotal antes de IVA
quote_iva
number
IVA calculado (16%)
quote_total
number
Total con IVA
quote_decorado
string
Técnica de decorado seleccionada
order_id
string
ID pedido confirmado
order_status
enum
Estado actual del pedido
delivery_date_est
string
Fecha estimada entrega
payment_status
enum
pendiente|anticipo|liquidado
🧑💼 Variables de Asesor / Sistema
assigned_advisor
string
Nombre asesor asignado
advisor_phone
string
WhatsApp del asesor
advisor_email
string
Email del asesor
pipeline_stage
number
Etapa pipeline (1-6)
nps_score
number
Score NPS del cliente
interaction_count
number
Número de interacciones
last_faq_id
string
Última FAQ consultada
current_intent
enum
Intención actual clasificada
sample_eligible
boolean
Si califica para muestra gratis
gas_response
object
Última respuesta de GAS
⚠️ Write Access: Los nodos Autonomous (N1, N2, N3, N5, N6, N7, N10) tienen "Allow write access" habilitado para modificar variables durante la conversación.
📋 Esquema Google Sheets — 12 Hojas
lead_id
STRING
ID único auto-generado (NP-L-XXXX)
timestamp
DATETIME
Fecha/hora de creación
phone
STRING
WhatsApp del lead
name
STRING
Nombre completo
email
STRING
Correo electrónico
source
STRING
Canal de origen
q1_producto
STRING
Respuesta Q1
q2_cuando
STRING
Respuesta Q2
q3_donde
STRING
Respuesta Q3
q4_cantidad
NUMBER
Respuesta Q4
q5_presupuesto
STRING
Respuesta Q5
q6_logotipo
BOOLEAN
Respuesta Q6
q7_evento
STRING
Respuesta Q7
q8_audiencia
STRING
Respuesta Q8
q9_emocion
STRING
Respuesta Q9
q10_estrategica
STRING
Respuesta Q10
qualification_score
NUMBER
Score calculado (0-100)
pipeline_stage
NUMBER
Etapa pipeline (1-6)
assigned_advisor
STRING
Asesor asignado
status
STRING
activo|ganado|perdido|dormido
notes
STRING
Notas adicionales
last_interaction
DATETIME
Última interacción
is_80_20
BOOLEAN
Si es cliente estratégico (Pulpo)
quote_id
STRING
NP-Q-YYYYMMDD-###
timestamp
DATETIME
Fecha creación
products_json
JSON
[{code, name, qty, unit_price, decoration}]
subtotal
NUMBER
Subtotal sin IVA
decoration_type
STRING
Técnica de decorado
delivery_est
STRING
8-12 días hábiles est.
status
STRING
borrador|enviada|aceptada|rechazada|expirada
advisor
STRING
Asesor que gestiona
valid_until
DATE
Vigencia (15 días default)
order_id
STRING
NP-O-YYYYMMDD-###
quote_id
STRING
FK → COTIZACIONES
order_date
DATETIME
Fecha de pedido
products_json
JSON
Productos confirmados
advance_paid
NUMBER
Anticipo recibido (50%)
balance_due
NUMBER
Saldo pendiente
payment_status
STRING
pendiente|anticipo|liquidado
production_status
STRING
7 etapas de producción
virtual_sample_approved
BOOLEAN
Muestra virtual autorizada
delivery_type
STRING
local_zmg|foraneo
shipping_address
STRING
Dirección de envío
tracking_number
STRING
Guía de envío
estimated_delivery
DATE
Fecha estimada entrega
delivered_date
DATE
Fecha real entrega
advisor
STRING
Asesor responsable
code, name, category, brand, unit_price_mayoreo, min_qty, colors, decoration_suggested, image_url, stock_status, nupromo_url
advisor_id, name, phone, email, is_active, daily_quota, leads_today, leads_month, pct_deficit, specialty, schedule, is_available
param_key, param_value, description — Incluye: min_order_amount, iva_rate, default_delivery_days, sample_max_pct, sample_min_project, quote_validity_days, working_hours
log_id, timestamp, action, lead_id, advisor, source_node, payload_json, status, error_msg
faq_id (1-15), command (/precios, /minimo...), category, response_template, links_json, emoji_set, active
sample_id, lead_id, quote_id, product_code, type (sin_logo|con_logo), value, approved, approved_by, shipped_date, status
history_id, lead_id, from_stage, to_stage, timestamp, changed_by, notes
date, total_leads, total_quotes, total_orders, conversion_rate, avg_ticket, avg_nps, top_product, top_advisor, response_time_avg
template_id, trigger_event, message_body, variables_used, target (client|advisor|supervisor), active
⚡ Google Apps Script — 18 Endpoints
🔌 Arquitectura GAS
Todos los endpoints se ejecutan vía doPost(e) con un action param que enruta al handler correcto. Autenticación por secret header que valida contra CONFIG.gas_secret.
URL Base: https://script.google.com/macros/s/{DEPLOY_ID}/exec
Method: POST (siempre) | Content-Type: application/json
Auth: Header x-gas-secret = env.GAS_SECRET de Botpress
📡 Lista de Endpoints
Method
Action
Descripción
Hoja Target
POST
check_returning_user
Busca usuario por phone, retorna historial
LEADS
POST
create_lead
Crea lead nuevo con datos 6-3-1
LEADS, LOGS
POST
update_lead
Actualiza campos de lead existente
LEADS
POST
assign_advisor
Rotación PCT MAGNETIK → asigna asesor
ASESORES, LEADS
POST
get_faq_response
Trae template FAQ por ID
FAQ_TEMPLATES
POST
search_products
Busca productos por keyword/categoría
PRODUCTOS
POST
generate_quote
Calcula y genera cotización
COTIZACIONES
POST
update_quote_status
Cambia estado de cotización
COTIZACIONES
POST
create_order
Convierte cotización en pedido
PEDIDOS
POST
get_order_status
Consulta estado de pedido
PEDIDOS
POST
update_order_status
Actualiza estado producción
PEDIDOS, LOGS
POST
check_sample_eligibility
Valida elegibilidad muestra física
MUESTRAS, CONFIG
POST
request_sample
Registra solicitud de muestra
MUESTRAS
POST
create_complaint
Registra queja con prioridad
LOGS
POST
close_interaction
Cierra interacción + NPS
LEADS, METRICAS
POST
update_pipeline_stage
Mueve lead a nueva etapa
PIPELINE_HISTORY
POST
get_daily_metrics
Retorna métricas del día
METRICAS
POST
send_notification
Dispara notificación WA a asesor
PLANTILLAS_WA
🔄 Algoritmo de Rotación PCT (MAGNETIK)
El endpoint assign_advisor ejecuta:
function assignAdvisor(leadData) {
const advisors = getActiveAdvisors();
const config = getConfig();
if (leadData.previous_advisor) {
const prev = advisors.find(a => a.name === leadData.previous_advisor);
if (prev && prev.is_available) return prev;
}
advisors.forEach(a => {
a.pct_ideal = 100 / advisors.length;
a.pct_actual = (a.leads_month / totalLeadsMonth) * 100;
a.deficit = a.pct_ideal - a.pct_actual;
});
const available = advisors
.filter(a => a.is_available && a.leads_today < a.daily_quota)
.sort((a, b) => b.deficit - a.deficit);
const selected = available[0];
updateAdvisorCount(selected);
logAssignment(leadData.lead_id, selected.name);
return selected;
}
🔀 Flujos Conversacionales Detallados
🟢 Flujo 1: Primer Contacto → Calificación Completa
👋 "Hola"
→
N0: Extrae phone/name
→
GAS: check_returning
→
¿Nuevo?
SÍ nuevo
→
Bienvenida + ¿En qué puedo ayudarte?
→
N1: Router clasifica
Quiere cotizar
→
N3: Pide datos cliente
→
N3: 6 preguntas producto
→
N3: 3 preguntas valor
→
N3: 1 estratégica
GAS: create_lead
→
GAS: assign_advisor
→
N8: Genera cotización
→
N9: Transfiere a asesor
→
HITL Activo
🔵 Flujo 2: Cliente Recurrente → Seguimiento
📱 Mensaje entrante
→
N0: Detecta phone
→
GAS: check_returning ✅
→
"¡Hola [nombre]! Bienvenido de vuelta"
¿Tiene pedido activo?
→
"Tu pedido [#] está en: [estado]"
→
N1: ¿Qué necesita?
Seguimiento
→
N4: Order Tracker
||
Nuevo pedido
→
N3: Qualifier (con asesor previo)
||
Asesor directo
→
N9: HITL con mismo asesor
🟡 Flujo 3: FAQ → Detección de Oportunidad Comercial
"¿Cuál es su mínimo?"
→
N1: FAQ detected
→
N2: FAQ /minimo
→
Respuesta + "¿Te gustaría cotizar?"
SÍ
→
N3: Qualifier
||
NO
→
"¿Algo más? Estoy para ayudarte"
→
N10: Cierre
🔴 Flujo 4: Queja → Resolución
"Tengo un problema con mi pedido"
→
N1: RECLAMO
→
N7: Empático + captura
→
GAS: create_complaint
GAS: notify supervisor
→
N9: HITL prioritario
→
Asesor toma control
🟣 Flujo 5: Muestra Física
"¿Puedo ver una muestra?"
→
N5: ¿Tiene cotización?
SÍ + ≥$20K
→
GAS: check_sample ✅
→
¿Sin logo o con logo?
Sin logo = GRATIS
→
GAS: request_sample
||
Con logo = COSTO (abonable)
→
Informa costo + pide OK
NO califica
→
"La muestra tiene costo, sujeto a aprobación"
→
N9: Escala a asesor
💬 Sistema FAQ — 15 Respuestas Rápidas
📌 Implementación: Las FAQ se almacenan en la hoja FAQ_TEMPLATES y se cargan vía GAS endpoint get_faq_response. El nodo N2 usa IA para personalizar la respuesta con el nombre del usuario y contexto. Las respuestas originales se usan como base/template.
/precios (FAQ #1)
Intent: precio, lista de precios, cuánto cuesta
Acción: Dirige a web + ofrece cotización personalizada
Nota clave: "Precios de mayoreo, no incluyen impresión"
/minimo (FAQ #2)
Intent: mínimo de compra, pedido mínimo
Respuesta: $5,000 + IVA (puede combinar productos)
Transición: Si interesado → N3
/catalogos (FAQ #3)
Intent: catálogo, ver productos
Links: nupromo.mx + FlippingBook
Mínimo mencionado: $2,000 + IVA
/ubicacion (FAQ #4)
Intent: dónde están, dirección
Dirección: Francisco Javier Alegre 446, GDL
Nota: "Necesario agendar cita"
/envios (FAQ #5)
Intent: envíos, cómo entregan
Local: Gratis dentro ZMG
Foráneo: Paquetería, cuenta y riesgo del cliente
/tiempos (FAQ #6)
Intent: tiempo entrega, cuánto tarda
Respuesta: 8-12 días hábiles post muestra+anticipo
/decorados (FAQ #7)
Intent: decorado, impresión, serigrafía
Link: nupromo.mx/decorados
/costoimpresiones (FAQ #8)
Intent: cuánto cuesta imprimir
Respuesta: Varía por producto/cantidad/técnica
Transición: Pide código → N8
/especial (FAQ #9)
Intent: producto especial, personalizado
Respuesta: Pide imagen + ¿revisaste catálogo?
/existencias (FAQ #10)
Intent: stock, hay existencia
Respuesta: Web muestra stock real-time
Si agotado: "Pregunta fechas de llegada"
/maquila (FAQ #11)
Intent: servicio de maquila
Respuesta: No disponible — solo producto vendido por Nu Promo
/presupuesto (FAQ #12)
Intent: cuánto debería gastar
Respuesta: Pregunta precio target → mejor propuesta
/condiciones (FAQ #13)
Intent: condiciones de pago, políticas
Clave: 50/50 anticipo, sin cambios post-autorización
/ficha (FAQ #14)
Intent: ficha técnica, especificaciones
Respuesta: Disponible en web al dar click en producto
/info (FAQ #15)
Intent: quiénes son, qué hacen
Respuesta: Presentación completa + web + catálogo + video
17,000+ artículos disponibles
⚠️ Discrepancia detectada: FAQ #2 dice mínimo $5,000 + IVA pero FAQ #3 (/catalogos) menciona $2,000 + IVA. Recomendación: Unificar en CONFIG.min_order_amount y que GAS inyecte el valor correcto en ambas respuestas.
📈 Pipeline CRM — 6 Etapas
🔄 Pipeline Visual
1️⃣ CONTACTOPrimer mensaje WA
2️⃣ CALIFICADO6-3-1 completado
3️⃣ COTIZADOCotización enviada
4️⃣ NEGOCIACIÓNAsesor en seguimiento
5️⃣ PEDIDOAnticipo recibido
6️⃣ ENTREGADOPedido completado
📊 Triggers de Cambio de Etapa
1→2: Bot completa Método 6-3-1 → GAS create_lead con score > 0
2→3: Cotización generada y enviada → GAS generate_quote
3→4: Asesor marca "cliente interesado" en Sheets o Botpress HITL command
4→5: Anticipo registrado → GAS create_order con payment_status='anticipo'
5→6: Pedido entregado/enviado → GAS update_order_status status='entregado'
Cada cambio de etapa: Se registra en PIPELINE_HISTORY + se recalculan METRICAS diarias automáticamente via trigger en GAS.
📦 Sub-Pipeline de Producción (dentro de Etapa 5)
5a
Anticipo50% recibido
5b
ProducciónEn fabricación
5c
Muestra VirtualAprobación cliente
5d
DecoradoImpresión/personalización
5e
ListoPago 50% restante
5f
EnviadoEn tránsito
🎯 Clasificación de Clientes (Estrategia Pulpo 80/20)
El campo is_80_20 en LEADS marca clientes estratégicos que reciben tratamiento especial basado en la Guía de Llamadas 80/20.
🥇 AAA
Recompra frecuente, alto ticket. 4 llamadas/día rotación mensual.
🥈 AA
Potencial alto, necesita nurturing. Seguimiento quincenal.
🥉 A
Compra ocasional. Bot maneja + escalamiento bajo demanda.
📋 Nuevo
Sin historial. Calificar vía 6-3-1 → clasificar.
🎯 Método 6-3-1 — Flujo Conversacional del Bot
📋 Implementación en Nodo N3 (QUALIFIER)
El nodo N3 es Autonomous con Write Access. La IA conduce la conversación de forma natural, no como cuestionario rígido. Captura respuestas en variables mientras mantiene fluidez conversacional.
📝 Fase 0 — Datos del Cliente (Pre-calificación)
Contexto IA: "Antes de ayudarte con tu cotización, necesito algunos datos. Prometo que será rápido 😊"
Captura: nombre, empresa, correo, teléfono (si no tenemos de WhatsApp)
Variable: qualification_phase = 0
Validación: Email con regex, teléfono con formato MX
🔷 Fase 1 — Armar el Producto (6 Preguntas)
Q1 — ¿Qué producto?
Variable: q1_producto
Si menciona código → búsqueda en PRODUCTOS
Si vago → ofrece catálogo/categorías
Q2 — ¿Cuándo lo necesitas?
Variable: q2_cuando
Valida vs tiempos de entrega (8-12 días)
Si urgente → flag para asesor
Q3 — ¿Dónde lo necesitas?
Variable: q3_donde (enum)
local → envío gratis ZMG
foráneo → cuenta del cliente
Q4 — ¿Cuántos necesitas?
Variable: q4_cantidad (number)
Valida vs mínimo de compra
Si < mínimo → informa y sugiere
Q5 — ¿Cuánto quieres invertir?
Variable: q5_presupuesto
Si no tiene → "OK, te armo opciones"
Si tiene → valida vs mínimo $5K
Q6 — ¿Tienes logotipo?
Variable: q6_logotipo (boolean)
Si SÍ → "Compártelo cuando gustes"
Si NO → "No te preocupes, te ayudamos"
Variable: qualification_phase = 1 al completar
🎯 Fase 2 — Construir Valor (3 Preguntas)
Contexto IA: "Excelente, ahora quiero entender mejor tu proyecto para darte la mejor propuesta posible 🎁"
Q7 — Cuéntame del evento
Variable: q7_evento
"¿De qué se trata el evento/campaña?"
Permite entender contexto completo
Q8 — ¿A quién va dirigido?
Variable: q8_audiencia
Empleados, clientes, ejecutivos, público general
Impacta en selección de producto
Q9 — ¿Qué quieres que sientan?
Variable: q9_emocion
"¿Qué quieres que piensen o sientan al recibirlo?"
🎁 Genera experiencia emocional memorable
Variable: qualification_phase = 2
🤝 Fase 3 — Crear Relaciones (1 Pregunta Estratégica)
Contexto IA: La IA selecciona UNA pregunta contextual basada en lo que ha aprendido del prospecto.
💡 "¿Cómo está estructurado su equipo?"
💡 "¿Cuántas personas forman parte de la organización?"
💡 "¿Cuál es tu rol dentro de la empresa?"
💡 "¿Cómo suelen organizar este tipo de eventos?"
💡 "¿Qué te gustaría lograr con este proyecto?"
Variable: qualification_phase = 3 → Score se calcula → POST GAS create_lead
📊 Cálculo de Score (qualification_score)
function calculateScore(lead) {
let score = 0;
if (lead.company) score += 5;
if (lead.email) score += 5;
if (lead.q7_evento) score += 5;
if (lead.q9_emocion) score += 5;
const budget = parseBudget(lead.q5_presupuesto);
if (budget >= 50000) score += 30;
else if (budget >= 20000) score += 20;
else if (budget >= 5000) score += 10;
if (isUrgent(lead.q2_cuando)) score += 15;
else score += 5;
if (lead.q4_cantidad >= 500) score += 20;
else if (lead.q4_cantidad >= 100) score += 10;
else score += 5;
if (lead.q6_logotipo) score += 10;
if (lead.q10_estrategica) score += 5;
return Math.min(score, 100);
}
📦 Política de Muestras Físicas — Automatización
📋 Reglas de Negocio Implementadas en N5
✅ Muestra GRATIS (sin logo)
• Proyecto ≥ $20,000 MXN
• Muestras ≤ 3% del cotizado
• Lead en etapa ≥ 5 (post-cotización filtrada)
• Prospecto calificado con interés real
• Aprobación automática vía bot
💰 Muestra CON LOGO (costo)
• Siempre tiene costo para el cliente
• Costo abonable al total si se concreta
• Se informa desde el inicio (transparencia)
• Requiere OK explícito del cliente
• Registrada en hoja MUESTRAS
❌ NO califica
• Proyecto < $20,000 MXN
• Sin cotización previa
• Lead en etapa < 5
• Acción: Muestra con costo, sujeta a aprobación interna
• Escala a asesor vía N9
🔧 Producto CUSTOM
• Creado desde cero para el cliente
• Límite del 3% aplica OBLIGATORIO
• Mayor esfuerzo de desarrollo
• Siempre requiere aprobación interna
• Flag especial en MUESTRAS
⚡ Lógica de Expression Cards en N5
IF workflow.quote_id !== null && workflow.quote_id !== ''
→ Continúa a Expression 2
ELSE → "Para solicitar muestra necesitas una cotización activa. ¿Te ayudo a cotizar?"
→ Transición a N3_QUALIFIER
IF workflow.quote_total >= 20000
→ Continúa a Expression 3
ELSE → "La muestra tiene costo en este caso. Te comunico con tu asesor."
→ Transición a N9_HITL
IF sampleValue <= workflow.quote_total * 0.03
→ ¿Sin logo o con logo?
ELSE → "El valor de la muestra excede nuestro límite. Te comunico con tu asesor."
→ Transición a N9_HITL
IF sampleType === 'sin_logo'
→ POST GAS request_sample (approved: true)
→ "¡Excelente! Tu muestra sin costo ha sido aprobada 🎉"
ELSE
→ Informa costo + pide confirmación
→ "La muestra con logo tiene un costo de $X, que será abonable a tu pedido final."
🧩 Estructura Código GAS — NuPromoBot_MASTER.gs
📄 Estructura de Archivos GAS
📁 NuPromoBot_MASTER
├── 00_Config.gs
├── 01_Router.gs
├── 02_Auth.gs
├── 03_Leads.gs
├── 04_Advisor.gs
├── 05_Quotes.gs
├── 06_Orders.gs
├── 07_Products.gs
├── 08_FAQ.gs
├── 09_Samples.gs
├── 10_Notifications.gs
├── 11_Pipeline.gs
├── 12_Metrics.gs
├── 13_Complaints.gs
├── 14_Cron.gs
└── 15_Utils.gs
⚡ 01_Router.gs — Entry Point
function doPost(e) {
try {
const data = JSON.parse(e.postData.contents);
const secret = e.parameter.secret || data.secret;
if (!validateSecret(secret)) {
return jsonResponse({ success: false, error: 'UNAUTHORIZED' }, 401);
}
const action = data.action;
const handlers = {
'check_returning_user': () => checkReturningUser(data),
'create_lead': () => createLead(data),
'update_lead': () => updateLead(data),
'assign_advisor': () => assignAdvisor(data),
'get_faq_response': () => getFaqResponse(data),
'search_products': () => searchProducts(data),
'generate_quote': () => generateQuote(data),
'update_quote_status': () => updateQuoteStatus(data),
'create_order': () => createOrder(data),
'get_order_status': () => getOrderStatus(data),
'update_order_status': () => updateOrderStatus(data),
'check_sample_eligibility': () => checkSampleEligibility(data),
'request_sample': () => requestSample(data),
'create_complaint': () => createComplaint(data),
'close_interaction': () => closeInteraction(data),
'update_pipeline_stage': () => updatePipelineStage(data),
'get_daily_metrics': () => getDailyMetrics(data),
'send_notification': () => sendNotification(data),
};
if (!handlers[action]) {
return jsonResponse({ success: false, error: `Unknown action: ${action}` });
}
const result = handlers[action]();
logAction(action, data.lead_id || 'N/A', 'SUCCESS', result);
return jsonResponse({ success: true, data: result });
} catch (err) {
logAction('ERROR', '', 'ERROR', err.message);
return jsonResponse({ success: false, error: err.message });
}
}
function jsonResponse(obj, code = 200) {
return ContentService
.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
🔧 14_Cron.gs — Automatizaciones Programadas
function cronDailyMetrics() {
calculateAndSaveMetrics(yesterday());
resetAdvisorDailyCounts();
}
function cronFollowUpReminders() {
const staleLeads = getLeadsWithoutInteraction(3);
staleLeads.forEach(lead => {
sendNotification({
target: 'advisor',
advisor: lead.assigned_advisor,
template: 'follow_up_reminder',
lead_name: lead.name,
lead_id: lead.lead_id
});
});
}
function cronQuoteExpiry() {
const expiring = getQuotesExpiringIn(2);
expiring.forEach(quote => {
sendNotification({
target: 'advisor',
advisor: quote.advisor,
template: 'quote_expiring',
quote_id: quote.quote_id,
client_name: quote.client_name
});
});
}
function cronWeeklyReport() {
const report = generateWeeklyReport();
sendNotification({
target: 'supervisor',
template: 'weekly_report',
data: report
});
}
📡 Execute Card en Botpress (Ejemplo N0)
const phone = event.tags?.conversation?.['whatsapp:userPhone'] || '';
const name = event.tags?.conversation?.['whatsapp:userName'] || 'Visitante';
workflow.user_phone = phone;
workflow.user_name = name;
workflow.conversation_id = event.conversationId;
workflow.entry_timestamp = new Date().toISOString();
const response = await axios.post(env.GAS_WEBHOOK_URL, {
action: 'check_returning_user',
secret: env.GAS_SECRET,
phone: phone
});
if (response.data.success && response.data.data.found) {
workflow.is_returning = true;
workflow.lead_id = response.data.data.lead_id;
workflow.previous_advisor = response.data.data.assigned_advisor;
workflow.pipeline_stage = response.data.data.pipeline_stage;
workflow.client_name = response.data.data.name;
} else {
workflow.is_returning = false;
}