From cc6980a80d95416cb6e374200bed298e1ee28298 Mon Sep 17 00:00:00 2001 From: Isaac_Aracena Date: Thu, 11 Jun 2026 04:00:20 +0000 Subject: [PATCH] Update backup: Tablero CDC - Sync Proyecto a Sheet WEBHOOK --- ...ero-cdc-sync-proyecto-a-sheet-webhook.json | 463 +++++++++++++++--- 1 file changed, 395 insertions(+), 68 deletions(-) diff --git a/tablero-cdc-sync-proyecto-a-sheet-webhook.json b/tablero-cdc-sync-proyecto-a-sheet-webhook.json index 5a70643..555d8bc 100644 --- a/tablero-cdc-sync-proyecto-a-sheet-webhook.json +++ b/tablero-cdc-sync-proyecto-a-sheet-webhook.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-06-05T17:52:49.771Z", + "updatedAt": "2026-06-10T20:28:31.848Z", "createdAt": "2026-05-27T18:36:03.445Z", "id": "d39DtIUTQzduqX0v", "name": "Tablero CDC - Sync Proyecto a Sheet WEBHOOK", @@ -80,12 +80,12 @@ }, { "parameters": { - "jsCode": "function getNodeJson(nodeName) {\n try {\n return $(nodeName).first().json;\n } catch (error) {\n return {};\n }\n}\n\nfunction getNodeItems(nodeName) {\n try {\n return $(nodeName).all();\n } catch (error) {\n return [];\n }\n}\n\nfunction normalizeProjectResponse(value) {\n if (Array.isArray(value)) return value[0] || null;\n return value || null;\n}\n\nfunction normalizeLinksResponse(items) {\n const links = [];\n\n for (const item of items) {\n const json = item.json;\n\n if (Array.isArray(json)) {\n links.push(...json);\n } else if (json && typeof json === 'object') {\n links.push(json);\n }\n }\n\n return links.filter(link => link && link.url);\n}\n\nfunction formatMonth(dateValue) {\n const date = dateValue ? new Date(dateValue) : new Date();\n\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n\n return date.toLocaleDateString('es-DO', {\n month: 'long',\n });\n}\n\nfunction formatYear(dateValue) {\n const date = dateValue ? new Date(dateValue) : new Date();\n\n if (Number.isNaN(date.getTime())) {\n return String(new Date().getFullYear());\n }\n\n return String(date.getFullYear());\n}\n\nfunction formatMoney(value) {\n if (value === null || value === undefined || value === '') return '';\n return value;\n}\n\nfunction formatLinks(links, type, prefix) {\n const filtered = links.filter(link => String(link.link_type || '').toLowerCase() === type);\n\n if (!filtered.length) return '';\n\n return filtered\n .map((link, index) => `${prefix}${index + 1}) ${link.url}`)\n .join('\\n');\n}\n\nconst rawProject = getNodeJson('Supabase - Leer proyecto');\nconst project = normalizeProjectResponse(rawProject);\n\nif (!project || !project.id) {\n throw new Error('No se encontró el proyecto en Supabase. Revisa el project_id.');\n}\n\nconst linkItems = getNodeItems('Supabase - Leer links del proyecto');\nconst links = normalizeLinksResponse(linkItems);\n\nconst proposalLinks = formatLinks(links, 'proposal', '');\nconst finalArtLinks = formatLinks(links, 'final_art', 'AF-');\n\nconst createdAt = project.created_at || new Date().toISOString();\n\nreturn [\n {\n json: {\n 'Año': formatYear(createdAt),\n 'Mes': formatMonth(createdAt),\n 'Tipo o nombre de proyecto': project.title || '',\n 'Cliente': project.client || '',\n 'BU Solicita': project.country || '',\n 'Marca': project.brand || '',\n 'CM': project.country_manager || '',\n 'Status': project.status || '',\n 'Comentarios': project.description || '',\n 'Link de propuesta': proposalLinks,\n '$ Interno Cargado': formatMoney(project.internal_amount),\n 'Link del Brief': project.brief_link || '',\n 'Link de artes finales': finalArtLinks,\n 'Project ID': project.id,\n },\n },\n];" + "jsCode": "function getNodeJson(nodeName) {\n try {\n return $(nodeName).first().json;\n } catch (error) {\n return {};\n }\n}\n\nfunction getNodeItems(nodeName) {\n try {\n return $(nodeName).all();\n } catch (error) {\n return [];\n }\n}\n\nfunction normalizeProjectResponse(value) {\n if (Array.isArray(value)) return value[0] || null;\n return value || null;\n}\n\nfunction normalizeLinksResponse(items) {\n const links = [];\n\n for (const item of items) {\n const json = item.json;\n\n if (Array.isArray(json)) {\n links.push(...json);\n } else if (json && typeof json === 'object') {\n links.push(json);\n }\n }\n\n return links.filter(link => link && link.url);\n}\n\nfunction formatMonth(dateValue) {\n const date = dateValue ? new Date(dateValue) : new Date();\n\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n\n const month = date.toLocaleDateString('es-DO', {\n month: 'long',\n });\n\n return month.charAt(0).toUpperCase() + month.slice(1);\n}\n\nfunction formatYear(dateValue) {\n const date = dateValue ? new Date(dateValue) : new Date();\n\n if (Number.isNaN(date.getTime())) {\n return String(new Date().getFullYear());\n }\n\n return String(date.getFullYear());\n}\n\nfunction formatMoney(value) {\n if (value === null || value === undefined || value === '') return '';\n return value;\n}\n\nfunction formatLinks(links, type, prefix) {\n const filtered = links.filter(link => String(link.link_type || '').toLowerCase() === type);\n\n if (!filtered.length) return '';\n\n return filtered\n .map((link, index) => `${prefix}${index + 1}) ${link.url}`)\n .join('\\n');\n}\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction findExistingSheetRow(projectId) {\n const sheetItems = getNodeItems('Sheets - Leer proyectos existentes');\n\n for (const item of sheetItems) {\n const row = item.json || {};\n const rowProjectId = clean(row['Project ID']);\n\n if (rowProjectId && rowProjectId === projectId) {\n return row;\n }\n }\n\n return null;\n}\n\nconst rawProject = getNodeJson('Supabase - Leer proyecto');\nconst project = normalizeProjectResponse(rawProject);\n\nif (!project || !project.id) {\n throw new Error('No se encontró el proyecto en Supabase. Revisa el project_id.');\n}\n\nconst linkItems = getNodeItems('Supabase - Leer links del proyecto');\nconst links = normalizeLinksResponse(linkItems);\n\nconst proposalLinks = formatLinks(links, 'proposal', '');\nconst finalArtLinks = formatLinks(links, 'final_art', 'AF-');\n\nconst createdAt = project.created_at || new Date().toISOString();\n\nconst existingSheetRow = findExistingSheetRow(project.id);\n\nconst sheetYear = existingSheetRow && clean(existingSheetRow['Año'])\n ? clean(existingSheetRow['Año'])\n : formatYear(createdAt);\n\nconst sheetMonth = existingSheetRow && clean(existingSheetRow['Mes'])\n ? clean(existingSheetRow['Mes'])\n : formatMonth(createdAt);\n\nreturn [\n {\n json: {\n 'Año': sheetYear,\n 'Mes': sheetMonth,\n 'Tipo o nombre de proyecto': project.title || '',\n 'Cliente': project.client || '',\n 'BU Solicita': project.country || '',\n 'Marca': project.brand || '',\n 'CM': project.country_manager || '',\n 'Status': project.status || '',\n 'Comentarios': project.description || '',\n 'Link de propuesta': proposalLinks,\n '$ Interno Cargado': formatMoney(project.internal_amount),\n 'Link del Brief': project.brief_link || '',\n 'Link de artes finales': finalArtLinks,\n 'Project ID': project.id,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 2320, + 2528, 288 ], "id": "1a9e76bd-20f8-481b-82c1-95d631e261ae", @@ -286,7 +286,7 @@ "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ - 2528, + 2736, 288 ], "id": "d66bf9b4-9694-4f43-8dd6-4e9efe4eece8", @@ -339,7 +339,7 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ - 2736, + 2944, 288 ], "id": "425a3374-03dc-4424-b089-3d86efbd565e", @@ -349,7 +349,7 @@ "parameters": { "content": "Sync Proyecto a Sheet WEBHOOK\n\nFlujo de producción que recibe project_id desde la app Tablero CDC y sincroniza el proyecto con Google Sheet PROYECTOS 2026.\n\nFlujo:\nWebhook -> preparar project_id -> leer projects -> leer project_links -> preparar fila -> upsert en Sheet -> responder webhook.\n\nNotas:\n- La app llama este flujo usando VITE_WEBHOOK_URL.\n- El upsert usa Project ID.\n- Encabezados del Sheet: fila 3.\n- Datos desde fila 4.\n- Filas viejas sin Project ID quedan como histórico.\n- Leer links del proyecto debe tener Always Output Data activo para que proyectos sin links también se peguen.\n\nSi cambia Supabase empresarial/self-hosted, actualizar URL y keys en los HTTP Request.", "height": 672, - "width": 2756 + "width": 2980 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, @@ -547,6 +547,46 @@ ], "id": "182432ed-700f-4fff-84b9-8aa45fd7c0b9", "name": "Respond - Proyecto no encontrado en Sheet" + }, + { + "parameters": { + "documentId": { + "__rl": true, + "value": "1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q", + "mode": "list", + "cachedResultName": "APROBACIONES PROYECTOS", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q/edit?usp=drivesdk" + }, + "sheetName": { + "__rl": true, + "value": 1563472127, + "mode": "list", + "cachedResultName": "PROYECTOS 2026", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q/edit#gid=1563472127" + }, + "options": { + "dataLocationOnSheet": { + "values": { + "rangeDefinition": "specifyRangeA1", + "range": "A3:N" + } + } + } + }, + "type": "n8n-nodes-base.googleSheets", + "typeVersion": 4.7, + "position": [ + 2272, + 288 + ], + "id": "b4f4366e-3381-4692-8740-8f7287eeb9a4", + "name": "Sheets - Leer proyectos existentes", + "credentials": { + "googleSheetsOAuth2Api": { + "id": "K0hDZh3a85MpOHCs", + "name": "Google Sheets account 2" + } + } } ], "connections": { @@ -565,7 +605,7 @@ "main": [ [ { - "node": "Code - Preparar fila Sheet", + "node": "Sheets - Leer proyectos existentes", "type": "main", "index": 0 } @@ -684,6 +724,17 @@ } ] ] + }, + "Sheets - Leer proyectos existentes": { + "main": [ + [ + { + "node": "Code - Preparar fila Sheet", + "type": "main", + "index": 0 + } + ] + ] } }, "settings": { @@ -698,9 +749,9 @@ "meta": { "templateCredsSetupCompleted": true }, - "versionId": "a9640103-b3d6-41e1-b710-1525d1f9ebd9", - "activeVersionId": "17638407-8c4f-4070-b040-a23f2750265e", - "versionCounter": 148, + "versionId": "98d628ed-dd52-4c59-9cea-c8e8a2ae8ff0", + "activeVersionId": "98d628ed-dd52-4c59-9cea-c8e8a2ae8ff0", + "versionCounter": 166, "triggerCount": 1, "shared": [ { @@ -723,9 +774,9 @@ ], "tags": [], "activeVersion": { - "updatedAt": "2026-06-05T13:32:25.000Z", - "createdAt": "2026-05-29T23:01:48.396Z", - "versionId": "17638407-8c4f-4070-b040-a23f2750265e", + "updatedAt": "2026-06-10T20:28:32.000Z", + "createdAt": "2026-06-10T20:28:31.850Z", + "versionId": "98d628ed-dd52-4c59-9cea-c8e8a2ae8ff0", "workflowId": "d39DtIUTQzduqX0v", "nodes": [ { @@ -757,8 +808,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 608, - 112 + 1824, + 288 ], "id": "5fdded5f-5a19-4f40-bc75-21ae92ac3df2", "name": "Supabase - Leer proyecto" @@ -792,8 +843,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 816, - 112 + 2064, + 288 ], "id": "d2839f2d-af1f-49ab-bfd6-4c3b76f656ad", "name": "Supabase - Leer links del proyecto", @@ -801,13 +852,13 @@ }, { "parameters": { - "jsCode": "function getNodeJson(nodeName) {\n try {\n return $(nodeName).first().json;\n } catch (error) {\n return {};\n }\n}\n\nfunction getNodeItems(nodeName) {\n try {\n return $(nodeName).all();\n } catch (error) {\n return [];\n }\n}\n\nfunction normalizeProjectResponse(value) {\n if (Array.isArray(value)) return value[0] || null;\n return value || null;\n}\n\nfunction normalizeLinksResponse(items) {\n const links = [];\n\n for (const item of items) {\n const json = item.json;\n\n if (Array.isArray(json)) {\n links.push(...json);\n } else if (json && typeof json === 'object') {\n links.push(json);\n }\n }\n\n return links.filter(link => link && link.url);\n}\n\nfunction formatMonth(dateValue) {\n const date = dateValue ? new Date(dateValue) : new Date();\n\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n\n return date.toLocaleDateString('es-DO', {\n month: 'long',\n });\n}\n\nfunction formatYear(dateValue) {\n const date = dateValue ? new Date(dateValue) : new Date();\n\n if (Number.isNaN(date.getTime())) {\n return String(new Date().getFullYear());\n }\n\n return String(date.getFullYear());\n}\n\nfunction formatMoney(value) {\n if (value === null || value === undefined || value === '') return '';\n return value;\n}\n\nfunction formatLinks(links, type, prefix) {\n const filtered = links.filter(link => String(link.link_type || '').toLowerCase() === type);\n\n if (!filtered.length) return '';\n\n return filtered\n .map((link, index) => `${prefix}${index + 1}) ${link.url}`)\n .join('\\n');\n}\n\nconst rawProject = getNodeJson('Supabase - Leer proyecto');\nconst project = normalizeProjectResponse(rawProject);\n\nif (!project || !project.id) {\n throw new Error('No se encontró el proyecto en Supabase. Revisa el project_id.');\n}\n\nconst linkItems = getNodeItems('Supabase - Leer links del proyecto');\nconst links = normalizeLinksResponse(linkItems);\n\nconst proposalLinks = formatLinks(links, 'proposal', '');\nconst finalArtLinks = formatLinks(links, 'final_art', 'AF-');\n\nconst createdAt = project.created_at || new Date().toISOString();\n\nreturn [\n {\n json: {\n 'Año': formatYear(createdAt),\n 'Mes': formatMonth(createdAt),\n 'Tipo o nombre de proyecto': project.title || '',\n 'Cliente': project.client || '',\n 'BU Solicita': project.country || '',\n 'Marca': project.brand || '',\n 'CM': project.country_manager || '',\n 'Status': project.status || '',\n 'Comentarios': project.description || '',\n 'Link de propuesta': proposalLinks,\n '$ Interno Cargado': formatMoney(project.internal_amount),\n 'Link del Brief': project.brief_link || '',\n 'Link de artes finales': finalArtLinks,\n 'Project ID': project.id,\n },\n },\n];" + "jsCode": "function getNodeJson(nodeName) {\n try {\n return $(nodeName).first().json;\n } catch (error) {\n return {};\n }\n}\n\nfunction getNodeItems(nodeName) {\n try {\n return $(nodeName).all();\n } catch (error) {\n return [];\n }\n}\n\nfunction normalizeProjectResponse(value) {\n if (Array.isArray(value)) return value[0] || null;\n return value || null;\n}\n\nfunction normalizeLinksResponse(items) {\n const links = [];\n\n for (const item of items) {\n const json = item.json;\n\n if (Array.isArray(json)) {\n links.push(...json);\n } else if (json && typeof json === 'object') {\n links.push(json);\n }\n }\n\n return links.filter(link => link && link.url);\n}\n\nfunction formatMonth(dateValue) {\n const date = dateValue ? new Date(dateValue) : new Date();\n\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n\n const month = date.toLocaleDateString('es-DO', {\n month: 'long',\n });\n\n return month.charAt(0).toUpperCase() + month.slice(1);\n}\n\nfunction formatYear(dateValue) {\n const date = dateValue ? new Date(dateValue) : new Date();\n\n if (Number.isNaN(date.getTime())) {\n return String(new Date().getFullYear());\n }\n\n return String(date.getFullYear());\n}\n\nfunction formatMoney(value) {\n if (value === null || value === undefined || value === '') return '';\n return value;\n}\n\nfunction formatLinks(links, type, prefix) {\n const filtered = links.filter(link => String(link.link_type || '').toLowerCase() === type);\n\n if (!filtered.length) return '';\n\n return filtered\n .map((link, index) => `${prefix}${index + 1}) ${link.url}`)\n .join('\\n');\n}\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction findExistingSheetRow(projectId) {\n const sheetItems = getNodeItems('Sheets - Leer proyectos existentes');\n\n for (const item of sheetItems) {\n const row = item.json || {};\n const rowProjectId = clean(row['Project ID']);\n\n if (rowProjectId && rowProjectId === projectId) {\n return row;\n }\n }\n\n return null;\n}\n\nconst rawProject = getNodeJson('Supabase - Leer proyecto');\nconst project = normalizeProjectResponse(rawProject);\n\nif (!project || !project.id) {\n throw new Error('No se encontró el proyecto en Supabase. Revisa el project_id.');\n}\n\nconst linkItems = getNodeItems('Supabase - Leer links del proyecto');\nconst links = normalizeLinksResponse(linkItems);\n\nconst proposalLinks = formatLinks(links, 'proposal', '');\nconst finalArtLinks = formatLinks(links, 'final_art', 'AF-');\n\nconst createdAt = project.created_at || new Date().toISOString();\n\nconst existingSheetRow = findExistingSheetRow(project.id);\n\nconst sheetYear = existingSheetRow && clean(existingSheetRow['Año'])\n ? clean(existingSheetRow['Año'])\n : formatYear(createdAt);\n\nconst sheetMonth = existingSheetRow && clean(existingSheetRow['Mes'])\n ? clean(existingSheetRow['Mes'])\n : formatMonth(createdAt);\n\nreturn [\n {\n json: {\n 'Año': sheetYear,\n 'Mes': sheetMonth,\n 'Tipo o nombre de proyecto': project.title || '',\n 'Cliente': project.client || '',\n 'BU Solicita': project.country || '',\n 'Marca': project.brand || '',\n 'CM': project.country_manager || '',\n 'Status': project.status || '',\n 'Comentarios': project.description || '',\n 'Link de propuesta': proposalLinks,\n '$ Interno Cargado': formatMoney(project.internal_amount),\n 'Link del Brief': project.brief_link || '',\n 'Link de artes finales': finalArtLinks,\n 'Project ID': project.id,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 1024, - 112 + 2528, + 288 ], "id": "1a9e76bd-20f8-481b-82c1-95d631e261ae", "name": "Code - Preparar fila Sheet" @@ -1007,8 +1058,8 @@ "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ - 1232, - 112 + 2736, + 288 ], "id": "d66bf9b4-9694-4f43-8dd6-4e9efe4eece8", "name": "Sheets - Upsert proyecto", @@ -1030,7 +1081,7 @@ "typeVersion": 2.1, "position": [ 208, - 112 + 272 ], "id": "b5d0c07b-8b2d-437c-9475-530f64e84247", "name": "Webhook", @@ -1038,13 +1089,13 @@ }, { "parameters": { - "jsCode": "const body = $json.body && typeof $json.body === 'object'\n ? $json.body\n : $json;\n\nconst projectId =\n body.project_id ||\n body.projectId ||\n body.id ||\n '';\n\nif (!projectId) {\n throw new Error('No se recibió project_id en el webhook.');\n}\n\nreturn [\n {\n json: {\n project_id: String(projectId).trim(),\n received_at: new Date().toISOString(),\n },\n },\n];" + "jsCode": "const input = $json || {};\nconst body = input.body || input;\n\nconst projectId =\n body.project_id ||\n body.projectId ||\n body.id ||\n '';\n\nconst action =\n String(body.action || 'sync').trim().toLowerCase();\n\nif (!projectId) {\n throw new Error('Falta project_id en el payload del webhook');\n}\n\nreturn [\n {\n json: {\n action,\n project_id: projectId,\n received_at: new Date().toISOString(),\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 416, - 112 + 272 ], "id": "017aafcb-5f7a-407d-8224-caf365186b78", "name": "Code - Preparar project_id" @@ -1060,8 +1111,8 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ - 1440, - 112 + 2944, + 288 ], "id": "425a3374-03dc-4424-b089-3d86efbd565e", "name": "Respond to Webhook" @@ -1069,8 +1120,8 @@ { "parameters": { "content": "Sync Proyecto a Sheet WEBHOOK\n\nFlujo de producción que recibe project_id desde la app Tablero CDC y sincroniza el proyecto con Google Sheet PROYECTOS 2026.\n\nFlujo:\nWebhook -> preparar project_id -> leer projects -> leer project_links -> preparar fila -> upsert en Sheet -> responder webhook.\n\nNotas:\n- La app llama este flujo usando VITE_WEBHOOK_URL.\n- El upsert usa Project ID.\n- Encabezados del Sheet: fila 3.\n- Datos desde fila 4.\n- Filas viejas sin Project ID quedan como histórico.\n- Leer links del proyecto debe tener Always Output Data activo para que proyectos sin links también se peguen.\n\nSi cambia Supabase empresarial/self-hosted, actualizar URL y keys en los HTTP Request.", - "height": 496, - "width": 1472 + "height": 672, + "width": 2980 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, @@ -1080,6 +1131,234 @@ ], "id": "16a4a31a-a288-45f5-86fd-be4d92a7a2c1", "name": "Sticky Note" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "36231932-b806-4b8a-8d59-e9a74ef4c74b", + "leftValue": "={{ $json.action }}", + "rightValue": "delete", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 688, + 272 + ], + "id": "e891ba4b-23d2-4432-bb33-deffbee8097a", + "name": "IF - Acción eliminar?" + }, + { + "parameters": { + "documentId": { + "__rl": true, + "value": "1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q", + "mode": "list", + "cachedResultName": "APROBACIONES PROYECTOS", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q/edit?usp=drivesdk" + }, + "sheetName": { + "__rl": true, + "value": 1563472127, + "mode": "list", + "cachedResultName": "PROYECTOS 2026", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q/edit#gid=1563472127" + }, + "options": { + "dataLocationOnSheet": { + "values": { + "rangeDefinition": "specifyRange", + "headerRow": 3, + "firstDataRow": 4 + } + } + } + }, + "type": "n8n-nodes-base.googleSheets", + "typeVersion": 4.7, + "position": [ + 1200, + -96 + ], + "id": "df240622-6ec0-44cb-b945-28c09bd50496", + "name": "Sheets - Leer proyectos para eliminar", + "credentials": { + "googleSheetsOAuth2Api": { + "id": "K0hDZh3a85MpOHCs", + "name": "Google Sheets account 2" + } + } + }, + { + "parameters": { + "jsCode": "const projectId = $('Code - Preparar project_id').first().json.project_id;\n\nconst rows = $input.all();\n\n// En este Sheet los encabezados están en fila 3 y los datos empiezan en fila 4.\n// Si Google Sheets no devuelve row_number, lo calculamos con index + 4.\nconst FIRST_DATA_ROW = 4;\n\nconst matches = [];\n\nrows.forEach((item, index) => {\n const row = item.json;\n\n const rowProjectId =\n row['Project ID'] ||\n row['project_id'] ||\n row['PROJECT ID'] ||\n '';\n\n if (String(rowProjectId).trim() === String(projectId).trim()) {\n const rowNumber =\n row.row_number ||\n row.rowNumber ||\n row.__rowNumber ||\n row.rowIndex ||\n index + FIRST_DATA_ROW;\n\n matches.push({\n row,\n row_number: Number(rowNumber),\n });\n }\n});\n\nif (matches.length === 0) {\n return [\n {\n json: {\n ok: true,\n action: 'delete',\n project_id: projectId,\n found: false,\n message: 'No se encontró fila en Google Sheet para ese Project ID. Se considera eliminado.',\n },\n },\n ];\n}\n\nreturn matches.map((match) => ({\n json: {\n ok: true,\n action: 'delete',\n project_id: projectId,\n found: true,\n row_number: match.row_number,\n row: match.row,\n },\n}));" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1408, + -96 + ], + "id": "ec6056cf-99b9-4b23-9ee4-62b71100aaa2", + "name": "Code - Buscar fila a eliminar" + }, + { + "parameters": { + "operation": "delete", + "documentId": { + "__rl": true, + "value": "1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q", + "mode": "list", + "cachedResultName": "APROBACIONES PROYECTOS", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q/edit?usp=drivesdk" + }, + "sheetName": { + "__rl": true, + "value": 1563472127, + "mode": "list", + "cachedResultName": "PROYECTOS 2026", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q/edit#gid=1563472127" + }, + "startIndex": "={{ $json.row_number }}" + }, + "type": "n8n-nodes-base.googleSheets", + "typeVersion": 4.7, + "position": [ + 1840, + -112 + ], + "id": "fc0117c5-9b01-44cc-b8d4-659ed9784fc1", + "name": "Sheets - Eliminar fila proyecto", + "credentials": { + "googleSheetsOAuth2Api": { + "id": "K0hDZh3a85MpOHCs", + "name": "Google Sheets account 2" + } + } + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "{\n \"ok\": true,\n \"action\": \"delete\",\n \"message\": \"Proyecto eliminado del Google Sheet\"\n}", + "options": {} + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.5, + "position": [ + 2048, + -112 + ], + "id": "02857828-6a02-40a6-ade7-203361f0698b", + "name": "Respond - Proyecto eliminado del Sheet" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "b513a96e-250f-4730-a926-bcc438daa0c6", + "leftValue": "={{ $json.found }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1616, + -96 + ], + "id": "c281d09b-16da-450b-bcf8-0412f6658dcb", + "name": "IF - Fila encontrada?" + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "{\n \"ok\": true,\n \"action\": \"delete\",\n \"found\": false,\n \"message\": \"No se encontró fila en Google Sheet para ese Project ID. Se considera eliminado.\"\n}", + "options": { + "responseCode": 200 + } + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.5, + "position": [ + 1856, + 112 + ], + "id": "182432ed-700f-4fff-84b9-8aa45fd7c0b9", + "name": "Respond - Proyecto no encontrado en Sheet" + }, + { + "parameters": { + "documentId": { + "__rl": true, + "value": "1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q", + "mode": "list", + "cachedResultName": "APROBACIONES PROYECTOS", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q/edit?usp=drivesdk" + }, + "sheetName": { + "__rl": true, + "value": 1563472127, + "mode": "list", + "cachedResultName": "PROYECTOS 2026", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1MfoIs_y7-4ad-g5dxzOo8oAFIdraA_ZT4k-OCGMtT4Q/edit#gid=1563472127" + }, + "options": { + "dataLocationOnSheet": { + "values": { + "rangeDefinition": "specifyRangeA1", + "range": "A3:N" + } + } + } + }, + "type": "n8n-nodes-base.googleSheets", + "typeVersion": 4.7, + "position": [ + 2272, + 288 + ], + "id": "b4f4366e-3381-4692-8740-8f7287eeb9a4", + "name": "Sheets - Leer proyectos existentes", + "credentials": { + "googleSheetsOAuth2Api": { + "id": "K0hDZh3a85MpOHCs", + "name": "Google Sheets account 2" + } + } } ], "connections": { @@ -1098,7 +1377,7 @@ "main": [ [ { - "node": "Code - Preparar fila Sheet", + "node": "Sheets - Leer proyectos existentes", "type": "main", "index": 0 } @@ -1131,7 +1410,7 @@ "main": [ [ { - "node": "Supabase - Leer proyecto", + "node": "IF - Acción eliminar?", "type": "main", "index": 0 } @@ -1148,52 +1427,100 @@ } ] ] + }, + "IF - Acción eliminar?": { + "main": [ + [ + { + "node": "Sheets - Leer proyectos para eliminar", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Supabase - Leer proyecto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Sheets - Leer proyectos para eliminar": { + "main": [ + [ + { + "node": "Code - Buscar fila a eliminar", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code - Buscar fila a eliminar": { + "main": [ + [ + { + "node": "IF - Fila encontrada?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Sheets - Eliminar fila proyecto": { + "main": [ + [ + { + "node": "Respond - Proyecto eliminado del Sheet", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF - Fila encontrada?": { + "main": [ + [ + { + "node": "Sheets - Eliminar fila proyecto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Respond - Proyecto no encontrado en Sheet", + "type": "main", + "index": 0 + } + ] + ] + }, + "Sheets - Leer proyectos existentes": { + "main": [ + [ + { + "node": "Code - Preparar fila Sheet", + "type": "main", + "index": 0 + } + ] + ] } }, "authors": "Isaac Aracena", - "name": "Tablero CDC - Sync Proyecto a Sheet WWEBHOOK", + "name": "Version 98d628ed", "description": "", - "autosaved": true, + "autosaved": false, "workflowPublishHistory": [ { - "createdAt": "2026-05-29T23:03:35.864Z", - "id": 788, + "createdAt": "2026-06-10T20:28:32.705Z", + "id": 1300, "workflowId": "d39DtIUTQzduqX0v", - "versionId": "17638407-8c4f-4070-b040-a23f2750265e", + "versionId": "98d628ed-dd52-4c59-9cea-c8e8a2ae8ff0", "event": "activated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" - }, - { - "createdAt": "2026-06-04T00:43:22.847Z", - "id": 1082, - "workflowId": "d39DtIUTQzduqX0v", - "versionId": "17638407-8c4f-4070-b040-a23f2750265e", - "event": "activated", - "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" - }, - { - "createdAt": "2026-06-05T13:32:25.776Z", - "id": 1112, - "workflowId": "d39DtIUTQzduqX0v", - "versionId": "17638407-8c4f-4070-b040-a23f2750265e", - "event": "activated", - "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" - }, - { - "createdAt": "2026-06-04T00:43:22.817Z", - "id": 1081, - "workflowId": "d39DtIUTQzduqX0v", - "versionId": "17638407-8c4f-4070-b040-a23f2750265e", - "event": "deactivated", - "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" - }, - { - "createdAt": "2026-06-05T13:32:21.145Z", - "id": 1111, - "workflowId": "d39DtIUTQzduqX0v", - "versionId": "17638407-8c4f-4070-b040-a23f2750265e", - "event": "deactivated", - "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" } ] }