{ "name": "FoodSnap - Coach AI (WhatsApp)", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "wa/coach-inbound", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -500, -940 ], "id": "webhook-coach", "name": "Webhook (Whatsapp)" }, { "parameters": { "jsCode": "const body = $json.body ?? $json;\nconst data = body.data ?? {};\n\nconst remoteJid = data?.key?.remoteJid?.includes('@s.whatsapp.net') ? data.key.remoteJid : data?.key?.remoteJidAlt || '';\nconst number = remoteJid.replace(/\\D/g, '');\nconst message_id = data?.key?.id || '';\nconst text = data?.message?.conversation || data?.message?.extendedTextMessage?.text || '';\n\n// Check for image\nconst imageMessage = data?.message?.imageMessage || data?.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage || null;\n\nreturn [{\n number,\n remoteJid,\n message_id,\n text,\n hasImage: !!imageMessage,\n imageMessage,\n username: data?.pushName || 'Atleta'\n}];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -280, -940 ], "id": "normalize-inbound", "name": "Normalizar Dados" }, { "parameters": { "operation": "executeQuery", "query": "select * from check_access_by_whatsapp('{{ $json.number }}')", "options": {} }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ -60, -940 ], "id": "validate-user", "name": "Validar Usuario (RPC)", "credentials": { "postgres": { "id": "2JDD2OJz4cAsWb0J", "name": "foodsnap_supabase" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "check-user", "leftValue": "={{ $json.exists }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 160, -940 ], "id": "if-exists", "name": "Usuario Existe?" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "check-process", "leftValue": "={{ $json.can_process }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 400, -1040 ], "id": "if-quota", "name": "Tem Quota/Plano?" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "check-image", "leftValue": "={{ $node[\"Normalizar Dados\"].json.hasImage }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 640, -940 ], "id": "if-image", "name": "Tem Imagem?" }, { "parameters": { "resource": "messages-api", "instanceName": "FoodSnap", "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", "messageText": "💪 *Coach AI*: Olá! Envie uma foto do seu corpo (preferencialmente de frente, roupa de treino) para eu fazer uma análise rápida do seu biótipo e sugestão de treino.", "options_message": {} }, "type": "n8n-nodes-evolution-api.evolutionApi", "typeVersion": 1, "position": [ 860, -840 ], "id": "msg-intro", "name": "Msg Intro", "credentials": { "evolutionApi": { "id": "nGWBERcZoQWgdxgk", "name": "FoodSnap" } } }, { "parameters": { "resource": "messages-api", "instanceName": "FoodSnap", "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", "messageText": "🧐 Analisando seu físico... Um momento!", "options_message": {} }, "type": "n8n-nodes-evolution-api.evolutionApi", "typeVersion": 1, "position": [ 860, -1040 ], "id": "msg-ack", "name": "Msg Ack", "credentials": { "evolutionApi": { "id": "nGWBERcZoQWgdxgk", "name": "FoodSnap" } } }, { "parameters": { "resource": "chat-api", "operation": "get-media-base64", "instanceName": "FoodSnap", "messageId": "={{ $('Normalizar Dados').item.json.message_id }}" }, "type": "n8n-nodes-evolution-api.evolutionApi", "typeVersion": 1, "position": [ 1080, -1040 ], "id": "get-image", "name": "Baixar Imagem", "credentials": { "evolutionApi": { "id": "nGWBERcZoQWgdxgk", "name": "FoodSnap" } } }, { "parameters": { "operation": "toBinary", "sourceProperty": "data.base64", "options": {} }, "type": "n8n-nodes-base.convertToFile", "typeVersion": 1.1, "position": [ 1300, -1040 ], "id": "convert-binary", "name": "Converter Binario" }, { "parameters": { "resource": "image", "operation": "analyze", "modelId": { "__rl": true, "value": "models/gemini-pro-vision", "mode": "list", "cachedResultName": "models/gemini-pro-vision" }, "text": "=Você é um Treinador Físico de Elite e Nutricionista Esportivo.\nAnalise a imagem fornecida (foto de corpo inteiro/físico).\n\n1. Verifique se é uma foto de corpo humano válida para análise fitness. Se não, retorne \"valid_body\": false.\n2. Se for válida, estime:\n - Biótipo Predominante (Ectomorfo, Mesomorfo, Endomorfo)\n - Percentual de Gordura (BF% aproximado)\n - Nível de Massa Muscular (Baixo, Médio, Alto)\n3. Sugira:\n - Objetivo Principal (Cutting, Bulking, Manutenção)\n - Divisão de Treino Recomendada (ex: ABC, ABCDE, Upper/Lower)\n - Foco Dietético (ex: Carb Cycling, Alto Carbo, Keto)\n - Dica de Ouro (uma frase curta de impacto)\n\nResponda APENAS em JSON estrito (sem markdown):\n{\n \"valid_body\": true,\n \"biotype\": \"...\",\n \"estimated_body_fat\": 15,\n \"muscle_mass\": \"Médio\",\n \"goal\": \"Bulking\",\n \"workout\": \"...\",\n \"diet\": \"...\",\n \"tip\": \"...\"\n}", "inputType": "binary", "simplify": false, "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1, "position": [ 1520, -1100 ], "id": "analyze-gemini", "name": "Gemini Coach Analysis", "credentials": { "googlePalmApi": { "id": "T2uIVBcjJ9h8BFCC", "name": "Backup APIKEY" } } }, { "parameters": { "jsCode": "const raw = $json?.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\nconst clean = raw.replace(/```json/g, '').replace(/```/g, '').trim();\nlet data = {};\ntry { \n data = JSON.parse(clean); \n} catch(e) {\n data = { valid_body: false, error: 'Failed to parse AI' };\n}\n\nreturn [{ ...data }];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1740, -1100 ], "id": "parse-response", "name": "Parse AI JSON" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "check-validity", "leftValue": "={{ $json.valid_body }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 1960, -1100 ], "id": "if-valid-body", "name": "Corpo Valido?" }, { "parameters": { "operation": "executeQuery", "query": "insert into public.coach_analyses\n(\n user_id,\n source,\n image_url,\n ai_raw_response,\n ai_structured,\n biotype,\n estimated_body_fat,\n goal_suggestion,\n muscle_mass_level,\n used_free_quota\n)\nvalues\n(\n cast('{{ $(\"Validar Usuario & Quota\").item.json.user_id }}' as uuid),\n 'whatsapp',\n null,\n '{{ JSON.stringify($json) }}',\n '{{ JSON.stringify($json) }}',\n '{{ $json.biotype }}',\n cast({{ $json.estimated_body_fat || 0 }} as numeric),\n '{{ $json.goal }}',\n '{{ $json.muscle_mass }}',\n CASE\n WHEN {{ $(\"Validar Usuario & Quota\").item.json.plan_active }} = true THEN false\n ELSE true\n END\n)\nreturning id as analysis_id;", "options": {} }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ 2200, -1180 ], "id": "save-db", "name": "Salvar DB", "credentials": { "postgres": { "id": "2JDD2OJz4cAsWb0J", "name": "foodsnap_supabase" } } }, { "parameters": { "resource": "messages-api", "instanceName": "FoodSnap", "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", "messageText": "=⚡ *Análise Coach AI*\n\n🧬 *Biótipo*: {{$json.biotype}}\n⚖️ *Gordura (BF)*: ~{{$json.estimated_body_fat}}%\n💪 *Massa Muscular*: {{$json.muscle_mass}}\n\n🎯 *Objetivo Sugerido*: {{$json.goal}}\n🏋️ *Treino*: {{$json.workout}}\n🥗 *Dieta*: {{$json.diet}}\n\n💡 *Dica*: {{$json.tip}}\n\n_Para ver o plano completo, acesse o App!_", "options_message": {} }, "type": "n8n-nodes-evolution-api.evolutionApi", "typeVersion": 1, "position": [ 2420, -1180 ], "id": "reply-success", "name": "Responder Resultado", "credentials": { "evolutionApi": { "id": "nGWBERcZoQWgdxgk", "name": "FoodSnap" } } }, { "parameters": { "resource": "messages-api", "instanceName": "FoodSnap", "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", "messageText": "⚠️ Não consegui identificar um físico claro nesta foto. Tente enviar uma foto de corpo inteiro ou tronco, com boa iluminação.", "options_message": {} }, "type": "n8n-nodes-evolution-api.evolutionApi", "typeVersion": 1, "position": [ 2200, -980 ], "id": "reply-invalid", "name": "Responder Invalido", "credentials": { "evolutionApi": { "id": "nGWBERcZoQWgdxgk", "name": "FoodSnap" } } }, { "parameters": { "resource": "messages-api", "instanceName": "FoodSnap", "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", "messageText": "🚫 *Limite do Coach Atingido*\n\nVocê já usou suas 3 análises de Coach gratuitas. Assine o plano PRO para avaliações ilimitadas! 🚀", "options_message": {} }, "type": "n8n-nodes-evolution-api.evolutionApi", "typeVersion": 1, "position": [ 640, -1140 ], "id": "reply-limit", "name": "Msg Limite", "credentials": { "evolutionApi": { "id": "nGWBERcZoQWgdxgk", "name": "FoodSnap" } } } ], "connections": { "Webhook (Whatsapp)": { "main": [ [ { "node": "Normalizar Dados", "type": "main", "index": 0 } ] ] }, "Normalizar Dados": { "main": [ [ { "node": "Validar Usuario & Quota", "type": "main", "index": 0 } ] ] }, "Validar Usuario & Quota": { "main": [ [ { "node": "Usuario Existe?", "type": "main", "index": 0 } ] ] }, "Usuario Existe?": { "main": [ [ { "node": "Tem Quota/Plano?", "type": "main", "index": 0 } ] ] }, "Tem Quota/Plano?": { "main": [ [ { "node": "Tem Imagem?", "type": "main", "index": 0 } ], [ { "node": "Msg Limite", "type": "main", "index": 0 } ] ] }, "Tem Imagem?": { "main": [ [ { "node": "Msg Ack", "type": "main", "index": 0 } ], [ { "node": "Msg Intro", "type": "main", "index": 0 } ] ] }, "Msg Ack": { "main": [ [ { "node": "Baixar Imagem", "type": "main", "index": 0 } ] ] }, "Baixar Imagem": { "main": [ [ { "node": "Converter Binario", "type": "main", "index": 0 } ] ] }, "Converter Binario": { "main": [ [ { "node": "Gemini Coach Analysis", "type": "main", "index": 0 } ] ] }, "Gemini Coach Analysis": { "main": [ [ { "node": "Parse AI JSON", "type": "main", "index": 0 } ] ] }, "Parse AI JSON": { "main": [ [ { "node": "Corpo Valido?", "type": "main", "index": 0 } ] ] }, "Corpo Valido?": { "main": [ [ { "node": "Salvar DB", "type": "main", "index": 0 } ], [ { "node": "Responder Invalido", "type": "main", "index": 0 } ] ] }, "Salvar DB": { "main": [ [ { "node": "Responder Resultado", "type": "main", "index": 0 } ] ] } } }