Análisis diario de enfrentamientos de lanzadores vs bateadores de MLB (Google Sheets + Telegram)

Intermedio

Este es unMiscellaneous, Multimodal AIflujo de automatización del dominio deautomatización que contiene 14 nodos.Utiliza principalmente nodos como Code, Telegram, HttpRequest, GoogleSheets, ScheduleTrigger. Análisis diario de lanzadores vs bateadores de MLB con Google Sheets y Telegram

Requisitos previos
  • Bot Token de Telegram
  • Pueden requerirse credenciales de autenticación para la API de destino
  • Credenciales de API de Google Sheets
Vista previa del flujo de trabajo
Visualización de las conexiones entre nodos, con soporte para zoom y panorámica
Exportar flujo de trabajo
Copie la siguiente configuración JSON en n8n para importar y usar este flujo de trabajo
{
  "meta": {
    "instanceId": "4b9ebdcb82324c7ebd0d3194b3afce46ddeac81826241b95da802461b66d7743"
  },
  "nodes": [
    {
      "id": "9a8548ad-12f1-41a7-9226-c8d06806b7b2",
      "name": "3. Extraer todos los ID de jugadores",
      "type": "n8n-nodes-base.code",
      "notes": "who will be playing today.",
      "position": [
        32,
        -128
      ],
      "parameters": {
        "jsCode": "const allPlayerIds = new Set();\nconst allGames = items[0].json.dates[0]?.games || [];\n\nif (allGames.length === 0) {\n  return [];\n}\n\n// Loop through all games to find every player\nfor (const game of allGames) {\n  if (game.teams?.home?.probablePitcher?.id) {\n    allPlayerIds.add(game.teams.home.probablePitcher.id);\n  }\n  if (game.teams?.away?.probablePitcher?.id) {\n    allPlayerIds.add(game.teams.away.probablePitcher.id);\n  }\n  if (game.lineups && Array.isArray(game.lineups.homePlayers)) {\n    for (const player of game.lineups.homePlayers) {\n      if (player.id) allPlayerIds.add(player.id);\n    }\n  }\n  if (game.lineups && Array.isArray(game.lineups.awayPlayers)) {\n    for (const player of game.lineups.awayPlayers) {\n      if (player.id) allPlayerIds.add(player.id);\n    }\n  }\n}\n\nif (allPlayerIds.size === 0) {\n  return [];\n}\n\n// We pass the original game data THROUGH this node by adding it to the output\nconst output = items[0].json;\noutput.playerIdsString = Array.from(allPlayerIds).join(',');\nreturn [{ json: output }];"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "a0e5e8f0-e2fe-415e-92f4-cb4e29a6fd72",
      "name": "4. Obtener estadísticas de jugadores por lotes",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        256,
        -128
      ],
      "parameters": {
        "url": "https://statsapi.mlb.com/api/v1/people",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "personIds",
              "value": "={{$json.playerIdsString}}"
            },
            {
              "name": "hydrate",
              "value": "stats(group=[pitching,hitting,fielding],type=[season])"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "ff23a51f-3dc8-4b85-97b0-611e0bb6a304",
      "name": "2. Obtener juegos diarios",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -176,
        -128
      ],
      "parameters": {
        "url": "https://statsapi.mlb.com/api/v1/schedule",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "sportId",
              "value": "1"
            },
            {
              "name": "date",
              "value": "={{ $now.toFormat('yyyy-MM-dd') }}"
            },
            {
              "name": "hydrate",
              "value": "probablePitcher,lineups"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "32ccdb2c-0b9d-4af4-9590-c58b9902c31c",
      "name": "5. Crear filas finales de enfrentamientos",
      "type": "n8n-nodes-base.code",
      "notes": "game schedule data and merges it with the detailed player stats",
      "position": [
        496,
        -128
      ],
      "parameters": {
        "jsCode": "const allMatchupRows = [];\n\n// Get player stats from the direct input of this node\nconst playerStatsData = items[0].json;\n\n// Get the original game data by looking back at the previous node\nconst scheduleNode = $('3. Extract All Player IDs').first();\nconst originalScheduleData = scheduleNode.json;\nconst games = originalScheduleData.dates[0]?.games || [];\n\nconst playersWithStats = playerStatsData.people || [];\n\nif (games.length === 0 || playersWithStats.length === 0) {\n  return [];\n}\n\nconst statsMap = new Map(playersWithStats.map(p => [p.id, p]));\n\nfor (const game of games) {\n\n  const gameStartTime = game.gameDate || 'N/A';\n\n  // --- Home Pitcher vs Away Batters ---\n  const homePitcherId = game.teams?.home?.probablePitcher?.id;\n  const awayLineup = game.lineups?.awayPlayers;\n\n  if (homePitcherId && Array.isArray(awayLineup)) {\n    const pitcherData = statsMap.get(homePitcherId);\n    if (pitcherData) {\n      for (const batter of awayLineup) {\n        const batterData = statsMap.get(batter.id);\n        if (batterData) {\n          const pitcherStats = pitcherData.stats?.find(s => s.group?.displayName === 'pitching')?.splits[0]?.stat || {};\n          const batterStats = batterData.stats?.find(s => s.group?.displayName === 'hitting')?.splits[0]?.stat || {};\n          allMatchupRows.push({\n            gameStartTime: gameStartTime,\n            gameDate: game.officialDate || 'N/A',\n            pitcherName: pitcherData.fullName || 'N/A',\n            pitcherTeam: game.teams?.home?.team?.name || 'N/A',\n            pitcherThrows: pitcherData.pitchHand?.description || 'N/A',\n            pitcherERA: pitcherStats.era,\n            pitcherSO: pitcherStats.strikeOuts,\n            opponent: game.teams?.away?.team?.name || 'N/A',\n            batterName: batterData.fullName || 'N/A',\n            batterPosition: batter.primaryPosition?.abbreviation || 'N/A',\n            batterBats: batterData.batSide?.description || 'N/A',\n            batterAVG: batterStats.avg,\n            batterOPS: batterStats.ops,\n            batterHR: batterStats.homeRuns,\n            batterHits: batterStats.hits,\n            batterRBI: batterStats.rbi,\n            pitcherId: pitcherData.id, // ADDED: Pitcher ID\n            batterId: batterData.id,   // ADDED: Batter ID\n          });\n        }\n      }\n    }\n  }\n\n  // --- Away Pitcher vs Home Batters ---\n  const awayPitcherId = game.teams?.away?.probablePitcher?.id;\n  const homeLineup = game.lineups?.homePlayers;\n\n  if (awayPitcherId && Array.isArray(homeLineup)) {\n    const pitcherData = statsMap.get(awayPitcherId);\n    if (pitcherData) {\n      for (const batter of homeLineup) {\n        const batterData = statsMap.get(batter.id);\n        if (batterData) {\n          const pitcherStats = pitcherData.stats?.find(s => s.group?.displayName === 'pitching')?.splits[0]?.stat || {};\n          const batterStats = batterData.stats?.find(s => s.group?.displayName === 'hitting')?.splits[0]?.stat || {};\n          allMatchupRows.push({\n            gameStartTime: gameStartTime,\n            gameDate: game.officialDate || 'N/A',\n            pitcherName: pitcherData.fullName || 'N/A',\n            pitcherTeam: game.teams?.away?.team?.name || 'N/A',\n            pitcherThrows: pitcherData.pitchHand?.description || 'N/A',\n            pitcherERA: pitcherStats.era,\n            pitcherSO: pitcherStats.strikeOuts,\n            opponent: game.teams?.home?.team?.name || 'N/A',\n            batterName: batterData.fullName || 'N/A',\n            batterPosition: batter.primaryPosition?.abbreviation || 'N/A',\n            batterBats: batterData.batSide?.description || 'N/A',\n            batterAVG: batterStats.avg,\n            batterOPS: batterStats.ops,\n            batterHR: batterStats.homeRuns,\n            batterHits: batterStats.hits,\n            batterRBI: batterStats.rbi,\n            pitcherId: pitcherData.id, // ADDED: Pitcher ID\n            batterId: batterData.id,   // ADDED: Batter ID\n          });\n        }\n      }\n    }\n  }\n}\n\nreturn allMatchupRows.map(row => ({ json: row }));"
      },
      "notesInFlow": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "73bac962-fa64-45dc-86bd-671a17488992",
      "name": "6. Filtrar mejores enfrentamientos",
      "type": "n8n-nodes-base.code",
      "notes": "Pandas to filter and sort before Sheets",
      "position": [
        736,
        -128
      ],
      "parameters": {
        "jsCode": "// Convert to Eastern Time using proper timezone handling\nfunction convertToEasternTime(isoString) {\n\tconst date = new Date(isoString);\n\treturn date.toLocaleTimeString('en-US', {\n\t\thour: '2-digit',\n\t\tminute: '2-digit',\n\t\thour12: true,\n\t\ttimeZone: 'America/New_York'\n\t});\n}\n\nconst HEADER_ORDER = [\n\t\"gameDate\",\n\t\"gameStartTime\",\n\t\"pitcherId\",\n\t\"pitcherName\",\n\t\"pitcherTeam\",\n\t\"pitcherSO\",\n\t\"pitcherERA\",\n\t\"pitcherThrows\",\n\t\"batterBats\",\n\t\"opponent\",\n\t\"batterName\",\n\t\"batterAVG\",\n\t\"batterHR\",\n\t\"batterHits\",\n\t\"batterOPS\",\n\t\"batterRBI\",\n\t\"batterId\",\n\t\"batterPosition\"\n];\n\nconst allMatchups = items.map(item => item.json);\n\n// Filter valid rows\nconst valid = allMatchups.filter(m => {\n\tconst era = parseFloat(m.pitcherERA);\n\tconst ops = parseFloat(m.batterOPS);\n\treturn !isNaN(era) && era > 3.33 && !isNaN(ops) && m.gameStartTime;\n});\n\n// Parse and add helper fields\nvalid.forEach(m => {\n\tm.era = parseFloat(m.pitcherERA);\n\tm.ops = parseFloat(m.batterOPS);\n\tm.gameStartObj = new Date(m.gameStartTime);\n});\n\n// Step 1: Get top 9 unique pitchers with highest ERA\nconst seenPitchers = new Set();\nconst topPitchers = [];\n\nvalid\n\t.sort((a, b) => b.era - a.era)\n\t.forEach(m => {\n\t\tif (!seenPitchers.has(m.pitcherName)) {\n\t\t\tseenPitchers.add(m.pitcherName);\n\t\t\ttopPitchers.push(m.pitcherName);\n\t\t}\n\t});\n\nconst top9 = topPitchers.slice(0, 9);\n\n// Step 2: For each of these pitchers, get top 3 batters by OPS\nconst final = [];\n\ntop9.forEach(pitcherName => {\n\tconst matchups = valid.filter(m => m.pitcherName === pitcherName);\n\tconst seenBatters = new Set();\n\tconst topBatters = [];\n\n\tmatchups\n\t\t.sort((a, b) => b.ops - a.ops)\n\t\t.forEach(m => {\n\t\t\tif (!seenBatters.has(m.batterId)) {\n\t\t\t\tseenBatters.add(m.batterId);\n\t\t\t\ttopBatters.push(m);\n\t\t\t}\n\t\t});\n\n\tfinal.push(...topBatters.slice(0, 3));\n});\n\n// Final sort by game start time\nfinal.sort((a, b) => a.gameStartObj - b.gameStartObj);\n\n// Format time and construct output with locked header order\nconst output = final.map(m => {\n\tm.gameStartTime = convertToEasternTime(m.gameStartObj.toISOString());\n\tconst obj = {};\n\tHEADER_ORDER.forEach(key => obj[key] = m[key] ?? '');\n\treturn { json: obj };\n});\n\nreturn output;\n"
      },
      "notesInFlow": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "7d9f19aa-a8a6-4f50-8462-0cad13de6e9c",
      "name": "Nota adhesiva",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        -368
      ],
      "parameters": {
        "color": 4,
        "width": 2316,
        "height": 500,
        "content": "Hits"
      },
      "typeVersion": 1
    },
    {
      "id": "38b3c276-1d1e-4ea5-8427-ddb9f6591bbe",
      "name": "Orden de columnas",
      "type": "n8n-nodes-base.code",
      "position": [
        944,
        -128
      ],
      "parameters": {
        "jsCode": "const orderedKeys = [\n  \"gameDate\",\n  \"gameStartTime\",\n  \"pitcherId\",\n  \"pitcherName\",\n  \"pitcherTeam\",\n  \"pitcherSO\",\n  \"pitcherERA\",\n  \"pitcherThrows\",\n  \"batterBats\",\n  \"opponent\",\n  \"batterName\",\n  \"batterAVG\",\n  \"batterHR\",\n  \"batterHits\",\n  \"batterOPS\",\n  \"batterRBI\",\n  \"batterId\",\n  \"batterPosition\"\n];\n\n// fields that should explicitly be numbers\nconst numericKeys = [\n  \"pitcherId\",\n  \"pitcherSO\",\n  \"pitcherERA\",\n  \"batterAVG\",\n  \"batterHR\",\n  \"batterHits\",\n  \"batterOPS\",\n  \"batterRBI\",\n  \"batterId\"\n];\n\n// 🔷 Helper: format to hh:mm AM/PM ET\nfunction toEasternTimeHHMM(value) {\n  if (typeof value === 'string' && /^\\d{1,2}:\\d{2}/.test(value)) {\n    return value.replace(/:00$/, ''); // clean up trailing :00 if it’s there\n  }\n  try {\n    const utcDate = new Date(value);\n    if (isNaN(utcDate.getTime())) return value;\n    const options = {\n      timeZone: 'America/New_York',\n      hour: '2-digit',\n      minute: '2-digit',\n      hour12: true\n    };\n    return new Intl.DateTimeFormat('en-US', options).format(utcDate);\n  } catch {\n    return value;\n  }\n}\n\n// Build new list\nconst enriched = items.map(item => {\n  const json = { ...item.json };\n\n  if (json.gameStartTime) {\n    json.gameStartTime = toEasternTimeHHMM(json.gameStartTime);\n  }\n\n  const output = {};\n  for (const key of orderedKeys) {\n    let val = json[key] ?? \"\";\n    if (numericKeys.includes(key) && val !== \"\") {\n      val = Number(val);\n\n      // Round batterAVG and batterOPS to .000\n      if (key === \"batterAVG\" || key === \"batterOPS\") {\n        val = Number(val.toFixed(3));\n      }\n    }\n    output[key] = val;\n  }\n\n  return { json: output };\n});\n\n// Sort enriched list\nenriched.sort((a, b) => {\n  const parseTime = (timeStr) => {\n    const [time, meridian] = timeStr.split(' ');\n    let [hours, minutes] = time.split(':').map(Number);\n    if (meridian === 'PM' && hours !== 12) hours += 12;\n    if (meridian === 'AM' && hours === 12) hours = 0;\n    return hours * 60 + minutes; // total minutes since midnight\n  };\n\n  const aTime = parseTime(a.json.gameStartTime);\n  const bTime = parseTime(b.json.gameStartTime);\n  if (aTime < bTime) return -1;\n  if (aTime > bTime) return 1;\n\n  const aPitcher = Number(a.json.pitcherId) || 0;\n  const bPitcher = Number(b.json.pitcherId) || 0;\n  if (aPitcher < bPitcher) return -1;\n  if (aPitcher > bPitcher) return 1;\n\n  const aOpp = a.json.opponent || \"\";\n  const bOpp = b.json.opponent || \"\";\n  return aOpp.localeCompare(bOpp);\n});\n\n\nreturn enriched;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "09b74296-10fa-43a4-8fb4-65a017dd9f78",
      "name": "9am Limpiar",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -400,
        -304
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            },
            {
              "triggerAtHour": 9,
              "triggerAtMinute": 15
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "31005912-843f-4c2f-8614-c92c00404fb8",
      "name": "11:02 - 8:02",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "02 11-20 * * *",
      "position": [
        -384,
        -128
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "02 11-20 * * *"
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 1
    },
    {
      "id": "e7d4e4e6-09f1-4ce0-820c-a60cd795a858",
      "name": "Notas: Hits de MLB",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -848
      ],
      "parameters": {
        "width": 752,
        "height": 656,
        "content": "MLB \"Hits\" Workflow — Overview\n• Pulls today's MLB schedule incl. probablePitcher + lineups (statsapi.mlb.com)\n• Batches season stats for all involved players\n• Builds pitcher vs. batter matchup rows\n• Filters: ERA > 3.33, take top 9 pitchers by ERA, then top 3 opposing batters by OPS each (27 rows)\n• Sorts by ET start time, writes to Google Sheets (Your Google SHeet → the tabName)\n• Sends Telegram message: Top 21 batters by OPS\n\nSetup\n1) Google Sheets OAuth2 → point to your Google Sheet → Tab name\n2) Telegram Bot cred + chatId\n3) Optional: tweak ERA/OPS thresholds or Top N in nodes 6 or 8.\n\nTriggers\n• \"11:02 – 8:02\" runs hourly at :02 (server time)\n• \"9am Clear\" clears the sheet at 09:00 & 09:15\n\nKey Nodes\n• 2. Get Daily Games — GET /schedule (date=today; hydrate=probablePitcher,lineups)\n• 3. Extract All Player IDs — collects personIds (pitchers + lineups)\n• 4. Get Batched Player Stats — GET /people?personIds=...&hydrate=stats(...)\n• 5. Create Final Matchup Rows — merges schedule + stats into pitcher↔batter rows\n• 6. Filter for Top Matchups — ERA/OPS filter; pick top 9×3; convert times to ET\n• Column Order — enforce column order, numeric typing, rounding (.000 for AVG/OPS)\n• 7. Update n8n-sheet — append/update by batterId\n• 8. 21 Hitters — composes Top 21 by OPS message\n• 9. Sends Telegram\n\nNotes\n• If lineups/schedule not ready yet, downstream may be empty (expected)\n• Keep node name \"3. Extract All Player IDs\" exact (used by $())\n• ET conversion is in code; cron uses server time"
      },
      "typeVersion": 1
    },
    {
      "id": "6ef30ef8-0023-42bc-b322-2fffd650894b",
      "name": "8. 21 Bateadores",
      "type": "n8n-nodes-base.code",
      "position": [
        1408,
        -128
      ],
      "parameters": {
        "jsCode": "// This code is for an n8n Code node.\n// It assumes the input from the preceding node (which now provides all 27 batters)\n// allows 'items' to contain all batter records.\n\nconsole.log(\"--- Code Node for Telegram Message Start ---\");\n\n// --- Input Processing (same as before to get all 27 batters) ---\nlet allBatterStats = [];\n\n// This block handles both scenarios:\n// 1. If \"Run Once for All Items\" is ON, 'items' contains all incoming records.\n// 2. If an \"Item Lists (Aggregate)\" node precedes this, 'items[0].json' will be the array.\nfor (const item of items) {\n  // Scenario A: Item contains a single object (e.g., from a direct data source outputting individual items)\n  if (item && typeof item.json === 'object' && item.json !== null && !Array.isArray(item.json)) {\n    allBatterStats.push(item.json);\n  }\n  // Scenario B: Item.json is an array (e.g., from an Item Lists aggregate node, or if a source directly outputs a single array)\n  else if (item && Array.isArray(item.json)) {\n    allBatterStats.push(...item.json);\n  }\n  // Fallback: If the item itself (not its .json) is the object, or other unexpected structures\n  else if (typeof item === 'object' && item !== null && item.batterName && item.batterHits) {\n      allBatterStats.push(item);\n  } else {\n      console.warn(\"Skipping an input item with an unrecognized structure:\", JSON.stringify(item, null, 2));\n  }\n}\n\nconsole.log(\"Total batter stats collected:\", allBatterStats.length);\n\n// Ensure batterHits is a number and filter out invalid entries.\nconst processedBatterStats = allBatterStats.map(batter => {\n    const newBatter = { ...batter };\n    newBatter.batterHits = typeof batter.batterHits === 'string'\n                           ? parseFloat(batter.batterHits)\n                           : batter.batterHits;\n    if (isNaN(newBatter.batterHits)) {\n        newBatter.batterHits = 0; // Default invalid numbers to 0\n    }\n    return newBatter;\n}).filter(batter =>\n    typeof batter === 'object' &&\n    batter !== null &&\n    typeof batter.batterHits === 'number' &&\n    typeof batter.batterName === 'string' &&\n    batter.batterName.trim() !== ''\n);\n\nconsole.log(\"Number of valid and processed batter stats:\", processedBatterStats.length);\n\nif (processedBatterStats.length === 0) {\n  console.warn(\"No valid batter stats found. Returning empty message.\");\n  return [{ json: { text: \"No batter stats available to display.\" } }];\n}\n\n// Sort the stats by batterHits in descending order.\nprocessedBatterStats.sort((a, b) => b.batterOPS - a.batterOPS);\n\n// Get the top 21 batters.\nconst top21Batters = processedBatterStats.slice(0, 21);\n\nconsole.log(\"Number of top 21 batters selected:\", top21Batters.length);\n\n// --- Telegram Message Formatting ---\n\n// Start the message with a title. Using Markdown for bold.\nlet telegramMessage = \"⚾ **Top 21 Batters by OPS** ⚾\\n\\n\";\n\n// Add each batter to the message string.\ntop21Batters.forEach((batter, index) => {\n    // Format each line using Markdown for name and hits.\n    // Remember to escape any special Markdown characters in the data if necessary,\n    // though typically names and numbers are safe.\n    telegramMessage += `${index + 1}. **${batter.batterName}**: ${batter.batterOPS}\\n`;\n});\n\nconsole.log(\"Generated Telegram message length:\", telegramMessage.length);\n\n// --- Return as a single n8n item for the Telegram node ---\n// The Telegram node's 'Text' field will consume the 'text' property from this output.\nreturn [{\n  json: {\n    message: telegramMessage // This is the single string containing the entire message\n  }\n}];"
      },
      "executeOnce": false,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "5d726f93-c7f4-42d6-b0a0-c0ff8abc159b",
      "name": "Limpiar tu hoja",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -176,
        -304
      ],
      "parameters": {
        "operation": "clear",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "bL32JLOAfaCKsZaj",
          "name": "Google Sheets account 2"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "29d5c57d-4f31-4fcd-aea0-865341d60d9c",
      "name": "7. Actualizar tu hoja",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueErrorOutput",
      "position": [
        1168,
        -128
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "bL32JLOAfaCKsZaj",
          "name": "Google Sheets account 2"
        }
      },
      "notesInFlow": true,
      "retryOnFail": true,
      "typeVersion": 4,
      "alwaysOutputData": true,
      "waitBetweenTries": 5000
    },
    {
      "id": "e9c4377c-93c9-4286-b567-e233e5e65014",
      "name": "9. Enviar a chatbot de Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1600,
        -128
      ],
      "webhookId": "3f317a60-e451-4150-9b04-2f9e5427a22d",
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "createYourOwnOnTelegram@BOTFather",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "5HNLqEK3ZgiYkMmw",
          "name": "Telegram account"
        }
      },
      "executeOnce": true,
      "typeVersion": 1.2
    }
  ],
  "pinData": {
    "9am Clear": [
      {
        "Hour": "09",
        "Year": "2025",
        "Month": "August",
        "Minute": "00",
        "Second": "32",
        "Timezone": "America/New_York (UTC-04:00)",
        "timestamp": "2025-08-04T09:00:32.005-04:00",
        "Day of week": "Monday",
        "Day of month": "04",
        "Readable date": "August 4th 2025, 9:00:32 am",
        "Readable time": "9:00:32 am"
      }
    ]
  },
  "connections": {
    "09b74296-10fa-43a4-8fb4-65a017dd9f78": {
      "main": [
        [
          {
            "node": "5d726f93-c7f4-42d6-b0a0-c0ff8abc159b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "31005912-843f-4c2f-8614-c92c00404fb8": {
      "main": [
        [
          {
            "node": "ff23a51f-3dc8-4b85-97b0-611e0bb6a304",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "38b3c276-1d1e-4ea5-8427-ddb9f6591bbe": {
      "main": [
        [
          {
            "node": "29d5c57d-4f31-4fcd-aea0-865341d60d9c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6ef30ef8-0023-42bc-b322-2fffd650894b": {
      "main": [
        [
          {
            "node": "e9c4377c-93c9-4286-b567-e233e5e65014",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ff23a51f-3dc8-4b85-97b0-611e0bb6a304": {
      "main": [
        [
          {
            "node": "9a8548ad-12f1-41a7-9226-c8d06806b7b2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "29d5c57d-4f31-4fcd-aea0-865341d60d9c": {
      "main": [
        [
          {
            "node": "6ef30ef8-0023-42bc-b322-2fffd650894b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9a8548ad-12f1-41a7-9226-c8d06806b7b2": {
      "main": [
        [
          {
            "node": "a0e5e8f0-e2fe-415e-92f4-cb4e29a6fd72",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a0e5e8f0-e2fe-415e-92f4-cb4e29a6fd72": {
      "main": [
        [
          {
            "node": "32ccdb2c-0b9d-4af4-9590-c58b9902c31c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "73bac962-fa64-45dc-86bd-671a17488992": {
      "main": [
        [
          {
            "node": "38b3c276-1d1e-4ea5-8427-ddb9f6591bbe",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "32ccdb2c-0b9d-4af4-9590-c58b9902c31c": {
      "main": [
        [
          {
            "node": "73bac962-fa64-45dc-86bd-671a17488992",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Preguntas frecuentes

¿Cómo usar este flujo de trabajo?

Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.

¿En qué escenarios es adecuado este flujo de trabajo?

Intermedio - Varios, IA Multimodal

¿Es de pago?

Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.

Flujos de trabajo relacionados recomendados

Generar blogs de WordPress optimizados para SEO con Gemini, Tavily y revisión humana
Usar Gemini, Tavily y revisión humana para generar blogs de WordPress optimizados para SEO
If
Set
Code
+
If
Set
Code
38 NodosAryan Shinde
Creación de contenido
Publicador de tuits de X y Meta Threads
Usar Late API y Google Sheets para publicar automáticamente contenido optimizado para plataformas en X y Threads
If
Set
Code
+
If
Set
Code
20 NodosFariez
Redes sociales
Descarga automática de Reels de Instagram con almacenamiento en Google Drive y recordatorios por Telegram
Automatizar la descarga de Reels de Instagram con almacenamiento en Google Drive y recordatorios de Telegram
If
Code
Webhook
+
If
Code
Webhook
11 NodosAryan Shinde
Gestión de archivos
Verificación de vencimiento y recordatorios de actualización para publicaciones de empleos desde Google Sheets usando HTTP Last-Modified
Automatización de recordatorios de publicación de empleos vencidos con Google Sheets, verificaciones HTTP y Gmail
If
Set
Code
+
If
Set
Code
19 NodosWeblineIndia
Recursos Humanos
Descubrimiento y publicación automatizados de noticias con GPT-4, Google Search API y Slack
Automatizar el descubrimiento y publicación de noticias con GPT-4, Google Search API y Slack
Code
Slack
Http Request
+
Code
Slack
Http Request
14 NodosKalyxi Ai
Varios
💥 Crear anuncios virales con NanoBanana y Seedance, publicar videos en redes sociales mediante upload-post VIDE II
Usar IA para crear anuncios virales multimedia: NanoBanana, Seedance y Suno para redes sociales
If
Set
Code
+
If
Set
Code
45 NodosDr. Firas
Varios
Información del flujo de trabajo
Nivel de dificultad
Intermedio
Número de nodos14
Categoría2
Tipos de nodos6
Descripción de la dificultad

Adecuado para usuarios con experiencia intermedia, flujos de trabajo de complejidad media con 6-15 nodos

Enlaces externos
Ver en n8n.io

Compartir este flujo de trabajo

Categorías

Categorías: 34