AutomationFlowsWeb Scraping › Sistema Inteligente De Viajes De Perú — V2.3

Sistema Inteligente De Viajes De Perú — V2.3

Sistema Inteligente de Viajes de Perú — v2.3. Uses httpRequest. Webhook trigger; 38 nodes.

Webhook trigger★★★★★ complexity38 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 38 Complexity: ★★★★★ Added:
Sistema Inteligente De Viajes De Perú — V2.3 — n8n workflow card showing HTTP Request integration

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Sistema Inteligente de Viajes de Per\u00fa \u2014 v2.3",
  "nodes": [
    {
      "id": "s-title",
      "name": "\ud83d\udccc T\u00edtulo",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1120,
        -320
      ],
      "parameters": {
        "width": 5400,
        "height": 120,
        "color": 7,
        "content": "## \ud83c\uddf5\ud83c\uddea Sistema Inteligente de Viajes de Per\u00fa \u2014 v2.3\n**Stack**: React 18 \u00b7 FastAPI \u00b7 Mistral AI \u00b7 Tavily \u00b7 Firebase \u00b7 Firestore \u00b7 Railway \u00b7 Desarrollado por Oscar Zelada Pozo"
      }
    },
    {
      "id": "s-f1",
      "name": "\ud83d\udccc Fondo Chat",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1120,
        -160
      ],
      "parameters": {
        "width": 5400,
        "height": 740,
        "color": 5,
        "content": "## \ud83e\udd16 FLUJO 1 \u2014 Chat Conversacional IA\n`POST /chat` \u00b7 Roles: **assistant_user \u00b7 admin** \u00b7 Fuente: `backend/main.py` \u2192 `backend/agent.py`"
      }
    },
    {
      "id": "s-f2",
      "name": "\ud83d\udccc Fondo Im\u00e1genes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1120,
        660
      ],
      "parameters": {
        "width": 4200,
        "height": 480,
        "color": 6,
        "content": "## \ud83d\udcf8 FLUJO 2 \u2014 Galer\u00eda de Im\u00e1genes por Destino\n`GET /images/{destination}` \u00b7 P\u00fablico \u00b7 Tavily Image Search \u2192 httpx concurrente \u2192 Base64 \u00b7 Cach\u00e9 en memoria 24h"
      }
    },
    {
      "id": "s-f3",
      "name": "\ud83d\udccc Fondo Mapa",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1120,
        1220
      ],
      "parameters": {
        "width": 3800,
        "height": 480,
        "color": 3,
        "content": "## \ud83d\uddfa\ufe0f FLUJO 3 \u2014 Mapa de Ruta Interactivo\n`GET /route-map/{destination}` \u00b7 P\u00fablico \u00b7 Nominatim geocoding \u2192 OSRM routing \u2192 staticmap PNG \u2192 Base64"
      }
    },
    {
      "id": "s-f4",
      "name": "\ud83d\udccc Fondo Resumen",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1120,
        1780
      ],
      "parameters": {
        "width": 3000,
        "height": 460,
        "color": 1,
        "content": "## \ud83d\udcc4 FLUJO 4 \u2014 Resumen Ejecutivo PDF (IA)\n`POST /summary` \u00b7 Roles: **assistant_user \u00b7 admin** \u00b7 Mistral AI extrae JSON estructurado: destino \u00b7 clima \u00b7 costos \u00b7 lugares \u00b7 consejos"
      }
    },
    {
      "id": "f1-webhook",
      "name": "POST /chat",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -880,
        100
      ],
      "parameters": {
        "path": "chat",
        "httpMethod": "POST",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "f1-auth",
      "name": "\ud83d\udd10 Verificar JWT Firebase",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -560,
        100
      ],
      "parameters": {
        "jsCode": "// auth.py \u2192 get_current_user()\n// Firebase Admin SDK: admin.auth().verify_id_token(token)\n// Extrae del token: uid, email, role (custom claim)\n// Lanza 401 si token inv\u00e1lido o expirado\nconst bearer = items[0].json.headers?.authorization ?? '';\nconst token = bearer.replace('Bearer ', '');\nconst decoded = await admin.auth().verifyIdToken(token);\nreturn [{ json: { uid: decoded.uid, email: decoded.email, role: decoded.role ?? null,\n  message: items[0].json.body.message, chat_id: items[0].json.body.chat_id } }];"
      }
    },
    {
      "id": "f1-role",
      "name": "\ud83c\udfad \u00bfRol assistant_user o admin?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -240,
        100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "r1",
              "leftValue": "={{ $json.role }}",
              "rightValue": "assistant_user",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "r2",
              "leftValue": "={{ $json.role }}",
              "rightValue": "admin",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      }
    },
    {
      "id": "f1-403",
      "name": "\u274c 403 Sin Permisos",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -240,
        360
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\"detail\":\"Insufficient permissions \u2014 role required: assistant_user or admin\"}",
        "options": {
          "responseCode": 403
        }
      }
    },
    {
      "id": "f1-history",
      "name": "\ud83d\udcda Obtener Historial Firestore",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        80,
        100
      ],
      "parameters": {
        "jsCode": "// firestore_service.py \u2192 get_chat_messages(uid, chat_id)\n// Colecci\u00f3n: sessions/{uid}/chats/{chat_id} \u2192 messages[]\n// Retorna lista [{role, content, tokens, timestamp}] para contexto del LLM\nconst { uid, chat_id, message } = $json;\nconst doc = await db.collection('sessions').doc(uid)\n  .collection('chats').doc(chat_id).get();\nconst history = doc.data()?.messages ?? [];\nreturn [{ json: { uid, chat_id, message, history } }];"
      }
    },
    {
      "id": "f1-searchif",
      "name": "\ud83d\udd0d \u00bfRequiere B\u00fasqueda Web?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        400,
        100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false
          },
          "conditions": [
            {
              "id": "s1",
              "leftValue": "={{ $json.message }}",
              "rightValue": "vuelo|bus|precio|hotel|clima|costo|tarifa|horario|cusco|lima|arequipa|trujillo|iquitos|piura|aerol",
              "operator": {
                "type": "string",
                "operation": "regex"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "f1-tav-transport",
      "name": "\ud83c\udf10 Tavily: Transporte y Turismo",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        720,
        -60
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.tavily.com/search",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ api_key: $env.TAVILY_API_KEY, query: $json.message + ' precios transporte Peru aereo terrestre hospedaje turismo', max_results: 4 }) }}",
        "options": {
          "timeout": 15000
        }
      }
    },
    {
      "id": "f1-tav-weather",
      "name": "\ud83c\udf10 Tavily: Clima Meteorol\u00f3gico",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        720,
        160
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.tavily.com/search",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ api_key: $env.TAVILY_API_KEY, query: 'clima temperatura lluvia ' + ($json.message.match(/cusco|lima|arequipa|trujillo|iquitos|piura|puno/i)?.[0] ?? 'Lima') + ' Peru hoy', max_results: 2 }) }}",
        "options": {
          "timeout": 15000
        }
      }
    },
    {
      "id": "f1-noop",
      "name": "\u23ed\ufe0f Sin B\u00fasqueda (respuesta r\u00e1pida)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        720,
        340
      ]
    },
    {
      "id": "f1-merge",
      "name": "\ud83d\udd17 Combinar Contexto de B\u00fasqueda",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        1040,
        60
      ],
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      }
    },
    {
      "id": "f1-mistral",
      "name": "\ud83e\udde0 Mistral AI \u2014 mistral-large-latest",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1360,
        100
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.mistral.ai/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.MISTRAL_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'mistral-large-latest', temperature: 0.3, messages: [{ role: 'system', content: 'Eres el asistente del Sistema Inteligente de Viajes de Peru. Fecha: ' + new Date().toLocaleDateString('es-PE') + '. Usa emojis, tablas markdown y secciones. Incluye siempre clima del destino.' }, ...$json.history.map(h => ({ role: h.role, content: h.content })), { role: 'user', content: $json.message }] }) }}",
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "f1-save",
      "name": "\ud83d\udcbe Guardar Mensajes en Firestore",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        100
      ],
      "parameters": {
        "jsCode": "// firestore_service.py \u2192 save_chat_message()\n// Guarda mensaje usuario (tokens=0) + respuesta asistente (tokens=total)\n// Actualiza: message_count, preview, created_at en el documento del chat\nconst reply = $json.choices[0].message.content;\nconst tokens = $json.usage?.total_tokens ?? 0;\nconst { uid, chat_id, message } = $('\ud83d\udcda Obtener Historial Firestore').first().json;\nawait saveMsg(uid, chat_id, 'user', message, 0);\nawait saveMsg(uid, chat_id, 'assistant', reply, tokens);\nreturn [{ json: { reply, chat_id, tokens_used: tokens, used_search: true } }];"
      }
    },
    {
      "id": "f1-respond",
      "name": "\u2705 Respuesta JSON \u2192 React Frontend",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2000,
        100
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ reply: $json.reply, chat_id: $json.chat_id, tokens_used: $json.tokens_used, used_search: $json.used_search }) }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "f2-webhook",
      "name": "GET /images/{destination}",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -880,
        860
      ],
      "parameters": {
        "path": "images/:destination",
        "httpMethod": "GET",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "f2-cacheif",
      "name": "\u26a1 \u00bfCache v\u00e1lido? (TTL 24h)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -560,
        860
      ],
      "parameters": {
        "conditions": {
          "options": {},
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.cache_age_seconds }}",
              "rightValue": 86400,
              "operator": {
                "type": "number",
                "operation": "lt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "f2-cache-hit",
      "name": "\u26a1 Servir desde Cach\u00e9",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -240,
        720
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json.cached_result) }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "f2-tav1",
      "name": "\ud83d\udd0d Tavily: Ciudad y Turismo (include_images)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -240,
        860
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.tavily.com/search",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ api_key: $env.TAVILY_API_KEY, query: 'fotos ' + $json.destination + ' Peru plaza ciudad turismo atractivos turisticos', max_results: 3, include_images: true }) }}",
        "options": {}
      }
    },
    {
      "id": "f2-tav2",
      "name": "\ud83d\udd0d Tavily: Gastronom\u00eda y Hospedaje (include_images)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        80,
        860
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.tavily.com/search",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ api_key: $env.TAVILY_API_KEY, query: 'gastronomia comida tipica hotel hospedaje ' + $json.destination + ' Peru', max_results: 3, include_images: true }) }}",
        "options": {}
      }
    },
    {
      "id": "f2-merge",
      "name": "\ud83d\udd17 Combinar URLs de Im\u00e1genes",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        400,
        860
      ],
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      }
    },
    {
      "id": "f2-download",
      "name": "\u2b07\ufe0f Descargar Im\u00e1genes \u2014 httpx async (max 8)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        860
      ],
      "parameters": {
        "jsCode": "// backend/main.py \u2192 _download_img() con httpx.AsyncClient\n// Descarga concurrente de hasta 8 URLs candidatas con asyncio.gather()\n// Filtros: content-type image/* \u00b7 tama\u00f1o < 4MB \u00b7 excluye SVG\n// Resultado: [{ title, data: 'data:image/jpeg;base64,...' }]\nconst urls1 = $input.all()[0].json.images ?? [];\nconst urls2 = $input.all()[1].json.images ?? [];\nconst allUrls = [...urls1, ...urls2].slice(0, 8);\nconst downloaded = await Promise.all(allUrls.map(url => downloadBase64(url)));\nconst images = downloaded.filter(Boolean).slice(0, 6);\nreturn [{ json: { images, destination: $('GET /images/{destination}').first().json.destination } }];"
      }
    },
    {
      "id": "f2-cache-store",
      "name": "\ud83d\udcbe Almacenar en Cach\u00e9 Memoria Python",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        860
      ],
      "parameters": {
        "jsCode": "// backend/main.py \u2192 _img_cache[key] = (result, time.time())\n// Estructura: { plaza: img0, top: img1, extras: [img2..img5] }\n// TTL: 86400 s \u00b7 Primera consulta ~8-14s \u00b7 Subsiguientes ~1-2s\nconst imgs = $json.images;\nconst result = { plaza: imgs[0] ?? null, top: imgs[1] ?? null, extras: imgs.slice(2) };\nstoreCache($json.destination, result);\nreturn [{ json: result }];"
      }
    },
    {
      "id": "f2-respond",
      "name": "\u2705 Base64 Images \u2192 jsPDF y Chat",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1360,
        860
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "f3-webhook",
      "name": "GET /route-map/{destination}",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -880,
        1420
      ],
      "parameters": {
        "path": "route-map/:destination",
        "httpMethod": "GET",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "f3-nom1",
      "name": "\ud83d\udccd Nominatim: Plaza de Armas (origen)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -560,
        1420
      ],
      "parameters": {
        "method": "GET",
        "url": "https://nominatim.openstreetmap.org/search",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "=Plaza de Armas {{ $json.destination }} Peru"
            },
            {
              "name": "format",
              "value": "json"
            },
            {
              "name": "limit",
              "value": "1"
            },
            {
              "name": "countrycodes",
              "value": "pe"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "TravelPeru/2.2 oszeladap@gmail.com"
            }
          ]
        },
        "options": {
          "timeout": 10000
        }
      }
    },
    {
      "id": "f3-nom2",
      "name": "\ud83d\udccd Nominatim: Atracci\u00f3n Principal (destino)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -240,
        1420
      ],
      "parameters": {
        "method": "GET",
        "url": "https://nominatim.openstreetmap.org/search",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{ $json.top_attraction + ' ' + $json.destination + ' Peru' }}"
            },
            {
              "name": "format",
              "value": "json"
            },
            {
              "name": "limit",
              "value": "1"
            },
            {
              "name": "countrycodes",
              "value": "pe"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "TravelPeru/2.2 oszeladap@gmail.com"
            }
          ]
        },
        "options": {
          "timeout": 10000
        }
      }
    },
    {
      "id": "f3-osrm",
      "name": "\ud83d\udee3\ufe0f OSRM: Ruta Driving con GeoJSON",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        80,
        1420
      ],
      "parameters": {
        "method": "GET",
        "url": "=https://router.project-osrm.org/route/v1/driving/{{ $json.from_lon }},{{ $json.from_lat }};{{ $json.to_lon }},{{ $json.to_lat }}",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "overview",
              "value": "full"
            },
            {
              "name": "geometries",
              "value": "geojson"
            }
          ]
        },
        "options": {
          "timeout": 12000
        }
      }
    },
    {
      "id": "f3-render",
      "name": "\ud83d\uddfa\ufe0f staticmap: Render PNG 640\u00d7340 sobre OSM",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        1420
      ],
      "parameters": {
        "jsCode": "// backend/main.py \u2192 _build_route_map_sync() en thread pool executor\n// staticmap + OSM tiles: a.tile.openstreetmap.org/{z}/{x}/{y}.png\n// Dibuja: Line azul (#1565A0 px4) \u00b7 CircleMarker A azul (px16) \u00b7 B rojo (px16)\n// PIL.Image.LANCZOS (Pillow 10+ compat)\nconst route = $json.routes?.[0];\nconst coords = route?.geometry?.coordinates ?? [];\nconst dist_m = Math.round(route?.distance ?? 0);\nconst dur_min = Math.round((route?.duration ?? 0) / 60);\nconst imgBytes = renderOSMMap(640, 340, coords, fromXY, toXY);\nreturn [{ json: { image: 'data:image/png;base64,' + btoa(imgBytes), from_label: $json.from_label,\n  to_label: $json.to_label, top_name: $json.top_name, dist_m, dur_min } }];"
      }
    },
    {
      "id": "f3-respond",
      "name": "\u2705 Mapa PNG Base64 \u2192 jsPDF PDF",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        720,
        1420
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ image: $json.image, from_label: $json.from_label, to_label: $json.to_label, top_name: $json.top_name, dist_m: $json.dist_m, dur_min: $json.dur_min }) }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "f4-webhook",
      "name": "POST /summary",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -880,
        1980
      ],
      "parameters": {
        "path": "summary",
        "httpMethod": "POST",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "f4-getmsgs",
      "name": "\ud83d\udcda Obtener Mensajes del Chat (Firestore)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -560,
        1980
      ],
      "parameters": {
        "jsCode": "// firestore_service.py \u2192 get_chat_messages(uid, chat_id)\n// Prepara texto de conversaci\u00f3n para prompt de extracci\u00f3n\n// Trunca cada mensaje a 1500 chars para no exceder context window\nconst { uid, chat_id } = $json;\nconst messages = await getFirestoreMsgs(uid, chat_id);\nconst conv = messages.map(m => `${m.role === 'user' ? 'Usuario' : 'Asistente'}: ${(m.content ?? '').slice(0, 1500)}`).join('\\n\\n');\nif (!conv) throw new Error('Chat vac\u00edo o no encontrado');\nreturn [{ json: { conversation: conv } }];"
      }
    },
    {
      "id": "f4-mistral",
      "name": "\ud83e\udde0 Mistral AI \u2014 Extracci\u00f3n Estructurada JSON",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -240,
        1980
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.mistral.ai/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.MISTRAL_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'mistral-large-latest', temperature: 0.1, messages: [{ role: 'user', content: 'Analiza esta conversaci\u00f3n de viajes en Peru y responde SOLO con JSON v\u00e1lido con claves: destino, clima{descripcion,temperatura,recomendacion}, costos{transporte,hospedaje,alimentacion,tours}{economico,comodo}, lugares[], consejos[]\\n\\nCONVERSACI\u00d3N:\\n' + $json.conversation }] }) }}",
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "f4-parse",
      "name": "\ud83d\udccb Parsear y Validar JSON Estructurado",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        80,
        1980
      ],
      "parameters": {
        "jsCode": "// agent.py \u2192 generate_summary()\n// Limpia markdown fences: ```json ... ```\n// Extrae objeto JSON con regex {[\\s\\S]*}\n// Valida y completa claves faltantes con 'No disponible'\nconst raw = ($json.choices[0]?.message?.content ?? '').trim();\nconst cleaned = raw.replace(/^```(?:json)?\\s*/m,'').replace(/\\s*```\\s*$/m,'');\nconst match = cleaned.match(/\\{[\\s\\S]*\\}/);\nif (!match) throw new Error('Mistral no devolvi\u00f3 JSON v\u00e1lido');\nconst result = JSON.parse(match[0]);\nconst base = { destino:'No disponible', clima:{descripcion:'',temperatura:'',recomendacion:''},\n  costos:{transporte:{economico:'',comodo:''},hospedaje:{economico:'',comodo:''},\n  alimentacion:{economico:'',comodo:''},tours:{economico:'',comodo:''}}, lugares:[], consejos:[] };\nreturn [{ json: Object.assign(base, result) }];"
      }
    },
    {
      "id": "f4-respond",
      "name": "\u2705 JSON Resumen \u2192 jsPDF Resumen Ejecutivo",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        400,
        1980
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseCode": 200
        }
      }
    }
  ],
  "connections": {
    "POST /chat": {
      "main": [
        [
          {
            "node": "\ud83d\udd10 Verificar JWT Firebase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd10 Verificar JWT Firebase": {
      "main": [
        [
          {
            "node": "\ud83c\udfad \u00bfRol assistant_user o admin?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udfad \u00bfRol assistant_user o admin?": {
      "main": [
        [
          {
            "node": "\ud83d\udcda Obtener Historial Firestore",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\u274c 403 Sin Permisos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcda Obtener Historial Firestore": {
      "main": [
        [
          {
            "node": "\ud83d\udd0d \u00bfRequiere B\u00fasqueda Web?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd0d \u00bfRequiere B\u00fasqueda Web?": {
      "main": [
        [
          {
            "node": "\ud83c\udf10 Tavily: Transporte y Turismo",
            "type": "main",
            "index": 0
          },
          {
            "node": "\ud83c\udf10 Tavily: Clima Meteorol\u00f3gico",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\u23ed\ufe0f Sin B\u00fasqueda (respuesta r\u00e1pida)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udf10 Tavily: Transporte y Turismo": {
      "main": [
        [
          {
            "node": "\ud83d\udd17 Combinar Contexto de B\u00fasqueda",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udf10 Tavily: Clima Meteorol\u00f3gico": {
      "main": [
        [
          {
            "node": "\ud83d\udd17 Combinar Contexto de B\u00fasqueda",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "\u23ed\ufe0f Sin B\u00fasqueda (respuesta r\u00e1pida)": {
      "main": [
        [
          {
            "node": "\ud83e\udde0 Mistral AI \u2014 mistral-large-latest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd17 Combinar Contexto de B\u00fasqueda": {
      "main": [
        [
          {
            "node": "\ud83e\udde0 Mistral AI \u2014 mistral-large-latest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udde0 Mistral AI \u2014 mistral-large-latest": {
      "main": [
        [
          {
            "node": "\ud83d\udcbe Guardar Mensajes en Firestore",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcbe Guardar Mensajes en Firestore": {
      "main": [
        [
          {
            "node": "\u2705 Respuesta JSON \u2192 React Frontend",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET /images/{destination}": {
      "main": [
        [
          {
            "node": "\u26a1 \u00bfCache v\u00e1lido? (TTL 24h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u26a1 \u00bfCache v\u00e1lido? (TTL 24h)": {
      "main": [
        [
          {
            "node": "\u26a1 Servir desde Cach\u00e9",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83d\udd0d Tavily: Ciudad y Turismo (include_images)",
            "type": "main",
            "index": 0
          },
          {
            "node": "\ud83d\udd0d Tavily: Gastronom\u00eda y Hospedaje (include_images)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd0d Tavily: Ciudad y Turismo (include_images)": {
      "main": [
        [
          {
            "node": "\ud83d\udd17 Combinar URLs de Im\u00e1genes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd0d Tavily: Gastronom\u00eda y Hospedaje (include_images)": {
      "main": [
        [
          {
            "node": "\ud83d\udd17 Combinar URLs de Im\u00e1genes",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "\ud83d\udd17 Combinar URLs de Im\u00e1genes": {
      "main": [
        [
          {
            "node": "\u2b07\ufe0f Descargar Im\u00e1genes \u2014 httpx async (max 8)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2b07\ufe0f Descargar Im\u00e1genes \u2014 httpx async (max 8)": {
      "main": [
        [
          {
            "node": "\ud83d\udcbe Almacenar en Cach\u00e9 Memoria Python",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcbe Almacenar en Cach\u00e9 Memoria Python": {
      "main": [
        [
          {
            "node": "\u2705 Base64 Images \u2192 jsPDF y Chat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET /route-map/{destination}": {
      "main": [
        [
          {
            "node": "\ud83d\udccd Nominatim: Plaza de Armas (origen)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udccd Nominatim: Plaza de Armas (origen)": {
      "main": [
        [
          {
            "node": "\ud83d\udccd Nominatim: Atracci\u00f3n Principal (destino)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udccd Nominatim: Atracci\u00f3n Principal (destino)": {
      "main": [
        [
          {
            "node": "\ud83d\udee3\ufe0f OSRM: Ruta Driving con GeoJSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udee3\ufe0f OSRM: Ruta Driving con GeoJSON": {
      "main": [
        [
          {
            "node": "\ud83d\uddfa\ufe0f staticmap: Render PNG 640\u00d7340 sobre OSM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\uddfa\ufe0f staticmap: Render PNG 640\u00d7340 sobre OSM": {
      "main": [
        [
          {
            "node": "\u2705 Mapa PNG Base64 \u2192 jsPDF PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "POST /summary": {
      "main": [
        [
          {
            "node": "\ud83d\udcda Obtener Mensajes del Chat (Firestore)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcda Obtener Mensajes del Chat (Firestore)": {
      "main": [
        [
          {
            "node": "\ud83e\udde0 Mistral AI \u2014 Extracci\u00f3n Estructurada JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udde0 Mistral AI \u2014 Extracci\u00f3n Estructurada JSON": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Parsear y Validar JSON Estructurado",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udccb Parsear y Validar JSON Estructurado": {
      "main": [
        [
          {
            "node": "\u2705 JSON Resumen \u2192 jsPDF Resumen Ejecutivo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "peru-travel-system-v23",
  "tags": [
    {
      "id": "tag-1",
      "name": "Viajes Peru",
      "createdAt": "2026-06-02T00:00:00.000Z",
      "updatedAt": "2026-06-02T00:00:00.000Z"
    }
  ]
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Sistema Inteligente de Viajes de Perú — v2.3. Uses httpRequest. Webhook trigger; 38 nodes.

Source: https://github.com/oszeladap/chatbot-infraestructura/blob/8e258d7e92a5471e130f9b90df9c8e8fd1e4d0fa/n8n-flujo-sistema.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia

HTTP Request
Web Scraping

This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c

HTTP Request
Web Scraping

Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.

Execute Command, HTTP Request, Read Write File +1