This workflow follows the Google Sheets → 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 →
{
"name": "P2-C2 Detector de Promesas (Skeleton)",
"nodes": [
{
"parameters": {
"path": "cobranza-in",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
0,
0
],
"id": "b2c00001-2222-4b02-a001-000000000001",
"name": "Verificacion GET"
},
{
"parameters": {
"respondWith": "text",
"responseBody": "={{ $json.query[\"hub.challenge\"] }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
208,
0
],
"id": "b2c00001-2222-4b02-a001-000000000002",
"name": "Respond to Webhook"
},
{
"parameters": {
"httpMethod": "POST",
"path": "cobranza-in",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
0,
176
],
"id": "b2c00001-2222-4b02-a001-000000000003",
"name": "Entrada POST"
},
{
"parameters": {
"jsCode": "// Desenvuelve el sobre de Meta y filtra: solo mensajes de texto\nconst value = $json.body?.entry?.[0]?.changes?.[0]?.value;\nif (!value?.messages?.[0]?.text?.body) return []; // ignora estados, reacciones, etc.\nreturn [{ json: value }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
208,
176
],
"id": "b2c00001-2222-4b02-a001-000000000004",
"name": "Normalizar"
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"value": "TU_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "facturas",
"mode": "name"
},
"filtersUI": {
"values": [
{
"lookupColumn": "telefono",
"lookupValue": "={{ $('Normalizar').item.json.messages[0].from }}"
}
]
},
"combineFilters": "AND",
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
416,
176
],
"id": "b2c00001-2222-4b02-a001-000000000005",
"name": "Buscar Factura",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "// Solo seguimos si el n\u00famero tiene una factura abierta (pendiente, recordada o promesa).\n// Si no es un deudor, este flujo lo ignora en silencio.\nconst abiertas = [\"pendiente\", \"recordada\", \"promesa\"];\nconst facturas = $input.all().map(i => i.json)\n .filter(f => f.factura_id && abiertas.includes(String(f.estado || \"\").trim().toLowerCase()));\nif (!facturas.length) return [];\n// Si tiene varias facturas abiertas, tomamos la de vencimiento m\u00e1s antiguo\nfacturas.sort((a, b) => String(a.fecha_vencimiento).localeCompare(String(b.fecha_vencimiento)));\nconst msg = $('Normalizar').first().json;\nreturn [{ json: {\n factura: facturas[0],\n telefono: msg.messages[0].from,\n nombre: msg.contacts?.[0]?.profile?.name || facturas[0].cliente || \"\",\n mensaje: msg.messages[0].text.body\n} }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
624,
176
],
"id": "b2c00001-2222-4b02-a001-000000000006",
"name": "Filtrar Deudor"
},
{
"parameters": {
"method": "POST",
"url": "https://generativelanguage.googleapis.com/v1beta/models/MODELO_GEMINI:generateContent",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "key",
"value": "TU_GEMINI_API_KEY"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"contents\": [{\n \"parts\": [{\n \"text\": \"Extrae del siguiente mensaje SOLO un objeto JSON estricto con esta forma exacta: {\\\"hay_promesa\\\": <true|false>, \\\"fecha_prometida\\\": \\\"<YYYY-MM-DD o null>\\\", \\\"monto\\\": <n\u00famero o null>}.\\n\\nMensaje del cliente: {{ $json.mensaje }}\"\n }]\n }],\n \"generationConfig\": { \"responseMimeType\": \"application/json\", \"temperature\": 0.1 }\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
832,
176
],
"id": "b2c00001-2222-4b02-a001-000000000007",
"name": "Gemini Extrae Promesa"
},
{
"parameters": {
"jsCode": "// Convierte la respuesta de Gemini a objeto. Si algo falla,\n// hay_promesa queda en false y el caso se escala a gesti\u00f3n humana (nunca se pierde).\nlet out = { hay_promesa: false, fecha_prometida: null, monto: null };\n\ntry {\n const raw = $input.first().json.candidates[0].content.parts[0].text;\n const clean = raw.replace(/```json|```/g, \"\").trim();\n const parsed = JSON.parse(clean);\n out = { ...out, ...parsed };\n if (out.fecha_prometida && !/^\\d{4}-\\d{2}-\\d{2}$/.test(String(out.fecha_prometida))) out.fecha_prometida = null;\n if (out.hay_promesa && !out.fecha_prometida) out.hay_promesa = false; // sin fecha no hay promesa agendable\n out.hay_promesa = out.hay_promesa === true || out.hay_promesa === \"true\";\n} catch (e) {\n out.error_parseo = e.message;\n}\n\nconst d = $('Filtrar Deudor').first().json;\nout.telefono = d.telefono;\nout.nombre = d.nombre;\nout.mensaje = d.mensaje;\nout.factura_id = d.factura.factura_id;\nout.cliente = d.factura.cliente || d.nombre;\nout.monto_factura = d.factura.monto;\nout.hoy = new Date().toISOString().slice(0, 10);\n\nreturn [{ json: out }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1040,
176
],
"id": "b2c00001-2222-4b02-a001-000000000008",
"name": "Parsear Promesa"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "b2c00001-2222-4b02-a001-000000000201",
"leftValue": "={{ $json.hay_promesa }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1248,
176
],
"id": "b2c00001-2222-4b02-a001-000000000009",
"name": "Hay Promesa"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "TU_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "promesas",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"factura_id": "={{ $json.factura_id }}",
"telefono": "={{ $json.telefono }}",
"cliente": "={{ $json.cliente }}",
"fecha_promesa": "={{ $json.hoy }}",
"fecha_prometida": "={{ $json.fecha_prometida }}",
"monto_prometido": "={{ $json.monto || $json.monto_factura }}",
"estado": "activa",
"recordada": "no",
"mensaje_original": "={{ $json.mensaje }}"
},
"matchingColumns": [
"factura_id"
],
"schema": []
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1456,
64
],
"id": "b2c00001-2222-4b02-a001-000000000010",
"name": "Registrar Promesa"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "TU_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "facturas",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"factura_id": "={{ $('Parsear Promesa').item.json.factura_id }}",
"estado": "promesa",
"notas": "={{ 'Promesa: ' + $('Parsear Promesa').item.json.fecha_prometida + ' \u2014 \"' + $('Parsear Promesa').item.json.mensaje + '\"' }}"
},
"matchingColumns": [
"factura_id"
],
"schema": []
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1664,
64
],
"id": "b2c00001-2222-4b02-a001-000000000011",
"name": "Marcar Factura Promesa"
},
{
"parameters": {
"resource": "message",
"operation": "send",
"phoneNumberId": "TU_PHONE_NUMBER_ID",
"recipientPhoneNumber": "={{ $('Parsear Promesa').item.json.telefono }}",
"textBody": "=\u00a1Perfecto{{ $('Parsear Promesa').item.json.nombre ? ', ' + $('Parsear Promesa').item.json.nombre : '' }}! \ud83d\ude4c Anotamos tu pago de ${{ $('Parsear Promesa').item.json.monto || $('Parsear Promesa').item.json.monto_factura }} para el {{ $('Parsear Promesa').item.json.fecha_prometida }}. Ese d\u00eda te enviaremos un recordatorio. \u00a1Gracias!",
"additionalFields": {}
},
"type": "n8n-nodes-base.whatsApp",
"typeVersion": 1,
"position": [
1872,
64
],
"id": "b2c00001-2222-4b02-a001-000000000012",
"name": "Confirmar al Cliente"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "TU_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "facturas",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"factura_id": "={{ $('Parsear Promesa').item.json.factura_id }}",
"estado": "gestion_humana",
"notas": "={{ 'Respuesta sin promesa clara: \"' + $('Parsear Promesa').item.json.mensaje + '\"' }}"
},
"matchingColumns": [
"factura_id"
],
"schema": []
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1456,
288
],
"id": "b2c00001-2222-4b02-a001-000000000013",
"name": "Marcar Gestion Humana"
},
{
"parameters": {
"resource": "message",
"operation": "send",
"phoneNumberId": "TU_PHONE_NUMBER_ID",
"recipientPhoneNumber": "TU_NUMERO_DUENO",
"textBody": "=\u26a0\ufe0f COBRANZA \u2014 Requiere gesti\u00f3n humana\n{{ $('Parsear Promesa').item.json.cliente || $('Parsear Promesa').item.json.telefono }} respondi\u00f3 sobre la factura {{ $('Parsear Promesa').item.json.factura_id }} (${{ $('Parsear Promesa').item.json.monto_factura }}) y no se detect\u00f3 una promesa de pago clara:\n\"{{ $('Parsear Promesa').item.json.mensaje }}\"\nResponder personalmente.",
"additionalFields": {}
},
"type": "n8n-nodes-base.whatsApp",
"typeVersion": 1,
"position": [
1664,
288
],
"id": "b2c00001-2222-4b02-a001-000000000014",
"name": "Alerta al Dueno"
}
],
"connections": {
"Verificacion GET": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Entrada POST": {
"main": [
[
{
"node": "Normalizar",
"type": "main",
"index": 0
}
]
]
},
"Normalizar": {
"main": [
[
{
"node": "Buscar Factura",
"type": "main",
"index": 0
}
]
]
},
"Buscar Factura": {
"main": [
[
{
"node": "Filtrar Deudor",
"type": "main",
"index": 0
}
]
]
},
"Filtrar Deudor": {
"main": [
[
{
"node": "Gemini Extrae Promesa",
"type": "main",
"index": 0
}
]
]
},
"Gemini Extrae Promesa": {
"main": [
[
{
"node": "Parsear Promesa",
"type": "main",
"index": 0
}
]
]
},
"Parsear Promesa": {
"main": [
[
{
"node": "Hay Promesa",
"type": "main",
"index": 0
}
]
]
},
"Hay Promesa": {
"main": [
[
{
"node": "Registrar Promesa",
"type": "main",
"index": 0
}
],
[
{
"node": "Marcar Gestion Humana",
"type": "main",
"index": 0
}
]
]
},
"Registrar Promesa": {
"main": [
[
{
"node": "Marcar Factura Promesa",
"type": "main",
"index": 0
}
]
]
},
"Marcar Factura Promesa": {
"main": [
[
{
"node": "Confirmar al Cliente",
"type": "main",
"index": 0
}
]
]
},
"Marcar Gestion Humana": {
"main": [
[
{
"node": "Alerta al Dueno",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"timezone": "America/Guayaquil"
},
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
P2-C2 Detector de Promesas (Skeleton). Uses googleSheets, httpRequest, whatsApp. Webhook trigger; 14 nodes.
Source: https://gist.github.com/Omar8485/bd4b6387b78d98e37189e53d5c0b0be9 — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow is a complete, production-ready solution for recovering abandoned carts in Shopify stores using a multi-channel, multi-touch approach. It automates personalized follow-ups via Email, SMS
WF_UNIFIED_LEGAL_AUTOMATION. Uses googleSheets, httpRequest, telegram, telegramTrigger. Webhook trigger; 53 nodes.
Automates LinkedIn job searches across multiple countries and categories, filters results with AI, stores data in Google Sheets, and sends weekly Telegram notifications. Perfect for professionals seek
01_order_processing_tilda. Uses stickyNote, googleSheets, httpRequest, telegram. Webhook trigger; 25 nodes.
Turn every sales meeting into a coaching opportunity. This workflow automatically analyzes tldv meeting recordings using OpenAI (GPT-4) to provide instant, actionable feedback to your sales team.