fix(webhook): move Coach state update outside setTimeout + re-read state before Food Scan + simplify menu
Root cause: the COACH_FRONT state was being saved inside setTimeout, creating a race condition where the next message arrives before state is persisted. Now state is saved synchronously before returning. Also added a safety guard that re-reads state from DB right before entering Food Scan flow, making it impossible for coach photos to be incorrectly processed as food. Menu simplified to two clear options: Analisar Prato / Coach Corporal.
This commit is contained in:
parent
55fe0308cd
commit
eb2ceab354
1 changed files with 77 additions and 69 deletions
|
|
@ -813,9 +813,7 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
|
||||||
state === "IDLE" &&
|
state === "IDLE" &&
|
||||||
(interactiveId === "action_coach" || (textMessage && /coach|treino|avalia[çc][aã]o/i.test(textMessage)))
|
(interactiveId === "action_coach" || (textMessage && /coach|treino|avalia[çc][aã]o/i.test(textMessage)))
|
||||||
) {
|
) {
|
||||||
// Offload long-running task to background
|
// Verificações de limite/cooldown ANTES de mudar o state (blocking)
|
||||||
setTimeout(async () => {
|
|
||||||
// Verificar quantas análises coach o usuário já fez
|
|
||||||
const { count: coachUsed } = await supabase
|
const { count: coachUsed } = await supabase
|
||||||
.from("coach_analyses")
|
.from("coach_analyses")
|
||||||
.select("*", { count: "exact", head: true })
|
.select("*", { count: "exact", head: true })
|
||||||
|
|
@ -823,7 +821,6 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
|
||||||
|
|
||||||
const FREE_COACH_LIMIT = 1;
|
const FREE_COACH_LIMIT = 1;
|
||||||
|
|
||||||
// Se passou do limite gratuito, verifica se tem plano pago
|
|
||||||
if ((coachUsed || 0) >= FREE_COACH_LIMIT) {
|
if ((coachUsed || 0) >= FREE_COACH_LIMIT) {
|
||||||
const { data: entitlement } = await supabase
|
const { data: entitlement } = await supabase
|
||||||
.from("user_entitlements")
|
.from("user_entitlements")
|
||||||
|
|
@ -848,7 +845,6 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar cooldown de 7 dias (apenas para usuários pagos com muitas análises)
|
|
||||||
const { data: lastAnalysis } = await supabase
|
const { data: lastAnalysis } = await supabase
|
||||||
.from("coach_analyses")
|
.from("coach_analyses")
|
||||||
.select("created_at")
|
.select("created_at")
|
||||||
|
|
@ -873,21 +869,23 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *** CRITICAL: Salvar state ANTES de retornar, fora do setTimeout ***
|
||||||
await supabase
|
await supabase
|
||||||
.from("whatsapp_conversations")
|
.from("whatsapp_conversations")
|
||||||
.update({ state: "COACH_FRONT", temp_data: {} })
|
.update({ state: "COACH_FRONT", temp_data: {} })
|
||||||
.eq("phone_number", senderNumber);
|
.eq("phone_number", senderNumber);
|
||||||
|
|
||||||
// Mensagem 1: Introdução calorosa
|
console.log(`[WH] State changed to COACH_FRONT for ${senderNumber}`);
|
||||||
|
|
||||||
|
// Mensagens de instrução (fire-and-forget, não bloqueia a resposta Meta)
|
||||||
|
setTimeout(async () => {
|
||||||
await sendWhatsAppMessage(
|
await sendWhatsAppMessage(
|
||||||
remoteJid,
|
remoteJid,
|
||||||
"🤖 *Coach IA Ativado!*\n\nVou analisar sua composição corporal e montar um protocolo *Titan 100% personalizado* de treino e dieta.\n\nO processo é super simples: você envia apenas *1 foto realista* e a IA processa o resto! ⚡"
|
"🤖 *Coach IA Ativado!*\n\nVou analisar sua composição corporal e montar um protocolo *Titan 100% personalizado* de treino e dieta.\n\nO processo é super simples: você envia apenas *1 foto* e a IA processa o resto! ⚡"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pequena pausa para não parecer robótico
|
|
||||||
await new Promise(r => setTimeout(r, 1500));
|
await new Promise(r => setTimeout(r, 1500));
|
||||||
|
|
||||||
// Mensagem 2: Instrução da primeira foto
|
|
||||||
await sendWhatsAppMessage(
|
await sendWhatsAppMessage(
|
||||||
remoteJid,
|
remoteJid,
|
||||||
"📸 *ENVIE SUA FOTO*\n\nTire uma selfie no espelho ou foto de frente do seu corpo.\n\n✅ Boa iluminação\n✅ Sem camisa (homens) ou de Top/Regata (mulheres)\n✅ Mostrando do pescoço até a cintura/joelhos"
|
"📸 *ENVIE SUA FOTO*\n\nTire uma selfie no espelho ou foto de frente do seu corpo.\n\n✅ Boa iluminação\n✅ Sem camisa (homens) ou de Top/Regata (mulheres)\n✅ Mostrando do pescoço até a cintura/joelhos"
|
||||||
|
|
@ -1113,6 +1111,21 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 6. Food Scan Flow (IDLE) ────────────────────────────────
|
// ── 6. Food Scan Flow (IDLE) ────────────────────────────────
|
||||||
|
// SAFETY: Re-read state from DB to prevent race conditions
|
||||||
|
const { data: freshConv } = await supabase
|
||||||
|
.from("whatsapp_conversations")
|
||||||
|
.select("state")
|
||||||
|
.eq("phone_number", senderNumber)
|
||||||
|
.order("updated_at", { ascending: false })
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const freshState = freshConv?.[0]?.state || "IDLE";
|
||||||
|
|
||||||
|
if (freshState !== "IDLE") {
|
||||||
|
console.warn(`[WH] SAFETY BLOCK: state is ${freshState}, NOT IDLE. Aborting Food Scan.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state === "IDLE") {
|
if (state === "IDLE") {
|
||||||
console.log(`[WH] Entering Food Scan flow. isImage=${isImage}`);
|
console.log(`[WH] Entering Food Scan flow. isImage=${isImage}`);
|
||||||
|
|
||||||
|
|
@ -1162,19 +1175,14 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
|
||||||
await sendWhatsAppListMenu(
|
await sendWhatsAppListMenu(
|
||||||
remoteJid,
|
remoteJid,
|
||||||
"FoodSnap IA",
|
"FoodSnap IA",
|
||||||
"Fala ai! Sou a FoodSnap, a IA projetada para revolucionar seu fisico.\n\nEscolha uma opcao no menu abaixo, ou se preferir, *mande direto a foto do que voce esta comendo* e eu calculo tudo na hora!",
|
"Ola! Sou a FoodSnap IA.\n\n*Envie a foto do seu prato* direto aqui que eu analiso na hora, ou escolha uma opcao no menu abaixo.",
|
||||||
"Abrir Menu",
|
"Abrir Menu",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
title: "Scanners Diarios",
|
title: "O que posso fazer",
|
||||||
rows: [
|
rows: [
|
||||||
{ id: "action_help_photo", title: "Dicas de Fotografia", description: "Veja como tirar a foto perfeita" },
|
{ id: "action_help_photo", title: "Analisar Prato", description: "Envie foto da comida e vejo calorias" },
|
||||||
]
|
{ id: "action_coach", title: "Coach Corporal IA", description: "Analiso seu fisico e monto treino" },
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Especialistas",
|
|
||||||
rows: [
|
|
||||||
{ id: "action_coach", title: "Coach de Saude", description: "Gerar plano de treino individual" },
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue