AutomationFlowsAI & RAG › Chanchito Prod

Chanchito Prod

Chanchito_PROD. Uses googleGemini, postgres, telegram, httpRequest. Scheduled trigger; 94 nodes.

Cron / scheduled trigger★★★★★ complexityAI-powered94 nodesGoogle GeminiPostgresTelegramHTTP RequestTelegram TriggerLm Chat Google VertexChain Llm
AI & RAG Trigger: Cron / scheduled Nodes: 94 Complexity: ★★★★★ AI nodes: yes Added:
Chanchito Prod — n8n workflow card showing Google Gemini, Postgres, Telegram integration

This workflow follows the Chainllm → HTTP Request recipe pattern — see all workflows that pair these two integrations.

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": "Chanchito_PROD",
  "nodes": [
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "models/gemini-2.5-flash",
          "mode": "list",
          "cachedResultName": "models/gemini-2.5-flash"
        },
        "messages": {
          "values": [
            {
              "content": "=Act\u00faa como un asistente financiero experto en el contexto econ\u00f3mico argentino.\nTu objetivo es extraer datos estructurados de un mensaje natural y categorizarlos con precisi\u00f3n usando los IDs provistos.\n\nINPUTS:\n1. Mensaje del Usuario: \n\"\"\"\n{{ $('Parseo de mensaje para Gemini').first().json.text }}\n\"\"\"\n\n2. Lista de Categor\u00edas (Referencia):\n{{ $('Formatear Categor\u00edas').first().json.categories_prompt }}\n\n3. DICCIONARIO DE IDs (Mapa Nombre -> UUID):\n{{ JSON.stringify($('Formatear Categor\u00edas').first().json.categories_map) }}\n\nINSTRUCCIONES:\nAnaliza el mensaje y devuelve EXCLUSIVAMENTE un objeto JSON.\nDetecta la INTENCI\u00d3N y elige la estructura correcta.\nIMPORTANTE: Cuando elijas una categor\u00eda, busca su nombre exacto en el \"DICCIONARIO DE IDs\" y extrae el UUID correspondiente para el campo \"category_id\".\n\n--- CASO A: ES UNA TRANSACCI\u00d3N (Gasto, Compra, Cuotas, Ingreso) ---\nSi el usuario informa un movimiento de dinero.\nDevuelve esta estructura:\n{\n  \"intencion\": \"transaccion\",\n  \"compra\": \"Breve descripci\u00f3n del \u00edtem (ej: Zapatillas Nike)\",\n  \"categoria\": \"El nombre exacto de la categor\u00eda elegida (ej: 'Comida')\",\n\"category_id\": \"El UUID exacto sacado del DICCIONARIO DE IDs correspondiente a la categor\u00eda elegida.\",\n  \"valor\": 0, (N\u00famero positivo puro. Si es gasto 12000, pon 12000. Si es ingreso, tambi\u00e9n positivo).\n  \"tipo\": \"Uno de: 'expense' (gasto) o 'income' (ingreso/sueldo/cobro)\",\n  \"medio_pago\": \"Nombre del medio si se menciona (ej: 'Visa', 'Master', 'Mercado Pago', 'Efectivo'). Si no dice nada, devuelve null.\",\n  \"es_gasto_real\": true, (Poner false si es publicidad, spam, aviso de seguridad, 'novedades', o notificaciones que NO implican movimiento de dinero),\n  \"cuotas\": {\n    \"es_cuota\": boolean, (true si el usuario menciona expl\u00edcitamente cuotas, pagos o plan de pagos),\n    \"cantidad\": number, (1 si es pago \u00fanico. Si son cuotas, la cantidad, ej: 6),\n    \"monto_total\": number (El precio TOTAL de la compra. IMPORTANTE: Si el usuario dice '6 cuotas de 10.000', el total es 60000. Si dice 'TV 100.000 en 6 pagos', el total es 100000)\n  },\n  \"fecha\": \"YYYY-MM-DD\" (Calculada en relaci\u00f3n a hoy: {{ $now }}. Por ejemplo, si dice 'hoy' es {{ $now }}, si es ayer es el dia previo a {{ $now }}. Si no dice nada, se asume que es {{ $now }})\n}\n\n--- CASO B: CONFIGURACI\u00d3N DE TARJETA (El usuario informa fechas) ---\nSi el usuario dice algo como \"La Visa cierra el 24/12 y vence el 05/01\" o \"Master cierra el 20\".\nDevuelve esta estructura:\n{\n  \"intencion\": \"configuracion_tarjeta\",\n  \"tarjeta_match\": \"Parte del nombre de la tarjeta para buscarla (ej: 'Visa')\",\n  \"fecha_cierre\": \"YYYY-MM-DD\" (Si solo dice el d\u00eda '24', asume el cierre pr\u00f3ximo l\u00f3gico seg\u00fan la fecha de hoy: {{ $now }}),\n  \"fecha_vencimiento\": \"YYYY-MM-DD\" (Calcula la fecha l\u00f3gica de vencimiento posterior al cierre)\n}\n\n--- CASO C: SUSCRIPCI\u00d3N O GASTO FIJO (Recurring Plan) ---\nSi el usuario menciona un gasto que se repite (ej: \"Suscripci\u00f3n Netflix\", \"Pago el gimnasio todos los meses\", \"D\u00e9bito autom\u00e1tico de seguro\", \"Alquiler\").\nDevuelve esta estructura:\n{\n  \"intencion\": \"suscripcion\",\n  \"descripcion\": \"Nombre del servicio (ej: Spotify)\",\n  \"valor\": 0, (Monto mensual),\n  \"moneda\": \"ARS\" (o USD si especifica),\n  \"categoria\": \"Nombre de la categor\u00eda elegida\",\n  \"category_id\": \"El UUID exacto sacado del DICCIONARIO DE IDs\",\n  \"frecuencia\": \"monthly\", (Por defecto 'monthly', salvo que diga 'anual' o 'semanal'),\n  \"medio_pago\": \"Nombre del medio de pago si se menciona (ej: 'Visa'). Si no, null.\"\n}\n\nREGLAS CR\u00cdTICAS DE PROCESAMIENTO:\n1. Si detectas palabras como \"Cobr\u00e9\", \"Sueldo\", \"Me transfirieron\", \"Ingreso\", define \"tipo\": \"income\" y \"categoria\": \"Ingresos\".\n2. Si \"es_gasto_real\" es false, el resto de campos pueden ser null.\n3. Prioriza tu lista de categor\u00edas personalizada. Si no encaja, usa \"Otros\".\n4. Si el usuario dice palabras como 'mensual', 'suscripci\u00f3n', 'd\u00e9bito autom\u00e1tico', 'plan', prioriza la intenci\u00f3n 'suscripcion' sobre 'transaccion'.\n5. El campo \"category_id\" ES OBLIGATORIO para transacciones y suscripciones. Nunca lo dejes null si encontraste una categor\u00eda."
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "typeVersion": 1,
      "position": [
        -4368,
        1712
      ],
      "id": "1a06738d-3b5e-4fe2-9252-de38ee7651fc",
      "name": "Procesar mensaje y categorizar",
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "ac739398-3003-494d-aa2f-9cdf926f5a53",
              "leftValue": "={{ $json.valor }}",
              "rightValue": "null",
              "operator": {
                "type": "number",
                "operation": "exists",
                "singleValue": true
              }
            },
            {
              "id": "0383615c-cc1d-463a-9d63-bad0e70f321c",
              "leftValue": "={{ $json.compra }}",
              "rightValue": "null",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              }
            },
            {
              "id": "5a7d4df5-a7d4-4d6d-a322-802121dbfc34",
              "leftValue": "={{ $json.categoria }}",
              "rightValue": "null",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              }
            },
            {
              "id": "8720a94f-e18e-4ea4-862b-799adca3459c",
              "leftValue": "={{ $json.fecha }}",
              "rightValue": "null",
              "operator": {
                "type": "dateTime",
                "operation": "exists",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -3664,
        1520
      ],
      "id": "4d4648cf-ebae-47ab-b1c8-165601f7fa8d",
      "name": "Chequeo de campos v\u00e1lidos"
    },
    {
      "parameters": {
        "jsCode": "// 1. Obtener texto de Gemini\nconst rawText = $input.first().json.text\nconst cleaned = rawText.replace(/```json|```/g, '').trim();\n\nlet result;\n\n// 2. Intentar parsear el JSON\ntry {\n  result = JSON.parse(cleaned);\n} catch (error) {\n  result = {\n    compra: null,\n    categoria: null,\n    valor: null,\n    fecha: null,\n    es_gasto_real: false // Asumimos que no es gasto si fall\u00f3 catastr\u00f3ficamente\n  };\n}\n\n// 3. RECUPERAR EL ORIGEN (Source)\n// Esto es vital: buscamos en el nodo unificador si vino de 'android' o 'telegram'\ntry {\n  result = JSON.parse(cleaned);\n  \n  // MANTENER INTENCI\u00d3N Y MEDIO DE PAGO\n  // Si Gemini no devolvi\u00f3 medio_pago, lo forzamos a null para evitar undefined\n  if (!result.medio_pago) result.medio_pago = null;\n  if (!result.intencion) result.intencion = 'transaccion'; // Default por seguridad\n\n} catch (error) {\n    // Si falla, por defecto asumimos telegram para que te avise el error\n    result.source = 'telegram';\n}\n\nreturn [{\n  json: result\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -4032,
        1712
      ],
      "id": "b37e8bb9-f115-48de-9c26-48ab2c2c1f0d",
      "name": "Parseo post Gemini"
    },
    {
      "parameters": {
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "__rl": true,
          "value": "installment_plans",
          "mode": "list",
          "cachedResultName": "installment_plans"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "total_amount": "={{ $('Seteo de campos').first\n().json['Monto total'] }}",
            "installments_count": "={{ $('Seteo de campos').first\n().json['Cantidad de cuotas'] }}",
            "user_id": "={{ $('Identificar usuario').first\n().json.id }}",
            "description": "={{ $json.Compra }}",
            "purchase_date": "={{ $json.FechaRealPago }}",
            "payment_method_id": "={{ $('Calculadora de vencimiento').first\n().json.payment_method_id }}",
            "category_id": "={{ $('Seteo de campos').item.json['Id Categoria'] }}"
          },
          "matchingColumns": [
            "id"
          ],
          "schema": [
            {
              "id": "id",
              "displayName": "id",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "user_id",
              "displayName": "user_id",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "displayName": "description",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "total_amount",
              "displayName": "total_amount",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "installments_count",
              "displayName": "installments_count",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "purchase_date",
              "displayName": "purchase_date",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "payment_method_id",
              "displayName": "payment_method_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "category_id",
              "displayName": "category_id",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -1968,
        1168
      ],
      "id": "e5faf890-28d2-4198-9663-d1f1b33d2396",
      "name": "Crear Plan (Cuotas)",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const plan = $input.first().json; // Datos del plan reci\u00e9n creado (necesitamos el ID)\nconst gemini = $('Seteo de campos').first().json; // Datos originales\nconst userId = $('Identificar usuario').first().json.id;\nconst cantidad = gemini['Cantidad de cuotas']\nconst montoCuota = gemini['Monto total'] / cantidad;\nconst fechaInicioPagos = new Date($('Calculadora de vencimiento').first().json.FechaRealPago)\n\nconst transacciones = [];\n\nfor (let i = 0; i < cantidad; i++) {\n  let fechaPago = new Date(fechaInicioPagos);\n  fechaPago.setMonth(fechaPago.getMonth() + i); // Sumamos meses desde el primer pago\n  \n  transacciones.push({\n    user_id: userId,\n    description: `${gemini['Compra']} (Cuota ${i + 1}/${cantidad})`,\n    category: gemini.categoria,\n    amount: Math.abs(montoCuota), // Siempre negativo\n    date: fechaPago.toISOString(), // Formato ISO para Postgres\n    type: 'expense',\n    payment_method: 'Credit Card', // Asumimos tarjeta si es cuota\n    installment_plan_id: plan.id // Vinculamos al padre\n  });\n}\n\nreturn transacciones;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1776,
        1168
      ],
      "id": "f95ebf87-39d5-42c3-bd75-bb6bf07bde3a",
      "name": "Calculadora de fechas"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "fb7f0829-d48c-4299-89be-b41e1309cd40",
              "leftValue": "={{ $('Chequeo de campos v\u00e1lidos').item.json.tipo }}",
              "rightValue": "=expense",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "1b63324a-7282-49a1-b86e-ff01a9755fe4",
              "leftValue": "={{ $('Chequeo de campos v\u00e1lidos').item.json.cuotas.es_cuota }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -2224,
        1344
      ],
      "id": "4e596150-4288-4f96-8474-0f535d7aa94e",
      "name": "Es gasto en cuotas?"
    },
    {
      "parameters": {
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "__rl": true,
          "value": "transactions",
          "mode": "list",
          "cachedResultName": "transactions"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "user_id": "={{ $('Identificar usuario').first().json.id }}",
            "amount": "={{ $('Seteo de campos').item.json.Valor }}",
            "description": "={{ $('Seteo de campos').item.json.Compra }}",
            "date": "={{ $('Calculadora de vencimiento').first().json.FechaRealPago }}",
            "type": "={{ $('Parseo post Gemini').first().json.tipo }}",
            "payment_method_id": "={{ $('Seteo de campos').first().json['Id Medio de pago'] }}",
            "category_id": "={{ $('Seteo de campos').item.json['Id Categoria'] }}"
          },
          "matchingColumns": [
            "id"
          ],
          "schema": [
            {
              "id": "id",
              "displayName": "id",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "user_id",
              "displayName": "user_id",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "displayName": "description",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "amount",
              "displayName": "amount",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "date",
              "displayName": "date",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true
            },
            {
              "id": "type",
              "displayName": "type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "installment_plan_id",
              "displayName": "installment_plan_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "recurring_plan_id",
              "displayName": "recurring_plan_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "payment_method_id",
              "displayName": "payment_method_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "category_id",
              "displayName": "category_id",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -1904,
        1568
      ],
      "id": "a18ba05c-9d24-4bc7-ad3d-2fe1dd842310",
      "name": "Guardar Transaccion",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Obtener el input inmediato (para ver si es Webhook)\nconst item = $input.first();\n\n// --- CASO 1: Viene del WEBHOOK (Android/MacroDroid) ---\n// Verificamos si la entrada inmediata tiene \"body\" (t\u00edpico de Webhook)\nif (item.json.body) {\n  return {\n    text: item.json.body.mensaje, // Asegurate que en MacroDroid mandaste la variable \"mensaje\"\n    source: 'android',\n    chat_id: 1995566176 // Tu ID fijo que pusiste\n  };\n}\n\n// --- CASO 2: Viene de TELEGRAM ---\n// Si no es webhook, asumimos que es Telegram.\n// PERO, como venimos de un nodo de \"Respuesta\", tenemos que ignorar el input inmediato\n// y buscar los datos \"aguas arriba\" en el nodo Trigger original.\n\ntry {\n  // Accedemos directamente a los datos del nodo \"Trigger mensaje\"\n  // Usamos try/catch por si acaso el nodo no existe o no corri\u00f3\n  \n  \n  if ($('Telegram Trigger').first().json.message) {\n    return {\n      text: $('Telegram Trigger').first().json.message.text,\n      source: 'telegram',\n      chat_id: ($('Telegram Trigger').first().json.message.from.id).toString()\n    };\n  }\n} catch (error) {\n  // Si da error aqu\u00ed, es porque el nodo Trigger no se ejecut\u00f3 \n  // (probablemente estamos en una prueba aislada o algo raro)\n  console.log(\"No se encontraron datos del Trigger de Telegram\");\n}\n\n// Si nada funcion\u00f3:\nreturn { text: '', error: 'No se detect\u00f3 origen v\u00e1lido' };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -6112,
        1456
      ],
      "id": "8f799e63-2a2d-40e2-9ba7-d12b9ca72ebd",
      "name": "Parseo de mensaje para Gemini"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.intencion }}",
                    "rightValue": "transaccion",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "f5414ca4-f772-49b9-8c14-7ea21628c76c"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Es transacci\u00f3n"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "32be032e-b2f5-4c00-b7da-1b4d89770966",
                    "leftValue": "={{ $json.intencion }}",
                    "rightValue": "configuracion_tarjeta",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Es configuraci\u00f3n de tarjeta"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "a41b3cde-3664-428b-b6d1-e77202076a1c",
                    "leftValue": "={{ $json.intencion }}",
                    "rightValue": "suscripcion",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Es suscripci\u00f3n"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        -3856,
        1696
      ],
      "id": "7d9ff517-6206-4e2c-a7f1-213181a2760b",
      "name": "Es transacci\u00f3n o configuraci\u00f3n de tarjeta"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT *\nFROM public.payment_methods\nWHERE user_id = {{ $('Identificar usuario').first\n().json.id }}\nORDER BY SIMILARITY(name, '{{ $('Parseo post Gemini').item.json.tarjeta_match || $('Parseo post Gemini').item.json.medio_pago }}') DESC -- Ordenar por el que m\u00e1s se parezca\nLIMIT 1;",
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -3424,
        1840
      ],
      "id": "8e7e8cb1-9135-496a-bc7e-07762c0b9be7",
      "name": "Identificar medio de pago",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "__rl": true,
          "value": "payment_methods",
          "mode": "list",
          "cachedResultName": "payment_methods"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "default_closing_day": "={{ new Date($('Parseo post Gemini').first\n().json.fecha_cierre).getUTCDate() }}",
            "default_payment_day": "={{ new Date($('Parseo post Gemini').first\n().json.fecha_vencimiento).getUTCDate() }}",
            "id": "={{ $json.id }}",
            "user_id": "={{ $json.user_id }}",
            "name": "={{ $json.name }}",
            "type": "={{ $json.type }}",
            "created_at": "={{ $json.created_at }}"
          },
          "matchingColumns": [
            "id"
          ],
          "schema": [
            {
              "id": "id",
              "displayName": "id",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "user_id",
              "displayName": "user_id",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false,
              "removed": false
            },
            {
              "id": "name",
              "displayName": "name",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false,
              "removed": false
            },
            {
              "id": "type",
              "displayName": "type",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false,
              "removed": false
            },
            {
              "id": "default_closing_day",
              "displayName": "default_closing_day",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "default_payment_day",
              "displayName": "default_payment_day",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": false
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": false,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -3248,
        1840
      ],
      "id": "2e63b169-0a32-40ce-af9b-cb15d112d532",
      "name": "Actualizar medio de pago",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Parseo de mensaje para Gemini').first\n().json.chat_id }}",
        "text": "=Error al procesar el mensaje. Asegurate de que todos los campos esten completos (nombre de gasto, precio y fecha). Por ejemplo: \"Gast\u00e9 $10000 pesos en ropa\".\nSi este gasto es de una notificaci\u00f3n, escrib\u00ed el gasto manual a continuaci\u00f3n.",
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -3424,
        1648
      ],
      "id": "8efb59a9-18fd-4220-b609-6cdc30c21888",
      "name": "Enviar mensaje de error",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "__rl": true,
          "value": "transactions",
          "mode": "list",
          "cachedResultName": "transactions"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "user_id": "={{ $json.user_id }}",
            "description": "={{ $json.description }}",
            "amount": "={{ $json.amount }}",
            "date": "={{ $json.date }}",
            "type": "={{ $json.type }}",
            "installment_plan_id": "={{ $json.installment_plan_id }}",
            "payment_method_id": "={{ $('Calculadora de vencimiento').first\n().json.payment_method_id }}",
            "category_id": "={{ $('Seteo de campos').item.json['Id Categoria'] }}"
          },
          "matchingColumns": [
            "id"
          ],
          "schema": [
            {
              "id": "id",
              "displayName": "id",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "user_id",
              "displayName": "user_id",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "displayName": "description",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "amount",
              "displayName": "amount",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true
            },
            {
              "id": "date",
              "displayName": "date",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true
            },
            {
              "id": "type",
              "displayName": "type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "installment_plan_id",
              "displayName": "installment_plan_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "recurring_plan_id",
              "displayName": "recurring_plan_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "payment_method_id",
              "displayName": "payment_method_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "category_id",
              "displayName": "category_id",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -1600,
        1168
      ],
      "id": "9c033ff7-4293-4716-baf4-a45789da8b95",
      "name": "Guardar cuotas",
      "alwaysOutputData": true,
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "\n// 2. INICIALIZAR FECHAS\nconst fechaCompra = new Date($input.first().json.Fecha);\nlet fechaPagoReal = new Date(fechaCompra);\n\n// 3. L\u00d3GICA DE CR\u00c9DITO (Cash Flow Real)\nif ($input.first().json['Tipo de Medio de pago'] === 'credit') {\n  const diaCompra = fechaCompra.getDate();\n  \n  // Paso A: Determinar si salta al pr\u00f3ximo resumen\n  if (diaCompra > $input.first().json['D\u00eda de cierre']) {\n    fechaPagoReal.setMonth(fechaPagoReal.getMonth() + 1);\n  }\n  \n  // Paso B: Determinar si el pago cae al mes siguiente del cierre\n  // (Si vence el 5 y cierra el 25, paga al mes siguiente)\n  if ($input.first().json['D\u00eda de vencimiento'] < $input.first().json['D\u00eda de cierre']) {\n    fechaPagoReal.setMonth(fechaPagoReal.getMonth() + 1);\n  }\n  \n  // Paso C: Fijar el d\u00eda exacto de PAGO\n  fechaPagoReal.setDate($input.first().json['D\u00eda de vencimiento']);\n}\n\n// 4. RETORNAR\nreturn {\n  Compra: $input.first().json.Compra,\n  Categor\u00eda: $input.first().json['Categor\u00eda'],\n  Valor:$input.first().json.Valor,\n  payment_method_id: $input.first().json['Id Medio de pago'],\n  FechaRealPago: fechaPagoReal.toISOString().split('T')[0]\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2464,
        1344
      ],
      "id": "fda00648-da19-4d18-a634-9bca4294624c",
      "name": "Calculadora de vencimiento"
    },
    {
      "parameters": {
        "chatId": "={{ $('Parseo de mensaje para Gemini').first\n().json.chat_id }}",
        "text": "=\u2705 Gasto en cuotas guardado:  \n- {{ $('Crear Plan (Cuotas)').item.json.description }}\n- {{ $('Seteo de campos').item.json['Categor\u00eda'] }}\n- \ud83d\udcb5${{ $('Chequeo de campos v\u00e1lidos').item.json.cuotas.es_cuota ? $('Seteo de campos').item.json.Valor + \" en \" + $('Seteo de campos').item.json['Cantidad de cuotas'] + \" cuotas\" : $('Seteo de campos').item.json.Valor }}\n- \ud83d\udcb3{{ $('Seteo de campos').item.json['Medio de pago'] }}\n- \ud83d\udd50{{ $('Seteo de campos').item.json.Fecha }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -1424,
        1168
      ],
      "id": "5967a4fd-0ccc-455e-8e1e-5fc1cfc622d9",
      "name": "Enviar mensaje de confirmaci\u00f3n de cuotas",
      "executeOnce": true,
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "7fcef0ec-9a8f-4e0f-824a-15f680683aa9",
              "leftValue": "={{ $json.type }}",
              "rightValue": "income",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -1712,
        1568
      ],
      "id": "daea3212-9049-486a-993d-2abb5e61a5b9",
      "name": "Es ingreso?"
    },
    {
      "parameters": {
        "chatId": "={{ $('Parseo de mensaje para Gemini').first\n().json.chat_id }}",
        "text": "=\u2705 Gasto guardado:  \n- {{ $json.description }}\n- {{ $('Seteo de campos').item.json['Categor\u00eda'] }}\n- \ud83d\udcb5${{ $('Chequeo de campos v\u00e1lidos').item.json.cuotas.es_cuota ? $('Seteo de campos').item.json['Monto total'] + \" en \" + $('Seteo de campos').item.json['Cantidad de cuotas'] + \" cuotas\" : $('Seteo de campos').item.json.Valor}}\n- \ud83d\udcb3{{ $('Seteo de campos').item.json['Medio de pago'] }}\n- \ud83d\udd50 {{ $('Calculadora de vencimiento').item.json.FechaRealPago }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -1488,
        1680
      ],
      "id": "23c93460-7483-4301-9f74-780caeb594b9",
      "name": "Enviar mensaje de confirmaci\u00f3n de gasto",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Parseo de mensaje para Gemini').first\n().json.chat_id }}",
        "text": "=\u2705 Ingreso guardado:  \n- {{ $json.description }}\n- {{ $('Seteo de campos').item.json['Categor\u00eda'] }}\n- \ud83d\udcb5${{ $json.amount }}\n- \ud83d\udcb3{{ $('Seteo de campos').item.json['Medio de pago'] }}\n- \ud83d\udd50{{ $('Seteo de campos').item.json.Fecha }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -1488,
        1504
      ],
      "id": "bf5bf8cf-bbfd-4abf-87c9-04358d4f64bf",
      "name": "Enviar mensaje de confirmaci\u00f3n de ingreso",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT \n  pm.name as nombre_tarjeta,\n  u.telegram_chat_id,\n  u.first_name\nFROM public.payment_methods pm\nJOIN public.users u ON pm.user_id = u.id\nWHERE \n  pm.type = 'credit'\n  -- Calculamos si el d\u00eda de cierre fue \"Ayer\" respetando la zona horaria argentina\n  AND pm.default_closing_day = EXTRACT(DAY FROM (NOW() AT TIME ZONE 'America/Argentina/Buenos_Aires' - INTERVAL '1 day'));",
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -7280,
        576
      ],
      "id": "7eaf5523-72ef-4907-967e-2322f51034d0",
      "name": "Chequeo de cierres diario",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 10
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -7488,
        576
      ],
      "id": "6c4f7b43-8e42-4dc1-9a9f-028206399a52",
      "name": "Trigger de chequeo de cierres diario"
    },
    {
      "parameters": {
        "operation": "sendAndWait",
        "chatId": "={{ $json.telegram_chat_id }}",
        "message": "=\u26a0\ufe0f *Actualizaci\u00f3n de Ciclo*\n\nHola {{ $json.first_name }}, ayer cerr\u00f3 tu tarjeta: *{{ $json.nombre_tarjeta }}*.\n\nPor favor, revisa el resumen y decime las nuevas fechas para calibrar el sistema.\n\nRespondeme con este formato exacto:\n\"{{ $json.nombre_tarjeta }} cierra el DD/MM y vence el DD/MM\"",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -7088,
        576
      ],
      "id": "ca9bdeeb-4259-406d-820b-0a01520e682c",
      "name": "Send message and wait for response",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "select",
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "__rl": true,
          "value": "users",
          "mode": "list",
          "cachedResultName": "users"
        },
        "limit": 1,
        "where": {
          "values": [
            {
              "column": "telegram_chat_id",
              "value": "={{ $json.chat_id }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -5904,
        1456
      ],
      "id": "562b225d-a03c-411c-9847-07b1155162a7",
      "name": "Identificar usuario",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE public.transactions\nSET date = make_date(\n    CAST(EXTRACT(YEAR FROM date) AS INTEGER),\n    CAST(EXTRACT(MONTH FROM date) AS INTEGER),\n    {{ new Date($('Parseo post Gemini').first\n().json.fecha_vencimiento).getUTCDate() }}\n)\nWHERE \n  payment_method_id = {{ $('Identificar medio de pago').first\n().json.id }}\n  AND type = 'expense'\n  -- L\u00d3GICA DE PROTECCI\u00d3N:\n  -- Solo actualiza si la fecha es mayor a HOY + 20 D\u00cdAS.\n  -- Esto salta cualquier vencimiento inminente (que ya tiene resumen cerrado)\n  -- y va directo a modificar Enero, Febrero, etc.\n  AND date > (CURRENT_DATE + interval '20 days');",
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -3040,
        1840
      ],
      "id": "e2413bcf-f9fb-4b80-b650-b60224d58a79",
      "name": "Actualizar futuros",
      "alwaysOutputData": true,
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Parseo de mensaje para Gemini').first\n().json.chat_id }}",
        "text": "=\u2705 Medio de pago modificado:\n- \ud83d\udcb3 {{ $('Identificar medio de pago').item.json.name }}\n- \ud83d\udcc5 Nuevo cierre: {{ $('Parseo post Gemini').item.json.fecha_cierre }}\n- \ud83d\udcc5 Nuevo vencimiento: {{ $('Parseo post Gemini').item.json.fecha_vencimiento }}",
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -2848,
        1840
      ],
      "id": "7881144e-d864-456d-bce7-dbb0dcf15ee2",
      "name": "Enviar mensaje de confirmaci\u00f3n de configuraci\u00f3n",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -5712,
        -144
      ],
      "id": "e48a3963-a876-4209-9916-359de7aa58bf",
      "name": "Schedule Trigger"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "-- Traemos solo los tickers \u00fanicos para no buscar AAPL 2 veces si compraste en dos fechas\nSELECT DISTINCT ticker, type, data_source_url\nFROM public.investments\nWHERE type IN ('stock', 'cedear', 'bond', 'on', 'crypto');",
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -5408,
        -144
      ],
      "id": "9de1d74c-0c47-440c-af26-2d100ce95b7f",
      "name": "Leer Cartera",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        -5200,
        -144
      ],
      "id": "7665bffe-156d-4764-a398-c70bec78d884",
      "name": "Split Batches"
    },
    {
      "parameters": {
        "url": "=https://query1.finance.yahoo.com/v8/finance/chart/{{ $json.ticker }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -4784,
        -208
      ],
      "id": "6daa26c9-531d-4329-90a8-e33813e4bb28",
      "name": "Obtener valores",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "=https://api.coingecko.com/api/v3/simple/price?ids={{ $json.ticker }}&vs_currencies=usd",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -4784,
        -64
      ],
      "id": "1705eb74-fcbd-492a-ba2c-466259515527",
      "name": "Obtener valores cripto",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\n\n// Navegamos la estructura loca de Yahoo\nconst meta = data.chart.result[0].meta;\nconst precio = meta.regularMarketPrice; // Precio actual de mercado\nconst moneda = meta.currency; // ARS o USD\n\nreturn {\n  ticker: meta.symbol,\n  last_price: precio,\n  currency: moneda\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -4576,
        -208
      ],
      "id": "142325ac-1f4d-404a-9c70-eb6ed3019af3",
      "name": "Limpiar valores"
    },
    {
      "parameters": {
        "jsCode": "const idCripto = $('Split Batches').first().json.ticker // \"bitcoin\"\nconst data = $input.first().json;\nconst precio = data[idCripto].usd;\n\nreturn {\n  ticker: idCripto,\n  last_price: precio,\n  currency: 'ARS'\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -4576,
        -64
      ],
      "id": "4d2caed3-be6f-4deb-9747-ce2699438f98",
      "name": "Limpiar valores cripto"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO public.market_prices (ticker, last_price, last_update)\nVALUES ('{{ $json.ticker }}', {{ $json.last_price }}, NOW()) -- Comillas aqu\u00ed\nON CONFLICT (ticker) \nDO UPDATE SET \n  last_price = EXCLUDED.last_price, \n  last_update = NOW();",
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -4192,
        -64
      ],
      "id": "442268bc-9442-4a6d-ad23-4fafee02be05",
      "name": "Execute a SQL query",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.type }}",
                    "rightValue": "={{ \"stock\" }}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "b9f548ff-3e20-403d-a0ea-16e39e8f8328"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "cedear OR stock OR bond OR on"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "891a3190-7a55-4797-9ca8-0cfbde0dd472",
                    "leftValue": "={{ $json.type }}",
                    "rightValue": "crypto",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "crypto"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "2448734c-8233-4dba-aff4-e8c64961a43b",
                    "leftValue": "={{ $json.type }}",
                    "rightValue": "={{ \"on\" || \"bond\" || \"cedear\" }}",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "on"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        -4992,
        -80
      ],
      "id": "6a9cf6ba-be92-4849-93e1-b1808ba2642c",
      "name": "El Clasificador"
    },
    {
      "parameters": {
        "url": "={{ $json.data_source_url }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -4784,
        160
      ],
      "id": "ccba4f2a-c9d3-4583-97c1-dbe27149b716",
      "name": "HTTP Request"
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "precio_texto",
              "cssSelector": "span[data-field=\"UltimoPrecio\"]"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [
        -4624,
        160
      ],
      "id": "a996a3ef-61d3-410b-a56d-bd75d0d4fc80",
      "name": "HTML"
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.precio_texto; // Ej: \"1.050,50\"\n\nif (!raw) {\n  throw new Error(\"No se pudo extraer el precio. Revisa el selector CSS.\");\n}\n\n// 1. Limpieza de texto (String)\nconst textoLimpio = raw\n  .replace(/[^0-9,]/g, '') // Borra todo lo que NO sea n\u00famero o coma (borra el punto de mil y el $)\n  .replace(',', '.');      // Cambia la coma decimal por punto (formato JS)\n\n// 2. Conversi\u00f3n a N\u00famero (Float)\n// Usamos 'let' porque vamos a modificar esta variable si entra en la regla\nlet precioFinal = parseFloat(textoLimpio);\n\n// 3. Regla Heur\u00edstica (ONs/Bonos)\n// Recuperamos el tipo del nodo anterior\nconst tipoActivo = $('El Clasificador').first().json.type;\n\nif (tipoActivo === 'on' || tipoActivo === 'bond') {\n  // Si vale m\u00e1s de 20 (ej: 109.90), asumimos que es precio \"cada 100\"\n  if (precioFinal > 20) {\n    precioFinal = precioFinal / 100;\n  }\n}\n\nreturn {\n  ticker: $('El Clasificador').first().json.ticker, // Mantenemos el ticker original\n  last_price: precioFinal\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -4464,
        160
      ],
      "id": "657f59fe-8b69-4ae8-a9d6-640a6db76ca2",
      "name": "Limpiar Scrap"
    },
    {
      "parameters": {
        "content": "## Actualizaci\u00f3n mensual de tarjetas",
        "height": 240,
        "width": 720,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -7584,
        512
      ],
      "id": "8ea1f22e-1f31-4117-9885-1cbcc5fbecea",
      "name": "Sticky Note5"
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.2,
      "position": [
        -7536,
        1280
      ],
      "id": "8007133a-74c1-483e-9e08-b69afac78989",
      "name": "Telegram Trigger",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const text = $('Telegram Trigger').first().json.message.text || \"\";\n\n// Separamos por espacios y limpiamos bordes\nconst parts = text.trim().split(/\\s+/);\n\n// VALIDACI\u00d3N ESTRICTA\n// 1. parts[0] debe ser \"/start\"\n// 2. parts[1] debe existir y tener longitud de UUID (36 chars)\n//    (Ej: 06e2ba95-3025-4aed-be17-410a88cc1eb4)\n\nif (parts[0] === '/start' && parts.length >= 2 && parts[1].length === 36) {\n  return {\n    isValid: true,\n    token: parts[1], // El UUID limpio\n    chatId: $('Telegram Trigger').first().json.message.chat.id,\n    firstName: $('Telegram Trigger').first().json.message.chat.first_name\n  };\n} else {\n  // Si dice \"gaste 4000 en un sanguche\", cae ac\u00e1\n  return { isValid: false }; \n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -7200,
        1280
      ],
      "id": "030e55bc-a407-45b9-be98-b1dbeeab8f29",
      "name": "Extraer el Token"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "f1217c3a-fa99-43bd-95cb-bea19049941d",
              "leftValue": "={{ $json.token }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -7008,
        1280
      ],
      "id": "2309b887-6e95-4282-b6c2-3abb12d8f196",
      "name": "Filtrar nuevos mensajes"
    },
    {
      "parameters": {
        "operation": "update",
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "__rl": true,
          "value": "users",
          "mode": "list",
          "cachedResultName": "users"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "telegram_chat_id": "={{ $('Extraer el Token').first\n().json.chatId }}",
            "id": "={{ $('Extraer el Token').first\n().json.token }}"
          },
          "matchingColumns": [
            "id"
          ],
          "schema": [
            {
              "id": "id",
              "displayName": "id",
              "required": true,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "email",
              "displayName": "email",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "first_name",
              "displayName": "first_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "last_name",
              "displayName": "last_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "avatar_url",
              "displayName": "avatar_url",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "telegram_chat_id",
              "displayName": "telegram_chat_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "created_at",
              "displayName": "created_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "canBeUsedToMatch": true,
              "removed": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -6800,
        896
      ],
      "id": "bd225d46-6485-4495-ad92-f80f1be4aeac",
      "name": "Actualizar ID Telegram del usuario",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.telegram_chat_id }}",
        "text": "=\u00a1Cuenta vinculada! \ud83d\udc37 Decime crack, \u00bfc\u00f3mo quer\u00e9s que te llame? \ud83d\udc47",
        "replyMarkup": "forceReply",
        "forceReply": {
          "force_reply": true
        },
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -6640,
        896
      ],
      "id": "f01496bd-364d-4c03-9d0c-7925a622500a",
      "name": "Confirmaci\u00f3n de vinculaci\u00f3n de telegram",
      "cr

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

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

