{
  "meta": {
    "templateId": "7756",
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "c142f5dd-fbeb-4b25-a0fe-f684546e6eff",
      "name": "Download Voice Message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2144,
        1072
      ],
      "parameters": {
        "fileId": "={{ $('Telegram Trigger').item.json.message.voice.file_id }}",
        "resource": "file",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "e15bb617-61da-4620-936d-15b0b5cf9c59",
      "name": "Input Message Router1",
      "type": "n8n-nodes-base.switch",
      "position": [
        1776,
        1136
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Text",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "fcb767ee-565e-4b56-a54e-6f97f739fc24",
                    "operator": {
                      "type": "string",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $('Telegram Trigger').item.json.message.text }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Voice Message",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c1016c40-f8f2-4e08-8ec8-5cdb88f5c87a",
                    "operator": {
                      "type": "object",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $('Telegram Trigger').item.json.message.voice }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Image",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "f8150ac7-eea4-4658-8da9-f7a1c88a471d",
                    "operator": {
                      "type": "string",
                      "operation": "exists",
                      "singleValue": true
                    },
                    "leftValue": "={{ $('Telegram Trigger').item.json.message.photo[0].file_id }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "ignoreCase": false,
          "fallbackOutput": "extra",
          "allMatchingOutputs": true
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "68f15a08-cbe2-4ad9-9cfe-8b7a7c60787c",
      "name": "get_message (text)",
      "type": "n8n-nodes-base.set",
      "position": [
        2144,
        912
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "801ec600-22ad-4a94-a2b4-ae72eb271df0",
              "name": "message",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.text }}"
            },
            {
              "id": "263071fb-bcdf-42b0-bb46-71b75fa0bf2a",
              "name": "chat_id",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "6561a9a0-2d00-424d-ba86-8852dcb7e935",
      "name": "Download IMAGE",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2144,
        1248
      ],
      "parameters": {
        "fileId": "={{ $('Telegram Trigger').item.json.message.photo[3]?.file_id || $('Telegram Trigger').item.json.message.photo[2]?.file_id || $('Telegram Trigger').item.json.message.photo[1]?.file_id }}",
        "resource": "file",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b975dcce-68d8-4e3d-b50e-330f3f63812d",
      "name": "Analyze image",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        2480,
        1248
      ],
      "parameters": {
        "text": "=You are a Nutrition Vision Assistant. Think like a food scientist and registered dietitian. Reason silently and do not reveal your steps. From a single food photo, identify the meal components, estimate portion weight in grams per component using geometric/visual cues, then compute total calories, protein, carbs, and fat.\n\nEstimation method (internal only; do not output these steps)\n\nIdentify components: list the main foods (e.g., chicken breast, white rice, mixed salad, sauce).\n\nChoose references: map each component to a standard reference food.\n\nEstimate volume/size: use visible objects for scale (plate \u2248 27 cm diameter, fork tines \u2248 3.5 cm, spoon bowl \u2248 5\u20136 cm). Approximate shapes (cuboid, cylinder, dome) to get volume in ml (\u2248 cm\u00b3).\n\nConvert to grams (densities, g/ml): meats 1.05; cooked rice 0.66; cooked pasta 0.60; potato/solid starchy veg 0.80; leafy salad 0.15; sauces creamy 1.00; oils 0.91. If the image clearly suggests deep-fried or glossy/oily coating, account for added oil.\n\nMacros & energy per 100 g (reference values):\n\nWhite rice, cooked: 130 kcal, P 2.7, C 28, F 0.3\n\nPasta, cooked: 131 kcal, P 5.0, C 25, F 1.1\n\nChicken breast, cooked skinless: 165 kcal, P 31, C 0, F 3.6\n\nSalmon, cooked: 208 kcal, P 20, C 0, F 13\n\nLean ground beef (\u224810% fat), cooked: 217 kcal, P 26, C 0, F 12\n\nBlack beans, cooked: 132 kcal, P 8.9, C 23.7, F 0.5\n\nPotato, baked: 93 kcal, P 2.5, C 21, F 0.1\n\nLettuce/leafy salad: 15 kcal, P 1.4, C 2.9, F 0.2\n\nAvocado: 160 kcal, P 2, C 9, F 15\n\nBread (white): 265 kcal, P 9, C 49, F 3.2\n\nEgg, cooked: 155 kcal, P 13, C 1.1, F 11\n\nCheddar cheese: 403 kcal, P 25, C 1.3, F 33\n\nOlive oil: 884 kcal, P 0, C 0, F 100\n(If a food is not listed, pick the closest standard equivalent.)\n\nHidden oil & sauces: if pan-fried or visibly glossy, add ~1 tablespoon oil = 13.5 g = 120 kcal = 13.5 g fat per clearly coated serving; adjust by visual coverage.\n\nSum totals: compute grams per component \u00d7 (per-100 g macros/energy) and add all components.\n\nValidation: enforce Calories \u2248 4\u00d7Protein + 4\u00d7Carbs + 9\u00d7Fat. If off by >8%, adjust fat first (oil/sauce most variable), then carbs (starches), keeping protein consistent with visible lean mass.\n\nRounding: round all final totals to integers. Never output ranges or decimals.\n\nOutput rules (must follow exactly)\n\nPlain text only.\n\nUse this exact structure and field order.\n\nValues are numbers only (no units, no \u201cg\u201d or \u201ckcal\u201d), no extra text, no JSON, no notes.\n\nMeal Description: [short description]\nCalories: [number]\nProteins: [number]\nCarbs: [number]\nFat: [number]",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-2.5-pro",
          "cachedResultName": "models/gemini-2.5-pro"
        },
        "options": {},
        "resource": "image",
        "inputType": "binary",
        "operation": "analyze"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "04b43855-1600-424d-a2ae-458fb72a0525",
      "name": "Analyze voice message",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        2480,
        1072
      ],
      "parameters": {
        "text": "What's in this audio message from telegram user?",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-2.5-pro",
          "cachedResultName": "models/gemini-2.5-pro"
        },
        "options": {},
        "resource": "audio",
        "inputType": "binary",
        "operation": "analyze"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3d5d43cc-4639-4d46-a095-b6de79d3aede",
      "name": "get_message (Audio/Video message)",
      "type": "n8n-nodes-base.set",
      "position": [
        2656,
        1072
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d8935452-fe20-469d-a68d-1aad056cb8dd",
              "name": "message",
              "type": "string",
              "value": "=Voice message description:{{ $json.candidates?.[0]?.content?.parts?.[0]?.text || $json.content?.parts?.[0]?.text }}"
            },
            {
              "id": "93f1bba1-1180-404a-93ca-c34cf1d1b7ac",
              "name": "chat_id",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "60e92d69-3fd6-4311-ba91-ea445adf9727",
      "name": "get_message (Media  message)",
      "type": "n8n-nodes-base.set",
      "position": [
        2656,
        1248
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d8935452-fe20-469d-a68d-1aad056cb8dd",
              "name": "message",
              "type": "string",
              "value": "=Content:\n{{ $json.content.parts[0].text }}"
            },
            {
              "id": "53e34499-7dad-4f94-aa7d-f778321f13f4",
              "name": "chat_id",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "563fd16d-25d2-4496-94eb-d6a8be600e92",
      "name": "Typing\u2026",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1296,
        1040
      ],
      "parameters": {
        "chatId": "={{ $json.message.chat.id }}",
        "operation": "sendChatAction"
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a07eaf7d-8e67-41c8-b5bd-367fbc5917f1",
      "name": "Fix mime",
      "type": "n8n-nodes-base.code",
      "position": [
        2320,
        1072
      ],
      "parameters": {
        "jsCode": "// --- Mapa Extendido de Tipos MIME ---\n// Una lista completa para cubrir la mayor\u00eda de los formatos de archivo comunes.\nconst mimeMap = {\n  // --- Formatos de Documentos ---\n  'pdf': 'application/pdf',\n  'txt': 'text/plain',\n  'rtf': 'application/rtf',\n  'csv': 'text/csv',\n  'html': 'text/html',\n  'htm': 'text/html',\n  'json': 'application/json',\n  'xml': 'application/xml', // 'text/xml' tambi\u00e9n es v\u00e1lido pero 'application/xml' es m\u00e1s com\u00fan\n  'yaml': 'application/x-yaml',\n  'yml': 'application/x-yaml',\n\n  // --- Formatos de Microsoft Office ---\n  'doc': 'application/msword',\n  'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n  'xls': 'application/vnd.ms-excel',\n  'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n  'ppt': 'application/vnd.ms-powerpoint',\n  'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n  'pub': 'application/vnd.ms-publisher',\n\n  // --- Formatos de OpenOffice / LibreOffice ---\n  'odt': 'application/vnd.oasis.opendocument.text',\n  'ods': 'application/vnd.oasis.opendocument.spreadsheet',\n  'odp': 'application/vnd.oasis.opendocument.presentation',\n  'odg': 'application/vnd.oasis.opendocument.graphics',\n\n  // --- Formatos de Apple iWork ---\n  'pages': 'application/vnd.apple.pages',\n  'numbers': 'application/vnd.apple.numbers',\n  'key': 'application/vnd.apple.keynote',\n\n  // --- Formatos de Imagen ---\n  'png': 'image/png',\n  'jpg': 'image/jpeg',\n  'jpeg': 'image/jpeg',\n  'gif': 'image/gif',\n  'webp': 'image/webp',\n  'svg': 'image/svg+xml',\n  'bmp': 'image/bmp',\n  'ico': 'image/vnd.microsoft.icon',\n  'tif': 'image/tiff',\n  'tiff': 'image/tiff',\n  'heic': 'image/heic',\n  'heif': 'image/heif',\n\n  // --- Formatos de Audio ---\n  'mp3': 'audio/mpeg',\n  'wav': 'audio/wav',\n  'oga': 'audio/ogg',\n  'ogg': 'audio/ogg',\n  'flac': 'audio/flac',\n  'm4a': 'audio/mp4',\n  'aac': 'audio/aac',\n  'opus': 'audio/opus',\n  'wma': 'audio/x-ms-wma',\n  'mid': 'audio/midi',\n  'midi': 'audio/midi',\n\n  // --- Formatos de Video ---\n  'mp4': 'video/mp4',\n  'mov': 'video/quicktime',\n  'webm': 'video/webm',\n  'mpeg': 'video/mpeg',\n  'mpg': 'video/mpeg',\n  'avi': 'video/x-msvideo',\n  'wmv': 'video/x-ms-wmv',\n  'flv': 'video/x-flv',\n  'mkv': 'video/x-matroska',\n\n  // --- Formatos de Archivos y Compresi\u00f3n ---\n  'zip': 'application/zip',\n  'rar': 'application/vnd.rar',\n  '7z': 'application/x-7z-compressed',\n  'tar': 'application/x-tar',\n  'gz': 'application/gzip',\n  'bz2': 'application/x-bzip2',\n\n  // --- Otros Formatos ---\n  'epub': 'application/epub+zip',\n  'ics': 'text/calendar',\n  'vcf': 'text/vcard',\n  'js': 'text/javascript',\n  'css': 'text/css',\n  'sh': 'application/x-sh',\n  'py': 'text/x-python',\n};\n\n// --- L\u00f3gica de Procesamiento (sin cambios) ---\n\n// Obtenemos todos los items que llegan al nodo\nconst items = $input.all();\n\n// Iteramos sobre cada item para procesarlo\nfor (const item of items) {\n  // Verificamos que el item tenga datos binarios para procesar\n  if (item.binary && item.binary['data']) {\n    // Obtenemos el nombre del archivo de forma segura\n    const fileName = item.binary['data'].fileName || '';\n    if (!fileName) {\n      continue; // Si no hay nombre de archivo, pasamos al siguiente item\n    }\n\n    // Extraemos la extensi\u00f3n del archivo de forma robusta\n    const extension = fileName.slice((fileName.lastIndexOf(\".\") - 1 >>> 0) + 2).toLowerCase();\n\n    // Buscamos la extensi\u00f3n en nuestro mapa\n    const newMimeType = mimeMap[extension];\n\n    // Si encontramos una coincidencia en el mapa, actualizamos el mimeType\n    if (newMimeType) {\n      if(item.binary['data'].mimeType !== newMimeType) {\n        console.log(`Cambiando mimeType para '${fileName}' de '${item.binary['data'].mimeType}' a '${newMimeType}'.`);\n        item.binary['data'].mimeType = newMimeType;\n      }\n    }\n  }\n}\n\n// Devolvemos todos los items, modificados o no\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "96ff38b5-261b-4359-bfec-8f48db43c005",
      "name": "Fix mime5",
      "type": "n8n-nodes-base.code",
      "position": [
        2320,
        1248
      ],
      "parameters": {
        "jsCode": "// --- Mapa Extendido de Tipos MIME ---\n// Una lista completa para cubrir la mayor\u00eda de los formatos de archivo comunes.\nconst mimeMap = {\n  // --- Formatos de Documentos ---\n  'pdf': 'application/pdf',\n  'txt': 'text/plain',\n  'rtf': 'application/rtf',\n  'csv': 'text/csv',\n  'html': 'text/html',\n  'htm': 'text/html',\n  'json': 'application/json',\n  'xml': 'application/xml', // 'text/xml' tambi\u00e9n es v\u00e1lido pero 'application/xml' es m\u00e1s com\u00fan\n  'yaml': 'application/x-yaml',\n  'yml': 'application/x-yaml',\n\n  // --- Formatos de Microsoft Office ---\n  'doc': 'application/msword',\n  'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n  'xls': 'application/vnd.ms-excel',\n  'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n  'ppt': 'application/vnd.ms-powerpoint',\n  'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n  'pub': 'application/vnd.ms-publisher',\n\n  // --- Formatos de OpenOffice / LibreOffice ---\n  'odt': 'application/vnd.oasis.opendocument.text',\n  'ods': 'application/vnd.oasis.opendocument.spreadsheet',\n  'odp': 'application/vnd.oasis.opendocument.presentation',\n  'odg': 'application/vnd.oasis.opendocument.graphics',\n\n  // --- Formatos de Apple iWork ---\n  'pages': 'application/vnd.apple.pages',\n  'numbers': 'application/vnd.apple.numbers',\n  'key': 'application/vnd.apple.keynote',\n\n  // --- Formatos de Imagen ---\n  'png': 'image/png',\n  'jpg': 'image/jpeg',\n  'jpeg': 'image/jpeg',\n  'gif': 'image/gif',\n  'webp': 'image/webp',\n  'svg': 'image/svg+xml',\n  'bmp': 'image/bmp',\n  'ico': 'image/vnd.microsoft.icon',\n  'tif': 'image/tiff',\n  'tiff': 'image/tiff',\n  'heic': 'image/heic',\n  'heif': 'image/heif',\n\n  // --- Formatos de Audio ---\n  'mp3': 'audio/mpeg',\n  'wav': 'audio/wav',\n  'oga': 'audio/ogg',\n  'ogg': 'audio/ogg',\n  'flac': 'audio/flac',\n  'm4a': 'audio/mp4',\n  'aac': 'audio/aac',\n  'opus': 'audio/opus',\n  'wma': 'audio/x-ms-wma',\n  'mid': 'audio/midi',\n  'midi': 'audio/midi',\n\n  // --- Formatos de Video ---\n  'mp4': 'video/mp4',\n  'mov': 'video/quicktime',\n  'webm': 'video/webm',\n  'mpeg': 'video/mpeg',\n  'mpg': 'video/mpeg',\n  'avi': 'video/x-msvideo',\n  'wmv': 'video/x-ms-wmv',\n  'flv': 'video/x-flv',\n  'mkv': 'video/x-matroska',\n\n  // --- Formatos de Archivos y Compresi\u00f3n ---\n  'zip': 'application/zip',\n  'rar': 'application/vnd.rar',\n  '7z': 'application/x-7z-compressed',\n  'tar': 'application/x-tar',\n  'gz': 'application/gzip',\n  'bz2': 'application/x-bzip2',\n\n  // --- Otros Formatos ---\n  'epub': 'application/epub+zip',\n  'ics': 'text/calendar',\n  'vcf': 'text/vcard',\n  'js': 'text/javascript',\n  'css': 'text/css',\n  'sh': 'application/x-sh',\n  'py': 'text/x-python',\n};\n\n// --- L\u00f3gica de Procesamiento (sin cambios) ---\n\n// Obtenemos todos los items que llegan al nodo\nconst items = $input.all();\n\n// Iteramos sobre cada item para procesarlo\nfor (const item of items) {\n  // Verificamos que el item tenga datos binarios para procesar\n  if (item.binary && item.binary['data']) {\n    // Obtenemos el nombre del archivo de forma segura\n    const fileName = item.binary['data'].fileName || '';\n    if (!fileName) {\n      continue; // Si no hay nombre de archivo, pasamos al siguiente item\n    }\n\n    // Extraemos la extensi\u00f3n del archivo de forma robusta\n    const extension = fileName.slice((fileName.lastIndexOf(\".\") - 1 >>> 0) + 2).toLowerCase();\n\n    // Buscamos la extensi\u00f3n en nuestro mapa\n    const newMimeType = mimeMap[extension];\n\n    // Si encontramos una coincidencia en el mapa, actualizamos el mimeType\n    if (newMimeType) {\n      if(item.binary['data'].mimeType !== newMimeType) {\n        console.log(`Cambiando mimeType para '${fileName}' de '${item.binary['data'].mimeType}' a '${newMimeType}'.`);\n        item.binary['data'].mimeType = newMimeType;\n      }\n    }\n  }\n}\n\n// Devolvemos todos los items, modificados o no\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "29051108-72c1-49da-8b51-25350affe0de",
      "name": "get_error_message1",
      "type": "n8n-nodes-base.set",
      "position": [
        2144,
        1408
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d8935452-fe20-469d-a68d-1aad056cb8dd",
              "name": "message",
              "type": "string",
              "value": "=It was not possible to process the file.File type not supported."
            },
            {
              "id": "38ba2498-2141-4a04-a22a-64563fe2ee6f",
              "name": "chat_id",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "41cf82ed-b4f4-4224-93b2-e2c96f4c0f4c",
      "name": "Send a text message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3680,
        1136
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "additionalFields": {
          "parse_mode": "MarkdownV2",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "938dbd1b-a566-4bcf-9c9c-7abf90174f8b",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        3072,
        1312
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ad29c874-9686-41aa-af26-5db22169f1dd",
      "name": "Simple Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        3200,
        1328
      ],
      "parameters": {
        "sessionKey": "={{ $json.chat_id }}",
        "sessionIdType": "customKey"
      },
      "typeVersion": 1.3
    },
    {
      "id": "dcbd0b50-3cb5-4e3e-be26-026199676bb7",
      "name": "MarkdownV2",
      "type": "n8n-nodes-base.code",
      "position": [
        3520,
        1136
      ],
      "parameters": {
        "jsCode": "/**\n * MarkdownV2-safe formatter + auto-chunker for Telegram (n8n Code node)\n * --------------------------------------------------------------------\n * - Allows: *bold*, _italic_, ||spoiler||, [label](url)\n * - Escapes everything else for Telegram MarkdownV2\n * - Validates/normalizes URLs\n * - Converts \"# Heading\" lines to bold titles\n * - Splits long messages into <= 4096-char chunks (uses a 4000-char budget)\n * - Outputs one item per chunk so the Telegram node sends all parts\n *\n * Recommended: Run this node in \"Run Once for All Items\".\n */\n\nconst MAX_TELEGRAM = 4096;\nconst SAFE_BUDGET = 4000; // small margin to avoid edge overflows\n\n// ============ MarkdownV2 helpers ============\nfunction escapeMarkdownV2(text) {\n  if (!text) return '';\n  return String(text).replace(/([\\\\_*[\\]()~`>#+\\-=|{}.!])/g, '\\\\$1');\n}\n\nfunction escapeForUrl(url) {\n  return String(url).replace(/[)\\\\]/g, '\\\\$&');\n}\n\nfunction normalizeAndValidateUrl(url) {\n  let raw = String(url || '').trim();\n  try {\n    const u = new URL(raw);\n    return u.toString();\n  } catch {}\n  // Try https:// for bare domains\n  const domainLike = /^[a-z0-9.-]+\\.[a-z]{2,}([/:?#].*)?$/i.test(raw);\n  if (domainLike) {\n    try {\n      const u2 = new URL('https://' + raw);\n      return u2.toString();\n    } catch {}\n  }\n  return null;\n}\n\nfunction normalizeHeadings(text) {\n  // Turn \"# Title\" \u2192 \"*Title*\"\n  return text.replace(/^(#{1,6})\\s+(.*)$/gm, (m, hashes, title) => `*${title.trim()}*`);\n}\n\nfunction normalizeCommonMd(text) {\n  return String(text)\n    .replace(/\\*\\*([\\s\\S]*?)\\*\\*/g, '*$1*') // **bold** \u2192 *bold*\n    .replace(/__([\\s\\S]*?)__/g, '_$1_');    // __italic__ \u2192 _italic_\n}\n\n/**\n * Convert incoming text to Telegram-safe MarkdownV2.\n */\nfunction processMarkdownV2Safe(inputText) {\n  if (!inputText) return '';\n\n  let text = normalizeCommonMd(String(inputText));\n  text = normalizeHeadings(text);\n\n  const placeholders = { links: [], bolds: [], italics: [], spoilers: [] };\n\n  // Links: keep safe via placeholders during escaping\n  text = text.replace(/\\[([^\\]\\n]+)\\]\\(([^)]+)\\)/g, (m, label, url) => {\n    const normalizedUrl = normalizeAndValidateUrl(url);\n    if (!normalizedUrl) return escapeMarkdownV2(label);\n    const idx = placeholders.links.length;\n    const ph = `\u27ecL${idx}\u27ed`;\n    const safeLabel = escapeMarkdownV2(label);\n    const safeUrl = escapeForUrl(normalizedUrl);\n    placeholders.links.push(`[${safeLabel}](${safeUrl})`);\n    return ph;\n  });\n\n  // Bold\n  text = text.replace(/\\*([\\s\\S]+?)\\*/g, (m, inner) => {\n    const idx = placeholders.bolds.length;\n    const ph = `\u27ecB${idx}\u27ed`;\n    placeholders.bolds.push(`*${escapeMarkdownV2(inner)}*`);\n    return ph;\n  });\n\n  // Italic\n  text = text.replace(/_([\\s\\S]+?)_/g, (m, inner) => {\n    const idx = placeholders.italics.length;\n    const ph = `\u27ecI${idx}\u27ed`;\n    placeholders.italics.push(`_${escapeMarkdownV2(inner)}_`);\n    return ph;\n  });\n\n  // Spoilers\n  text = text.replace(/\\|\\|([\\s\\S]+?)\\|\\|/g, (m, inner) => {\n    const idx = placeholders.spoilers.length;\n    const ph = `\u27ecS${idx}\u27ed`;\n    placeholders.spoilers.push(`||${escapeMarkdownV2(inner)}||`);\n    return ph;\n  });\n\n  // Escape everything else\n  text = escapeMarkdownV2(text);\n\n  // Restore placeholders\n  placeholders.links.forEach((md, i) => { text = text.replace(`\u27ecL${i}\u27ed`, md); });\n  placeholders.bolds.forEach((md, i) => { text = text.replace(`\u27ecB${i}\u27ed`, md); });\n  placeholders.italics.forEach((md, i) => { text = text.replace(`\u27ecI${i}\u27ed`, md); });\n  placeholders.spoilers.forEach((md, i) => { text = text.replace(`\u27ecS${i}\u27ed`, md); });\n\n  return text;\n}\n\n// ============ Chunking helpers ============\n/**\n * Split text into Telegram-safe chunks <= maxLen.\n * Prefers paragraph boundaries, then sentence boundaries, then words.\n * Falls back to hard cuts only when unavoidable (e.g., extremely long URL).\n */\nfunction chunkForTelegram(text, maxLen = SAFE_BUDGET) {\n  if (!text || text.length <= maxLen) return [text || ''];\n\n  const parts = [];\n  let buffer = '';\n\n  const flush = () => {\n    if (buffer) {\n      parts.push(buffer);\n      buffer = '';\n    }\n  };\n\n  // 1) Paragraph-level packing\n  const paragraphs = text.split(/\\n{2,}/);\n  for (const pRaw of paragraphs) {\n    const p = pRaw; // keep paragraph as-is\n    const candidate = buffer ? buffer + '\\n\\n' + p : p;\n    if (candidate.length <= maxLen) {\n      buffer = candidate;\n      continue;\n    }\n    if (p.length <= maxLen) {\n      flush();\n      buffer = p;\n      continue;\n    }\n\n    // 2) Sentence-level packing (paragraph is still too big)\n    flush();\n    const sentences = p.split(/(?<=[.!?\u2026])\\s+(?=[^\\s])/u);\n    let sBuf = '';\n    for (const s of sentences) {\n      const sCandidate = sBuf ? sBuf + ' ' + s : s;\n      if (sCandidate.length <= maxLen) {\n        sBuf = sCandidate;\n        continue;\n      }\n      if (s.length <= maxLen) {\n        if (sBuf) parts.push(sBuf);\n        sBuf = s;\n        continue;\n      }\n\n      // 3) Word-level packing (sentence is still too big)\n      if (sBuf) { parts.push(sBuf); sBuf = ''; }\n      let wBuf = '';\n      const words = s.split(/\\s+/);\n      for (const w of words) {\n        const wCandidate = wBuf ? wBuf + ' ' + w : w;\n        if (wCandidate.length <= maxLen) {\n          wBuf = wCandidate;\n          continue;\n        }\n        if (w.length <= maxLen) {\n          if (wBuf) parts.push(wBuf);\n          wBuf = w;\n          continue;\n        }\n        // 4) Hard split (extremely long token, e.g., massive URL)\n        if (wBuf) { parts.push(wBuf); wBuf = ''; }\n        const re = new RegExp(`.{1,${maxLen}}`, 'g');\n        const hardPieces = w.match(re) || [];\n        parts.push(...hardPieces);\n      }\n      if (wBuf) parts.push(wBuf);\n    }\n    if (sBuf) parts.push(sBuf);\n  }\n  if (buffer) parts.push(buffer);\n\n  // Final safety pass: trim chunks that might still exceed MAX_TELEGRAM\n  return parts.flatMap(part => {\n    if (part.length <= MAX_TELEGRAM) return [part];\n    const re = new RegExp(`.{1,${SAFE_BUDGET}}`, 'g');\n    return part.match(re) || [];\n  });\n}\n\n// ============ Main ============\nconst inputItems = $input.all();\nconst out = [];\n\nfor (const item of inputItems) {\n  const j = item.json || {};\n  const raw =\n    j.message ?? j.output ?? j.text ?? j.content ?? '';\n\n  const formatted = processMarkdownV2Safe(raw);\n  const chunks = chunkForTelegram(formatted, SAFE_BUDGET);\n\n  chunks.forEach((chunk, idx) => {\n    out.push({\n      json: {\n        ...j,\n        message: chunk,\n        message_part_index: idx + 1,\n        message_parts_total: chunks.length,\n      },\n      binary: item.binary,\n    });\n  });\n}\n\nreturn out;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9e4623d1-a1d6-426b-bb97-186edb14f3f5",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        1472,
        1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "b9e63bbf-24e6-424b-ba4e-6acd3f17b57c",
              "operator": {
                "type": "number",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.User_ID }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1d9a7fa5-5b3c-4531-a268-99ec7befc78f",
      "name": "Registered?",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        1296,
        1200
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
              "lookupColumn": "User_ID"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit#gid=0",
          "cachedResultName": "Profile"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Dm_YOUR_AWS_SECRET_KEY_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit?usp=drivesdk",
          "cachedResultName": "Cal AI"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "2a6e36c8-12fc-48c2-a909-40ffbe07f6f9",
      "name": "Send a text message1",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2480,
        1776
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
        "additionalFields": {
          "parse_mode": "MarkdownV2",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6d1ab7a7-b072-45d0-ad0d-984d09f8b371",
      "name": "Google Gemini Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1872,
        1968
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3c36bb0c-84d9-43b2-83c0-69e36226c098",
      "name": "Simple Memory1",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        2032,
        2032
      ],
      "parameters": {
        "sessionKey": "={{ $json.chat_id }}",
        "sessionIdType": "customKey"
      },
      "typeVersion": 1.3
    },
    {
      "id": "f371ad0b-bf0b-4927-a169-9b56cc8fce63",
      "name": "MarkdownV",
      "type": "n8n-nodes-base.code",
      "position": [
        2320,
        1776
      ],
      "parameters": {
        "jsCode": "/**\n * MarkdownV2-safe formatter + auto-chunker for Telegram (n8n Code node)\n * --------------------------------------------------------------------\n * - Allows: *bold*, _italic_, ||spoiler||, [label](url)\n * - Escapes everything else for Telegram MarkdownV2\n * - Validates/normalizes URLs\n * - Converts \"# Heading\" lines to bold titles\n * - Splits long messages into <= 4096-char chunks (uses a 4000-char budget)\n * - Outputs one item per chunk so the Telegram node sends all parts\n *\n * Recommended: Run this node in \"Run Once for All Items\".\n */\n\nconst MAX_TELEGRAM = 4096;\nconst SAFE_BUDGET = 4000; // small margin to avoid edge overflows\n\n// ============ MarkdownV2 helpers ============\nfunction escapeMarkdownV2(text) {\n  if (!text) return '';\n  return String(text).replace(/([\\\\_*[\\]()~`>#+\\-=|{}.!])/g, '\\\\$1');\n}\n\nfunction escapeForUrl(url) {\n  return String(url).replace(/[)\\\\]/g, '\\\\$&');\n}\n\nfunction normalizeAndValidateUrl(url) {\n  let raw = String(url || '').trim();\n  try {\n    const u = new URL(raw);\n    return u.toString();\n  } catch {}\n  // Try https:// for bare domains\n  const domainLike = /^[a-z0-9.-]+\\.[a-z]{2,}([/:?#].*)?$/i.test(raw);\n  if (domainLike) {\n    try {\n      const u2 = new URL('https://' + raw);\n      return u2.toString();\n    } catch {}\n  }\n  return null;\n}\n\nfunction normalizeHeadings(text) {\n  // Turn \"# Title\" \u2192 \"*Title*\"\n  return text.replace(/^(#{1,6})\\s+(.*)$/gm, (m, hashes, title) => `*${title.trim()}*`);\n}\n\nfunction normalizeCommonMd(text) {\n  return String(text)\n    .replace(/\\*\\*([\\s\\S]*?)\\*\\*/g, '*$1*') // **bold** \u2192 *bold*\n    .replace(/__([\\s\\S]*?)__/g, '_$1_');    // __italic__ \u2192 _italic_\n}\n\n/**\n * Convert incoming text to Telegram-safe MarkdownV2.\n */\nfunction processMarkdownV2Safe(inputText) {\n  if (!inputText) return '';\n\n  let text = normalizeCommonMd(String(inputText));\n  text = normalizeHeadings(text);\n\n  const placeholders = { links: [], bolds: [], italics: [], spoilers: [] };\n\n  // Links: keep safe via placeholders during escaping\n  text = text.replace(/\\[([^\\]\\n]+)\\]\\(([^)]+)\\)/g, (m, label, url) => {\n    const normalizedUrl = normalizeAndValidateUrl(url);\n    if (!normalizedUrl) return escapeMarkdownV2(label);\n    const idx = placeholders.links.length;\n    const ph = `\u27ecL${idx}\u27ed`;\n    const safeLabel = escapeMarkdownV2(label);\n    const safeUrl = escapeForUrl(normalizedUrl);\n    placeholders.links.push(`[${safeLabel}](${safeUrl})`);\n    return ph;\n  });\n\n  // Bold\n  text = text.replace(/\\*([\\s\\S]+?)\\*/g, (m, inner) => {\n    const idx = placeholders.bolds.length;\n    const ph = `\u27ecB${idx}\u27ed`;\n    placeholders.bolds.push(`*${escapeMarkdownV2(inner)}*`);\n    return ph;\n  });\n\n  // Italic\n  text = text.replace(/_([\\s\\S]+?)_/g, (m, inner) => {\n    const idx = placeholders.italics.length;\n    const ph = `\u27ecI${idx}\u27ed`;\n    placeholders.italics.push(`_${escapeMarkdownV2(inner)}_`);\n    return ph;\n  });\n\n  // Spoilers\n  text = text.replace(/\\|\\|([\\s\\S]+?)\\|\\|/g, (m, inner) => {\n    const idx = placeholders.spoilers.length;\n    const ph = `\u27ecS${idx}\u27ed`;\n    placeholders.spoilers.push(`||${escapeMarkdownV2(inner)}||`);\n    return ph;\n  });\n\n  // Escape everything else\n  text = escapeMarkdownV2(text);\n\n  // Restore placeholders\n  placeholders.links.forEach((md, i) => { text = text.replace(`\u27ecL${i}\u27ed`, md); });\n  placeholders.bolds.forEach((md, i) => { text = text.replace(`\u27ecB${i}\u27ed`, md); });\n  placeholders.italics.forEach((md, i) => { text = text.replace(`\u27ecI${i}\u27ed`, md); });\n  placeholders.spoilers.forEach((md, i) => { text = text.replace(`\u27ecS${i}\u27ed`, md); });\n\n  return text;\n}\n\n// ============ Chunking helpers ============\n/**\n * Split text into Telegram-safe chunks <= maxLen.\n * Prefers paragraph boundaries, then sentence boundaries, then words.\n * Falls back to hard cuts only when unavoidable (e.g., extremely long URL).\n */\nfunction chunkForTelegram(text, maxLen = SAFE_BUDGET) {\n  if (!text || text.length <= maxLen) return [text || ''];\n\n  const parts = [];\n  let buffer = '';\n\n  const flush = () => {\n    if (buffer) {\n      parts.push(buffer);\n      buffer = '';\n    }\n  };\n\n  // 1) Paragraph-level packing\n  const paragraphs = text.split(/\\n{2,}/);\n  for (const pRaw of paragraphs) {\n    const p = pRaw; // keep paragraph as-is\n    const candidate = buffer ? buffer + '\\n\\n' + p : p;\n    if (candidate.length <= maxLen) {\n      buffer = candidate;\n      continue;\n    }\n    if (p.length <= maxLen) {\n      flush();\n      buffer = p;\n      continue;\n    }\n\n    // 2) Sentence-level packing (paragraph is still too big)\n    flush();\n    const sentences = p.split(/(?<=[.!?\u2026])\\s+(?=[^\\s])/u);\n    let sBuf = '';\n    for (const s of sentences) {\n      const sCandidate = sBuf ? sBuf + ' ' + s : s;\n      if (sCandidate.length <= maxLen) {\n        sBuf = sCandidate;\n        continue;\n      }\n      if (s.length <= maxLen) {\n        if (sBuf) parts.push(sBuf);\n        sBuf = s;\n        continue;\n      }\n\n      // 3) Word-level packing (sentence is still too big)\n      if (sBuf) { parts.push(sBuf); sBuf = ''; }\n      let wBuf = '';\n      const words = s.split(/\\s+/);\n      for (const w of words) {\n        const wCandidate = wBuf ? wBuf + ' ' + w : w;\n        if (wCandidate.length <= maxLen) {\n          wBuf = wCandidate;\n          continue;\n        }\n        if (w.length <= maxLen) {\n          if (wBuf) parts.push(wBuf);\n          wBuf = w;\n          continue;\n        }\n        // 4) Hard split (extremely long token, e.g., massive URL)\n        if (wBuf) { parts.push(wBuf); wBuf = ''; }\n        const re = new RegExp(`.{1,${maxLen}}`, 'g');\n        const hardPieces = w.match(re) || [];\n        parts.push(...hardPieces);\n      }\n      if (wBuf) parts.push(wBuf);\n    }\n    if (sBuf) parts.push(sBuf);\n  }\n  if (buffer) parts.push(buffer);\n\n  // Final safety pass: trim chunks that might still exceed MAX_TELEGRAM\n  return parts.flatMap(part => {\n    if (part.length <= MAX_TELEGRAM) return [part];\n    const re = new RegExp(`.{1,${SAFE_BUDGET}}`, 'g');\n    return part.match(re) || [];\n  });\n}\n\n// ============ Main ============\nconst inputItems = $input.all();\nconst out = [];\n\nfor (const item of inputItems) {\n  const j = item.json || {};\n  const raw =\n    j.message ?? j.output ?? j.text ?? j.content ?? '';\n\n  const formatted = processMarkdownV2Safe(raw);\n  const chunks = chunkForTelegram(formatted, SAFE_BUDGET);\n\n  chunks.forEach((chunk, idx) => {\n    out.push({\n      json: {\n        ...j,\n        message: chunk,\n        message_part_index: idx + 1,\n        message_parts_total: chunks.length,\n      },\n      binary: item.binary,\n    });\n  });\n}\n\nreturn out;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0086d3f2-cc2f-45a8-9db7-a7b1a803700a",
      "name": "Get Meals Info",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3056,
        1808
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.Date }}",
              "lookupColumn": "Date"
            },
            {
              "lookupValue": "={{ $json.User_ID }}",
              "lookupColumn": "User_ID"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 403788598,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit#gid=403788598",
          "cachedResultName": "Meals"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Dm_YOUR_AWS_SECRET_KEY_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit?usp=drivesdk",
          "cachedResultName": "Cal AI"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "6e61ba86-d1ad-4140-b59d-715fdf7472fa",
      "name": "Get User Info",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3056,
        2064
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.User_ID }}",
              "lookupColumn": "User_ID"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit#gid=0",
          "cachedResultName": "Profile"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Dm_YOUR_AWS_SECRET_KEY_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit?usp=drivesdk",
          "cachedResultName": "Cal AI"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "49cbbd8e-dc4f-4ac9-b17a-afc6f8f5431b",
      "name": "get_message (register)",
      "type": "n8n-nodes-base.set",
      "position": [
        1728,
        1776
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "801ec600-22ad-4a94-a2b4-ae72eb271df0",
              "name": "message",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.text }}"
            },
            {
              "id": "263071fb-bcdf-42b0-bb46-71b75fa0bf2a",
              "name": "chat_id",
              "type": "string",
              "value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "544b7ab2-d054-4235-b46b-2a3cbcbd3583",
      "name": "Register User",
      "type": "n8n-nodes-base.googleSheetsTool",
      "position": [
        2192,
        2032
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Name', ``, 'string') }}",
            "User_ID": "={{ $json.chat_id }}",
            "Protein_target": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Protein_target', ``, 'string') }}",
            "Calories_target": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Calories_target', ``, 'string') }}"
          },
          "schema": [
            {
              "id": "User_ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "User_ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Calories_target",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Calories_target",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Protein_target",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Protein_target",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "ID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit#gid=0",
          "cachedResultName": "Profile"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Dm_YOUR_AWS_SECRET_KEY_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit?usp=drivesdk",
          "cachedResultName": "Cal AI"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "615bc756-bc1c-49a7-86a2-af2c940e474a",
      "name": "Register Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2000,
        1776
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "options": {
          "systemMessage": "=You are Cal AI \ud83c\udfcb\ufe0f\u200d\u2642\ufe0f\ud83e\udd66, the friendly fitness & nutrition coach assistant.\nYour single job is to register a new user into the Users table while keeping the tone supportive, clear, and motivational. Always use emojis related to health, training, and food (\ud83d\udd25\ud83d\udcaa\ud83e\udd66\ud83c\udf57\ud83c\udf3e\ud83e\udd51).\n\n\ud83d\udd11 Registration Rules\n\nYou MUST collect these fields:\n\nuser_id (not asked, system provides it)\n\nname\n\ncalories_target\n\nprotein_target\n\n\ud83d\udccc When sending data to the Register User tool, always send numbers only (no units, no text, no emojis) for calories_target and protein_targe\n\nIf the user does not know their numeric targets, do not invent them. Instead, coach them by politely asking for:\n\nweight \u2696\ufe0f\n\nheight \ud83d\udccf\n\nage \ud83c\udf82\n\ngoal \ud83c\udfaf (gain muscle \ud83d\udcaa, lose fat \ud83d\udd25, maintain \u2696\ufe0f)\n\nBased on this info, calculate precise calorie and protein targets.\n\nOnly when all fields are collected and confirmed \u2192 call Register User tool with the final data.\n\n\u2705 After Successful Registration\n\nSend a short, friendly confirmation like a coach:\n\n\u201cAwesome, champ \ud83d\udcaa! Your nutrition targets are locked in: \ud83d\udd25 [calories] kcal, \ud83c\udf57 [protein] g protein.\u201d\n\nThen, explain clearly how to use Cal AI:\n\n\ud83d\udcf8 Send food photos \u2192 get instant calories + macros.\n\n\u2699\ufe0f View or update your targets anytime.\n\n\ud83d\udcd1 Request daily reports \u2192 compare intake vs. targets with charts.\n\n\ud83d\udde3\ufe0f Style Guide\n\nAlways keep responses simple, clear, and concise.\n\nSpeak like a friendly trainer/coach who motivates the user.\n\nUse emojis to highlight key concepts.\n\nBe warm, supportive, and practical:\n\n\u201cLet\u2019s get you set up for success \ud83c\udfcb\ufe0f\u200d\u2642\ufe0f\ud83d\udd25\u201d\n\n\u201cStrong start, [Name]! Targets ready \ud83d\udcaa\ud83e\udd66.\u201d"
        },
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "6d4404a0-3993-4719-b830-e5786e0a4ab5",
      "name": "Cal IA Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3200,
        1136
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "options": {
          "systemMessage": "=You are Cal AI \ud83c\udfcb\ufe0f\u200d\u2642\ufe0f\ud83e\udd66, your friendly fitness coach and nutrition orchestrator.\nYour mission is to guide the user with motivation, clarity, and precision while managing their nutrition data. Speak in a supportive, energetic tone like a personal trainer, and use relevant emojis (\ud83d\udd25\ud83d\udcaa\ud83e\udd66\ud83c\udf57\ud83c\udf3e\ud83e\udd51) to keep the conversation fun and engaging.\n\nYou have four tools available:\n\nappendMealData(tool) \u2192 store a meal row in Meals sheet.\n\nupdateProfileData(tool) \u2192 update the user's profile targets (fields: Name, Calories_target, Protein_target).\n\ngetUserData(tool) \u2192 fetch the user's profile info.\n\ngetReport(tool) \u2192 generate or fetch the daily report (requires date).\n\n\ud83d\udd11 Rules\n\nThe image analysis is done before reaching you. You will always receive structured info:\nMeal Description: [short description]\nCalories: [number]\nProteins: [number]\nCarbs: [number]\nFat: [number]\n\nWith this info, call appendMealData.\n\nAfter appendMealData success, confirm naturally in a coach style: repeat the meal info using emojis (\ud83d\udd25 Calories, \ud83c\udf57 Protein, \ud83c\udf3e Carbs, \ud83e\udd51 Fat).\n\nEnd confirmations with a quick motivational phrase like:\n\n\u201cGreat fuel for your body \ud83d\udcaa\ud83d\udd25\u201d\n\n\u201cAnother step closer to your goals \ud83e\udd66\ud83c\udfcb\ufe0f\u200d\u2642\ufe0f\u201d\n\nAlways offer short next-step options:\n\ud83d\udc49 \u201cView daily report \ud83d\udcd1\u201d\n\ud83d\udc49 \u201cAnalyze another meal \ud83d\udcf8\u201d\n\ud83d\udc49 \u201cView or update profile targets \u2699\ufe0f\u201d\n\n\ud83d\udd04 Profile Update Logic\n\nWhen the user wants to update their profile (Name, Calories_target, or Protein_target):\n\nFirst call getUserData to fetch current profile info.\n\nCompare the requested update with the existing values.\n\nOnly pass the changed fields to updateProfileData (never overwrite unchanged values).\n\nConfirm to the user in a friendly way, e.g.:\n\n\u201c\u2705 Your Calories_target is now 2200 \ud83d\udd25. Protein_target stays strong at 150 \ud83c\udf57.\u201d\n\n\u201cProfile updated! Let\u2019s crush it \ud83d\udcaa\ud83e\udd66.\u201d\n\n\ud83d\udcd1 Tool Usage Rules\n\ngetUserData \u2192 always called first before updating the profile.\n\nupdateProfileData \u2192 only include the fields that have changed.\n\ngetReport \u2192 only pass the requested date.\n\nKeep all responses short, clear, motivational, and full of energy.\n\n\ud83d\udcc5 Date: {{ $today.format('yyyy-MM-dd') }}"
        },
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "a2b7e00f-1e9b-4032-8ae1-6612b3ef6520",
      "name": "Update Profile Data",
      "type": "n8n-nodes-base.googleSheetsTool",
      "position": [
        3520,
        1392
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Name', ``, 'string') }}",
            "User_ID": "={{ $json.chat_id }}",
            "Protein_target": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Protein_target', ``, 'string') }}",
            "Calories_target": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Calories_target', ``, 'string') }}"
          },
          "schema": [
            {
              "id": "User_ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "User_ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Calories_target",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Calories_target",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Protein_target",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Protein_target",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "User_ID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit#gid=0",
          "cachedResultName": "Profile"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Dm_YOUR_AWS_SECRET_KEY_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit?usp=drivesdk",
          "cachedResultName": "Cal AI"
        },
        "descriptionType": "manual",
        "toolDescription": "=updateProfileData\nPurpose: Update the user\u2019s profile targets.\nFields that can be updated:\n\nName (string)\n\nCalories_target (string/number)\n\nProtein_target (string/number)\nWhen to use: When the user explicitly asks to update their name, calorie target, or protein target."
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "83f1cb62-818d-4161-99e4-7c984552fde1",
      "name": "Get Profile Data",
      "type": "n8n-nodes-base.googleSheetsTool",
      "position": [
        3344,
        1392
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.chat_id }}",
              "lookupColumn": "User_ID"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit#gid=0",
          "cachedResultName": "Profile"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Dm_YOUR_AWS_SECRET_KEY_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit?usp=drivesdk",
          "cachedResultName": "Cal AI"
        },
        "descriptionType": "manual",
        "toolDescription": "=getUserData\nPurpose: Retrieve the user\u2019s profile information.\nInputs: none.\nWhen to use: When the user asks about their profile info or targets."
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b4411d17-99e7-40c3-999b-21109d12e057",
      "name": "Get Report",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "position": [
        2848,
        1808
      ],
      "parameters": {
        "workflowId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $workflow.id }}"
        },
        "description": "getReport\nPurpose: Generate or fetch the user\u2019s daily nutrition report.\nInput required: date (string, format: YYYY-MM-DD).\nWhen to use: When the user asks to see their daily summary or report for a specific date.",
        "workflowInputs": {
          "value": {
            "Date": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Date', ``, 'string') }}",
            "User_ID": "={{ $json.chat_id }}"
          },
          "schema": [
            {
              "id": "User_ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "User_ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1df46a7c-3ef6-4698-93cc-75558e882ca9",
      "name": "Append Meal Data",
      "type": "n8n-nodes-base.googleSheetsTool",
      "position": [
        3424,
        1536
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $today.format(\"yyyy-LL-dd\") }}\n",
            "Fats": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Fats', ``, 'string') }}",
            "Carbs": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Carbs', ``, 'string') }}",
            "User_ID": "={{ $json.chat_id }}",
            "Calories": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Calories', ``, 'string') }}",
            "Proteins": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Proteins', ``, 'string') }}",
            "Meal_description": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Meal_description', ``, 'string') }}"
          },
          "schema": [
            {
              "id": "User_ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "User_ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Meal_description",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Meal_description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Calories",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Calories",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Proteins",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Proteins",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Carbs",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Carbs",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Fats",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Fats",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 403788598,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit#gid=403788598",
          "cachedResultName": "Meals"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Dm_YOUR_AWS_SECRET_KEY_HERE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Dm_YOUR_AWS_SECRET_KEY_HERE/edit?usp=drivesdk",
          "cachedResultName": "Cal AI"
        },
        "descriptionType": "manual",
        "toolDescription": "appendMealData\nPurpose: Store one meal entry into the Meals sheet.\nInputs required:\n\nMeal Description (string)\n\nCalories (number)\n\nProteins (number)\n\nCarbs (number)\n\nFat (number)\nWhen to use: Every time you receive structured meal information from image analysis."
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "0e6b6065-de69-4ef8-af08-8f1c0016ca38",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        240
      ],
      "parameters": {
        "width": 608,
        "height": 336,
        "content": "# \ud83d\udcd8 Cal AI Alternative \u2013 Nutrition Assistant \n\nThis workflow implements a **Nutrition Assistant** that helps users log meals, track nutritional goals, and receive personalized reports.  \nThe system integrates **Telegram**, **Google Sheets**, and an **AI Agent (Gemini)** within **n8n**.\n\n\u2705 **With this workflow, users can:**  \n- Register easily via Telegram.  \n- Log meals with text, voice, or images.  \n- Track nutrition goals automatically.  \n- Receive daily personalized reports.  "
      },
      "typeVersion": 1
    },
    {
      "id": "1d48fbab-a382-47f5-909a-4e1c2f4e8772",
      "name": "Get Data",
      "type": "n8n-nodes-base.set",
      "position": [
        3264,
        1808
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d3ff7d44-6241-41c6-ac56-4549ba0cbd6d",
              "name": "Calories",
              "type": "number",
              "value": "={{ $json.Calories }}"
            },
            {
              "id": "64afda83-d211-48fa-830e-c0b7ddb5d50e",
              "name": "Proteins",
              "type": "number",
              "value": "={{ $json.Proteins }}"
            },
            {
              "id": "ab3861af-5716-400b-a736-f5a2dc4713a7",
              "name": "Carbs",
              "type": "number",
              "value": "={{ $json.Carbs }}"
            },
            {
              "id": "cce6d192-9bc9-401e-98b0-697dc4ec8f08",
              "name": "Fats",
              "type": "number",
              "value": "={{ $json.Fats }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "2a40606e-6469-4081-8e22-8012b1c06d04",
      "name": "Get chart message",
      "type": "n8n-nodes-base.code",
      "position": [
        3776,
        2048
      ],
      "parameters": {
        "jsCode": "/**\n * Nutrition summary \u2192 Telegram MarkdownV2 (n8n Code node)\n */\n\nconst MAX_TELEGRAM = 4096;\nconst SAFE_BUDGET = 4000; // margen de seguridad\n\n// ============ Helpers de barras ============\nfunction makeProgressBar(current, target, length = 20) {\n  const ratio = Math.min(current / target, 1);\n  const filled = Math.round(ratio * length);\n  const empty = length - filled;\n  return '\u2588'.repeat(filled) + '\u2591'.repeat(empty);\n}\n\nfunction percent(current, target) {\n  if (!target || target === 0) return 0;\n  return Math.round((current / target) * 100);\n}\n\n// ============ Helpers MarkdownV2 ============\nfunction escapeMarkdownV2(text) {\n  if (!text) return '';\n  return String(text).replace(/([\\\\_*[\\]()~`>#+\\-=|{}.!])/g, '\\\\$1');\n}\n\nfunction escapeForUrl(url) {\n  return String(url).replace(/[)\\\\]/g, '\\\\$&');\n}\n\nfunction normalizeAndValidateUrl(url) {\n  let raw = String(url || '').trim();\n  try {\n    const u = new URL(raw);\n    return u.toString();\n  } catch {}\n  const domainLike = /^[a-z0-9.-]+\\.[a-z]{2,}([/:?#].*)?$/i.test(raw);\n  if (domainLike) {\n    try {\n      const u2 = new URL('https://' + raw);\n      return u2.toString();\n    } catch {}\n  }\n  return null;\n}\n\nfunction normalizeHeadings(text) {\n  return text.replace(/^(#{1,6})\\s+(.*)$/gm, (m, hashes, title) => `*${title.trim()}*`);\n}\n\nfunction normalizeCommonMd(text) {\n  return String(text)\n    .replace(/\\*\\*([\\s\\S]*?)\\*\\*/g, '*$1*')\n    .replace(/__([\\s\\S]*?)__/g, '_$1_');\n}\n\nfunction processMarkdownV2Safe(inputText) {\n  if (!inputText) return '';\n\n  let text = normalizeCommonMd(String(inputText));\n  text = normalizeHeadings(text);\n\n  const placeholders = { links: [], bolds: [], italics: [], spoilers: [] };\n\n  // Links\n  text = text.replace(/\\[([^\\]\\n]+)\\]\\(([^)]+)\\)/g, (m, label, url) => {\n    const normalizedUrl = normalizeAndValidateUrl(url);\n    if (!normalizedUrl) return escapeMarkdownV2(label);\n    const idx = placeholders.links.length;\n    const ph = `\u27ecL${idx}\u27ed`;\n    const safeLabel = escapeMarkdownV2(label);\n    const safeUrl = escapeForUrl(normalizedUrl);\n    placeholders.links.push(`[${safeLabel}](${safeUrl})`);\n    return ph;\n  });\n\n  // Bold\n  text = text.replace(/\\*([\\s\\S]+?)\\*/g, (m, inner) => {\n    const idx = placeholders.bolds.length;\n    const ph = `\u27ecB${idx}\u27ed`;\n    placeholders.bolds.push(`*${escapeMarkdownV2(inner)}*`);\n    return ph;\n  });\n\n  // Italic\n  text = text.replace(/_([\\s\\S]+?)_/g, (m, inner) => {\n    const idx = placeholders.italics.length;\n    const ph = `\u27ecI${idx}\u27ed`;\n    placeholders.italics.push(`_${escapeMarkdownV2(inner)}_`);\n    return ph;\n  });\n\n  // Spoilers\n  text = text.replace(/\\|\\|([\\s\\S]+?)\\|\\|/g, (m, inner) => {\n    const idx = placeholders.spoilers.length;\n    const ph = `\u27ecS${idx}\u27ed`;\n    placeholders.spoilers.push(`||${escapeMarkdownV2(inner)}||`);\n    return ph;\n  });\n\n  text = escapeMarkdownV2(text);\n\n  placeholders.links.forEach((md, i) => { text = text.replace(`\u27ecL${i}\u27ed`, md); });\n  placeholders.bolds.forEach((md, i) => { text = text.replace(`\u27ecB${i}\u27ed`, md); });\n  placeholders.italics.forEach((md, i) => { text = text.replace(`\u27ecI${i}\u27ed`, md); });\n  placeholders.spoilers.forEach((md, i) => { text = text.replace(`\u27ecS${i}\u27ed`, md); });\n\n  return text;\n}\n\nfunction chunkForTelegram(text, maxLen = SAFE_BUDGET) {\n  if (!text || text.length <= maxLen) return [text || ''];\n\n  const parts = [];\n  let buffer = '';\n\n  const flush = () => {\n    if (buffer) {\n      parts.push(buffer);\n      buffer = '';\n    }\n  };\n\n  const paragraphs = text.split(/\\n{2,}/);\n  for (const p of paragraphs) {\n    const candidate = buffer ? buffer + '\\n\\n' + p : p;\n    if (candidate.length <= maxLen) {\n      buffer = candidate;\n      continue;\n    }\n    if (p.length <= maxLen) {\n      flush();\n      buffer = p;\n      continue;\n    }\n    flush();\n    const sentences = p.split(/(?<=[.!?\u2026])\\s+(?=[^\\s])/u);\n    let sBuf = '';\n    for (const s of sentences) {\n      const sCandidate = sBuf ? sBuf + ' ' + s : s;\n      if (sCandidate.length <= maxLen) {\n        sBuf = sCandidate;\n        continue;\n      }\n      if (s.length <= maxLen) {\n        if (sBuf) parts.push(sBuf);\n        sBuf = s;\n        continue;\n      }\n      if (sBuf) { parts.push(sBuf); sBuf = ''; }\n      let wBuf = '';\n      const words = s.split(/\\s+/);\n      for (const w of words) {\n        const wCandidate = wBuf ? wBuf + ' ' + w : w;\n        if (wCandidate.length <= maxLen) {\n          wBuf = wCandidate;\n          continue;\n        }\n        if (w.length <= maxLen) {\n          if (wBuf) parts.push(wBuf);\n          wBuf = w;\n          continue;\n        }\n        if (wBuf) { parts.push(wBuf); wBuf = ''; }\n        const re = new RegExp(`.{1,${maxLen}}`, 'g');\n        const hardPieces = w.match(re) || [];\n        parts.push(...hardPieces);\n      }\n      if (wBuf) parts.push(wBuf);\n    }\n    if (sBuf) parts.push(sBuf);\n  }\n  if (buffer) parts.push(buffer);\n\n  return parts.flatMap(part => {\n    if (part.length <= MAX_TELEGRAM) return [part];\n    const re = new RegExp(`.{1,${SAFE_BUDGET}}`, 'g');\n    return part.match(re) || [];\n  });\n}\n\n// ============ MAIN ============\nconst inputItems = $input.all();\nconst out = [];\n\nfor (const item of inputItems) {\n  const j = item.json || {};\n\n  // Datos din\u00e1micos\n  const name = j.Name || 'User';\n  const cal = j.Total_Calories || 0;\n  const calTarget = j.Calories_target || 1;\n\n  const prot = j.Total_Proteins || 0;\n  const protTarget = j.Protein_target || 1;\n\n  const carbs = j.Total_Carbs || 0;\n  const fats  = j.Total_Fats || 0;\n\n  // Construcci\u00f3n del mensaje\n  let msg = `*Hello ${name}*\\nHere is your nutrition summary:\\n\\n`;\n\n  msg += `\ud83d\udd25 *Calories*: ${cal}/${calTarget} (${percent(cal, calTarget)}%)\\n`;\n  msg += makeProgressBar(cal, calTarget) + '\\n\\n';\n\n  msg += `\ud83c\udf57 *Protein*: ${prot}/${protTarget} (${percent(prot, protTarget)}%)\\n`;\n  msg += makeProgressBar(prot, protTarget) + '\\n\\n';\n\n  msg += `\ud83c\udf3e *Carbs*: ${carbs} g\\n`;\n  msg += `\ud83e\udd51 *Fats*: ${fats} g\\n`;\n\n  // Formateo seguro\n  const formatted = processMarkdownV2Safe(msg);\n  const chunks = chunkForTelegram(formatted, SAFE_BUDGET);\n\n  chunks.forEach((chunk, idx) => {\n    out.push({\n      json: {\n        ...j,\n        message: chunk,\n        message_part_index: idx + 1,\n        message_parts_total: chunks.length,\n      },\n      binary: item.binary,\n    });\n  });\n}\n\nreturn out;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "dd89154b-8aa8-48dc-865a-6214e8ca0cd0",
      "name": "Unify data",
      "type": "n8n-nodes-base.code",
      "position": [
        3472,
        1808
      ],
      "parameters": {
        "jsCode": "// Get all input items\nconst items = $input.all();\n\n// Initialize accumulators\nlet totalCalories = 0;\nlet totalProteins = 0;\nlet totalCarbs = 0;\nlet totalFats = 0;\n\n// Sum up each field from every item\nfor (const item of items) {\n  const data = item.json;\n\n  totalCalories += Number(data.Calories || 0);\n  totalProteins += Number(data.Proteins || 0);\n  totalCarbs += Number(data.Carbs || 0);\n  totalFats += Number(data.Fats || 0);\n}\n\n// Return a single result with totals\nreturn [\n  {\n    json: {\n      Total_Calories: totalCalories,\n      Total_Proteins: totalProteins,\n      Total_Carbs: totalCarbs,\n      Total_Fats: totalFats,\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "67260623-70d6-4e4b-aba6-9804c4396b67",
      "name": "Get report",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        2848,
        1920
      ],
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "User_ID"
            },
            {
              "name": "Date"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "80fdb6f0-8d64-44aa-923c-9b94f9ce132f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        1024
      ],
      "parameters": {
        "color": 4,
        "width": 608,
        "height": 464,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "d1917184-4c8e-42db-9bbf-a7ef28ca71f4",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1744,
        880
      ],
      "parameters": {
        "color": 5,
        "width": 1104,
        "height": 720,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "2ad00761-954b-4a7c-af46-25c74619176a",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3024,
        1008
      ],
      "parameters": {
        "color": 6,
        "width": 896,
        "height": 720,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "c7e182da-6f2a-4a8f-96df-8cbd0445b67e",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1648,
        1696
      ],
      "parameters": {
        "color": 3,
        "width": 1104,
        "height": 528,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "4ade95cd-0d9d-4ab3-bfbb-93d792a6d880",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2784,
        1760
      ],
      "parameters": {
        "color": 2,
        "width": 1392,
        "height": 464,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "659b06e0-1253-4f19-8e40-3f849515da68",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2320,
        2272
      ],
      "parameters": {
        "color": 3,
        "width": 368,
        "height": 304,
        "content": "![](https://github.com/JarsRat/Images/blob/main/cal_ia_start.png?raw=true)\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c1eebd5c-62fe-4794-badc-872a5e32dcd8",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2432,
        512
      ],
      "parameters": {
        "color": 5,
        "width": 352,
        "height": 224,
        "content": "![](https://github.com/JarsRat/Images/blob/main/cal_ia_meal.png?raw=true)\n"
      },
      "typeVersion": 1
    },
    {
      "id": "80cf0536-6f63-4b7a-969e-245d8d5ca8cc",
      "name": "Send back message",
      "type": "n8n-nodes-base.set",
      "position": [
        3952,
        2048
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "750d5bca-c08f-42bb-b9cc-5709c6fad4a9",
              "name": "message",
              "type": "string",
              "value": "={{ $json.message }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e0da0385-fff5-4b95-a846-29bc9be1ffef",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3456,
        2432
      ],
      "parameters": {
        "color": 2,
        "width": 368,
        "height": 208,
        "content": "![](https://github.com/JarsRat/Images/blob/main/cal_ia_report.png?raw=true)\n "
      },
      "typeVersion": 1
    },
    {
      "id": "af1fc6e8-f21b-49fc-8613-36522fbad32d",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        624
      ],
      "parameters": {
        "color": 4,
        "width": 496,
        "height": 352,
        "content": "## Telegram Trigger & User Check\n\n**Purpose:** Handle incoming messages and validate user registration.\n\n1. **Telegram Trigger**  \n   - Captures user messages (text, voice, or images) from Telegram.\n\n2. **User Registration Check**  \n   - Verifies if the sender exists in the `Profile` table (Google Sheets).\n   - If the user **is not registered** \u2192 Redirects to **Zone Red (Register Agent)**.  \n   - If the user **is registered** \u2192 Forwards the message to **Zone Blue (Message Processing)**."
      },
      "typeVersion": 1
    },
    {
      "id": "f5d317bb-e0e1-4f7d-8ee4-e3b7d4262f36",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1648,
        2256
      ],
      "parameters": {
        "color": 3,
        "width": 624,
        "height": 496,
        "content": "## Register Agent\n\n**Purpose:** Register new users and set up nutritional goals.\n\n1. **Register Agent**  \n   - Guides new users through the registration process.  \n   - Collects user details:\n     - **Name**\n     - **Calories_target**\n     - **Protein_target**\n\n2. **Target Assistance**  \n   - If the user is unsure about their targets, the agent asks simple questions to help determine suitable daily calorie and protein goals.  \n   - \u26a0\ufe0f **Note:** No personal health data (e.g., weight, height) is stored.\n\n3. **Database Update**  \n   - Once confirmed, the new profile is added to the **Profile Table** in Google Sheets.  \n   - The user is now considered \"registered\" and future messages will be processed in **Zone Blue**.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "6c68706c-0c00-4e44-8338-89a7c5e655c1",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1744,
        336
      ],
      "parameters": {
        "color": 5,
        "width": 672,
        "height": 512,
        "content": "## Message Processing\n\n**Purpose:** Classify and process different types of user messages.\n\n1. **Message Routing**  \n   - Determines the type of message received:\n     - **Text** \u2192 Sent directly to the AI Agent.  \n     - **Voice/Audio** \u2192 Downloaded, transcribed, and converted into text.  \n     - **Image** \u2192 Downloaded and analyzed by Google AI Vision.\n\n2. **Image Analysis (Food Recognition)**  \n   - Google AI receives a custom prompt to analyze food images.  \n   - The AI:\n     - Identifies food items.  \n     - Estimates nutritional values (Calories, Proteins, Carbs, Fats).  \n     - Returns the analysis in a structured, human-readable text format.  \n\n3. **Error Handling**  \n   - If processing fails, a fallback node (`get_error_message`) sends an error notification to the user.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "18b4c021-2564-44eb-87db-cc64191777bf",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2800,
        2288
      ],
      "parameters": {
        "color": 2,
        "width": 624,
        "height": 448,
        "content": "## Report Subworkflow\n\n**Purpose:** Generate personalized daily nutrition reports.\n\n1. **Get Meals & Targets**  \n   - Retrieves all logged meals for the selected day from the database.  \n   - Fetches the user\u2019s nutrition targets from the profile.\n\n2. **Progress Calculation**  \n   - Runs a code node to calculate:\n     - Daily totals (Calories, Proteins, etc.)  \n     - Percentage progress toward targets.\n\n3. **Report Formatting**  \n   - Returns a **personalized message** with:\n     - Summary of meals logged.  \n     - Totals and percentages.  \n     - A **progress bar** visualization of calories and proteins.  \n"
      },
      "typeVersion": 1
    },
    {
      "id": "16a7e256-e252-4492-8e62-2f2044e4d95a",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3024,
        464
      ],
      "parameters": {
        "color": 6,
        "width": 480,
        "height": 528,
        "content": "##  Main AI Agent\n\n**Purpose:** Central decision-making agent for all user interactions.\n\nThe AI Agent (Gemini) operates with **four key tools**:\n\n1. **getProfileData**  \n   - Retrieves user profile (calorie/protein targets).\n\n2. **updateProfileData**  \n   - Updates user goals upon request.\n\n3. **appendMealData**  \n   - Adds new meal entries to the **Meals Table** in Google Sheets.\n\n4. **getReport**  \n   - Triggers **Zone Yellow** to generate a daily progress report.\n\n**Conversation Flow:**  \n- Managed with the **Simple Memory node**, ensuring contextual and natural dialogue.  \n- All responses are returned to the user via Telegram.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b59a26f2-a478-4be8-a70c-7ba70face917",
      "name": "Sticky Note14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3536,
        512
      ],
      "parameters": {
        "color": 6,
        "width": 368,
        "content": "![](https://github.com/JarsRat/Images/blob/main/cal_ia_target.png?raw=true)\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e1c875c3-2720-41b7-93d4-794ed182790d",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3536,
        736
      ],
      "parameters": {
        "color": 2,
        "width": 368,
        "height": 208,
        "content": "## \ud83d\udca1 Need Assistance?\n\nIf you\u2019d like help customizing or extending this workflow, feel free to reach out:  \n\n\ud83d\udce7 Email: [johnsilva11031@gmail.com](mailto:johnsilva11031@gmail.com)  \n\ud83d\udd17 LinkedIn: [John Alejandro Silva Rodr\u00edguez](https://www.linkedin.com/in/john-alejandro-silva-rodriguez-48093526b/)"
      },
      "typeVersion": 1
    },
    {
      "id": "4dc3eac9-6640-4cd8-b07d-44254df59f48",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        1072,
        1200
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4c0c1bc0-6f7d-4470-ab98-15ac3afc422a",
      "name": "Sticky Note16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        624
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 2032,
        "content": "# **Documentation: Configuring the Telegram Nutrition AI Assistant Workflow**\n\nThis guide provides step-by-step instructions for setting up the Google Sheets database and configuring the required n8n nodes to make the workflow fully operational.\n\n---\n\n### **Important Notes on Errors and Customization**\n\n*   **Public Sheet Errors:** If you encounter persistent errors while using the pre-configured public Google Sheet, it is highly probable that another user has modified its structure (e.g., deleted or renamed a column header), causing the workflow to fail. The most reliable way to resolve this is to follow the instructions in **Step 1** to set up and connect your own private Google Sheet.\n\n*   **Connection Issues:** Please be aware that intermittent connection errors with Google services can occasionally occur. If a Google (Sheets or Gemini) node fails, simply re-executing the step or the entire workflow is often enough to resolve the issue.\n\n*   **Customizing the AI Model:** While this workflow is pre-configured with Google Gemini for all AI-driven tasks, n8n's modular design allows for easy customization. You can swap the Gemini nodes for any large language model (LLM) you prefer, such as those from OpenAI, Anthropic, or others, to better suit your specific needs or existing subscriptions.\n\n---\n\n## **Step 1: Setting Up Your Google Sheets**\n\nYou will need a Google Sheet with two specific tabs (\"Sheets\") to act as your database.\n\n1.  **Create a Copy of the Template**\n    The easiest way to start is by making a copy of the official template. Open the link below and go to `File > Make a copy`.\n    *   **Template Link:** [Google Sheets Nutrition Template](https://docs.google.com/spreadsheets/d/11kI8q0oB2vPzbVJOItdna5o0y7szuoprJSA8v0Bt-Ec/edit?usp=sharing)\n\n2.  **Verify Sheet Structure**\n    Ensure your copy has the following two tabs with the exact column headers:\n\n    *   **Sheet 1: `Profile`**\n        This sheet stores user information and their nutritional goals.\n        *   `User_ID`\n        *   `Name`\n        *   `Calories_target`\n        *   `Protein_target`\n\n    *   **Sheet 2: `Meals`**\n        This sheet logs every meal entry for all users.\n        *   `User_ID`\n        *   `Date`\n        *   `Meal_description`\n        *   `Calories`\n        *   `Proteins`\n        *   `Carbs`\n        *   `Fats`\n\n3.  **Get Your Spreadsheet ID**\n    Copy the ID from your new spreadsheet's URL. It is the long string of characters between `/d/` and `/edit`.\n    `https://docs.google.com/spreadsheets/d/`**`[YOUR_SPREADSHEET_ID]`**`/edit`\n\n---\n\n## **Step 2: Configuring the n8n Google Sheets Nodes**\n\n>Now it's time to connect the workflow to your new Google Sheet. The following documentation is organized to match the colored sections you see on the n8n canvas. Each part of this guide details the specific configuration for the Google Sheets nodes located within its corresponding colored section.\n\n---\n\nBy following these steps, you will have successfully reconfigured the workflow to use your own private and secure Google Sheet, ensuring its long-term stability and the privacy of your data."
      },
      "typeVersion": 1
    },
    {
      "id": "64dd86de-16fe-4e84-a65e-e43370d618a7",
      "name": "Sticky Note17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        1520
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 1216,
        "content": "#### **\ud83d\udfe9 Green Section: Telegram Trigger & User Check**\n\nThis section checks if a user is already registered.\n\n**Node: `Registered?`**\n*   **Purpose:** Looks for the user's `chat.id` in the `Profile` sheet to see if they exist.\n*   **Configuration:**\n    1.  **Credential to connect with:** Select your Google Sheets account credential.\n    2.  **Operation:** `Get Row(s)`.\n    3.  **Document:** Select your Google Sheet document (\"Cal AI\" in the example).\n    4.  **Sheet:** Select `Profile`.\n    5.  **Filters > Column:** `User_ID`.\n    6.  **Filters > Value:** Set this to the following expression to get the chat ID from the Telegram trigger: `{{ $('Telegram Trigger').item.json.message.chat.id }}`.\n![](https://github.com/JarsRat/Images/blob/main/Documentation/Screenshot%202025-08-28%20070009.png?raw=true)"
      },
      "typeVersion": 1
    },
    {
      "id": "c864f199-349f-4d01-b014-45967763c3af",
      "name": "Sticky Note18",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1648,
        2784
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 1344,
        "content": "#### **\ud83d\udfe5 Red Section: Register Agent**\n\nThis section handles the creation of a new user profile.\n\n**Node: `Register User`**\n*   **Purpose:** Adds a new row with the new user's information to the `Profile` sheet after they complete the registration dialogue.\n*   **Configuration:**\n    1.  **Credential to connect with:** Select your Google Sheets account credential.\n    2.  **Operation:** `Append Row`.\n    3.  **Document:** Select your Google Sheet.\n    4.  **Sheet:** Select `Profile`.\n    5.  **Mapping Column Mode:** `Map Each Column Manually`.\n    6.  **Values to Send:**\n        *   **User\\_ID:** Set this to the expression that holds the user's chat ID, for example: `{{ $json.chat_id }}`.\n        *   **Name, Calories\\_target, Protein\\_target:** These fields will be populated dynamically by the AI model (`Register Agent`) that runs just before this node. You don't need to enter a static value here; the workflow will automatically pass the extracted information.\n![](https://github.com/JarsRat/Images/blob/main/Documentation/Screenshot%202025-08-28%20070403.png?raw=true)"
      },
      "typeVersion": 1
    },
    {
      "id": "801026e6-b6cf-42c1-9da9-3a6d689e2833",
      "name": "Sticky Note19",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3952,
        416
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 1328,
        "content": "#### **\ud83d\udfea Purple Section: Main AI Agent**\n\nThis is the core section that handles user interactions, including retrieving data, logging meals, and updating profiles.\n\n**Node: `Get Profile Data`**\n*   **Purpose:** Retrieves the user's profile (including their goals) from the `Profile` sheet. This provides the AI Agent with the necessary context for the conversation.\n*   **Configuration:**\n    1.  **Credential to connect with:** Select your Google Sheets account credential.\n    2.  **Operation:** `Get Row(s)`.\n    3.  **Document:** Select your Google Sheet.\n    4.  **Sheet:** Select `Profile`.\n    5.  **Filters > Column:** `User_ID`.\n    6.  **Filters > Value:** Use an expression to reference the user's chat ID from the input, for example: `{{ $json.chat_id }}`.\n\n![](https://github.com/JarsRat/Images/blob/main/Documentation/Screenshot%202025-08-28%20070500.png?raw=true)\n\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "0324ea13-1eb3-439d-a206-4d717bb81136",
      "name": "Sticky Note20",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4592,
        416
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 1328,
        "content": "**Node: `Append Meal Data`**\n*   **Purpose:** Logs a new meal into the `Meals` sheet after the AI has analyzed the user's text, voice, or image input.\n*   **Configuration:**\n    1.  **Credential to connect with:** Select your Google Sheets account credential.\n    2.  **Operation:** `Append Row`.\n    3.  **Document:** Select your Google Sheet.\n    4.  **Sheet:** Select `Meals`.\n    5.  **Values to Send:**\n        *   **User\\_ID:** `{{ $json.chat_id }}`.\n        *   **Date:** `{{ $today.format(\"YYYY-LL-DD\") }}` (This sets the current date for the meal entry).\n        *   **Meal\\_description, Calories, Proteins, Carbs, Fats:** These fields are populated dynamically by the AI model. The workflow will pass the extracted nutritional data to this node.\n\n![](https://github.com/JarsRat/Images/blob/main/Documentation/Screenshot%202025-08-28%20070542.png?raw=true)"
      },
      "typeVersion": 1
    },
    {
      "id": "7d7772f4-d959-4820-8043-55cdbe71eb44",
      "name": "Sticky Note21",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5232,
        416
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 1328,
        "content": "**Node: `Update Profile Data`**\n*   **Purpose:** Modifies an existing user's record in the `Profile` sheet, typically used when a user asks to change their calorie or protein goals.\n*   **Configuration:**\n    1.  **Credential to connect with:** Select your Google Sheets account credential.\n    2.  **Operation:** `Update Row`.\n    3.  **Document:** Select your Google Sheet.\n    4.  **Sheet:** Select `Profile`.\n    5.  **Column to match on:** `User_ID`. This tells the node which column to use to find the correct row to update.\n    6.  **Values to Send:**\n        *   **User\\_ID (using to match):** `{{ $json.chat_id }}`. This value is used to find the right row.\n        *   **Name, Calories\\_target, Protein\\_target:** These are the fields to be updated. The AI Agent will provide the new values, which the workflow will pass into these fields.\n\n![](https://github.com/JarsRat/Images/blob/main/Documentation/Screenshot%202025-08-28%20070601.png?raw=truee)"
      },
      "typeVersion": 1
    },
    {
      "id": "1686d6af-3214-4fb2-9042-afee6b2942af",
      "name": "Sticky Note22",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2800,
        2784
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 1408,
        "content": "#### **\ud83d\udfe8 Yellow Section: Report Subworkflow**\n\nThis subworkflow is triggered to generate and send a daily nutrition report to the user.\n\n**Node: `Get Meals Info`**\n*   **Purpose:** Fetches all meals logged by a specific user for the current day from the `Meals` sheet.\n*   **Configuration:**\n    1.  **Credential to connect with:** Select your Google Sheets account credential.\n    2.  **Operation:** `Get Row(s)`.\n    3.  **Document:** Select your Google Sheet.\n    4.  **Sheet:** Select `Meals`.\n    5.  **Filters:**\n        *   **Filter 1:**\n            *   **Column:** `Date`.\n            *   **Value:** `{{ $json.Date }}` (or the expression representing the current date from the input).\n        *   **Filter 2 (Recommended):**\n            *   Click **Add Filter**.\n            *   **Combine Filters:** `AND`.\n            *   **Column:** `User_ID`.\n            *   **Value:** `{{ $json.User_ID }}` (or the expression for the user's ID from the input). This is critical to ensure you only get meals for the correct user.\n\n![](https://github.com/JarsRat/Images/blob/main/Documentation/Screenshot%202025-08-28%20070616.png?raw=true)\n\n\n\n---"
      },
      "typeVersion": 1
    },
    {
      "id": "80401901-9a63-46a8-863d-7851b523904e",
      "name": "Sticky Note23",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3456,
        2784
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 1408,
        "content": "**Node: `Get User Info`**\n*   **Purpose:** Retrieves the user's goals from the `Profile` sheet to calculate their progress in the daily report.\n*   **Configuration:**\n    1.  **Credential to connect with:** Select your Google Sheets account credential.\n    2.  **Operation:** `Get Row(s)`.\n    3.  **Document:** Select your Google Sheet.\n    4.  **Sheet:** Select `Profile`.\n    5.  **Filters > Add Filter:**\n        *   **Column:** `User_ID`.\n        *   **Value:** Use an expression that references the User ID passed into the subworkflow, for example: `{{ $json.User_ID }}`.\n\n![](https://github.com/JarsRat/Images/blob/main/Documentation/Correction.png?raw=true)"
      },
      "typeVersion": 1
    },
    {
      "id": "d0d1277d-ede9-475c-9df9-a12a7587b8ff",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        3616,
        2048
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    }
  ],
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Input Message Router1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "get_message (register)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Get chart message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fix mime": {
      "main": [
        [
          {
            "node": "Analyze voice message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Data": {
      "main": [
        [
          {
            "node": "Unify data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fix mime5": {
      "main": [
        [
          {
            "node": "Analyze image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MarkdownV": {
      "main": [
        [
          {
            "node": "Send a text message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Typing\u2026": {
      "main": [
        []
      ]
    },
    "Get Report": {
      "ai_tool": [
        [
          {
            "node": "Cal IA Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Get report": {
      "main": [
        [
          {
            "node": "Get Meals Info",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get User Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MarkdownV2": {
      "main": [
        [
          {
            "node": "Send a text message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unify data": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Registered?": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cal IA Agent": {
      "main": [
        [
          {
            "node": "MarkdownV2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze image": {
      "main": [
        [
          {
            "node": "get_message (Media  message)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get User Info": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Register User": {
      "ai_tool": [
        [
          {
            "node": "Register Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "Cal IA Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Download IMAGE": {
      "main": [
        [
          {
            "node": "Fix mime5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Meals Info": {
      "main": [
        [
          {
            "node": "Get Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Register Agent": {
      "main": [
        [
          {
            "node": "MarkdownV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory1": {
      "ai_memory": [
        [
          {
            "node": "Register Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Append Meal Data": {
      "ai_tool": [
        [
          {
            "node": "Cal IA Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Get Profile Data": {
      "ai_tool": [
        [
          {
            "node": "Cal IA Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Registered?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Typing\u2026",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get chart message": {
      "main": [
        [
          {
            "node": "Send back message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_error_message1": {
      "main": [
        [
          {
            "node": "Cal IA Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_message (text)": {
      "main": [
        [
          {
            "node": "Cal IA Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Profile Data": {
      "ai_tool": [
        [
          {
            "node": "Cal IA Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Analyze voice message": {
      "main": [
        [
          {
            "node": "get_message (Audio/Video message)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Input Message Router1": {
      "main": [
        [
          {
            "node": "get_message (text)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download Voice Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download IMAGE",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "get_error_message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Voice Message": {
      "main": [
        [
          {
            "node": "Fix mime",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_message (register)": {
      "main": [
        [
          {
            "node": "Register Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Cal IA Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Register Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "get_message (Media  message)": {
      "main": [
        [
          {
            "node": "Cal IA Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_message (Audio/Video message)": {
      "main": [
        [
          {
            "node": "Cal IA Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}