How this works

This workflow processes incoming financial messages through Google Gemini to categorise transactions, then records the details and any payment plans into Postgres while sending updates via Telegram. It suits users who need reliable expense tracking and quota calculations without manual data entry, automatically handling date maths and validation checks along the way. The core step is the Gemini categorisation that feeds clean data into the database and messaging steps.

Use it for daily personal or small-team bookkeeping where messages arrive via Telegram and need structured storage. Skip it if you require real-time fraud detection or multi-currency handling, as those features are absent. Common tweaks include swapping the cron schedule or adding extra validation rules before the Postgres insert.

About this workflow

Chanchito_PROD. Uses googleGemini, postgres, telegram, httpRequest. Scheduled trigger; 94 nodes.

Source: https://github.com/papuzinH/finanzas-lh/blob/b207383452c2e81c8a1e430b02b0841ae650110c/n8n/Chanchito_PROD.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

System Architecture Two integrated N8N workflows providing automated US stock portfolio management through Telegram:

Output Parser Autofixing, OpenAI Chat, Perplexity +10
AI & RAG

Effortlessly generate, review, and publish SEO-optimized blog posts to WordPress using AI and automation.

WordPress, Google Gemini Chat, Output Parser Structured +6
AI & RAG

&gt; Optimize your AI workflows, cut costs, and get faster, more accurate answers.

Model Selector, Output Parser Structured, Google Gemini Chat +6
AI & RAG

Awesome N8N Templates. Uses agent, telegramTrigger, mcpClientTool, mcpTrigger. Event-driven trigger; 33 nodes.

Agent, Telegram Trigger, Mcp Client Tool +9
AI & RAG

This workflow automatically generates stock market insights for selected tickers (e.g. GAZP, SBER, LKOH) using historical data, technical indicators, and an AI model. The results are then sent to Tele

Agent, OpenRouter Chat, Telegram Trigger +5