{ "updatedAt": "2026-06-05T16:53:12.439Z", "createdAt": "2026-05-27T18:36:03.445Z", "id": "d39DtIUTQzduqX0v", "name": "Tablero CDC - Sync Proyecto a Sheet WEBHOOK", "description": null, "active": true, "isArchived": false, "nodes": [ { "parameters": { "url": "=https://dbit.digitalcompass.agency/rest/v1/tablero_cdc_projects?id=eq.{{ $('Code - Preparar project_id').first().json.project_id }}&select=*", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Accept", "value": "application/json" }, { "name": "Content-Type", "value": "application/json" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 1824, 288 ], "id": "5fdded5f-5a19-4f40-bc75-21ae92ac3df2", "name": "Supabase - Leer proyecto" }, { "parameters": { "url": "=https://dbit.digitalcompass.agency/rest/v1/tablero_cdc_project_links?project_id=eq.{{ $('Code - Preparar project_id').first().json.project_id }}&select=*&order=created_at.asc", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Accept", "value": "application/json" }, { "name": "Content-Type", "value": "application/json" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 2064, 288 ], "id": "d2839f2d-af1f-49ab-bfd6-4c3b76f656ad", "name": "Supabase - Leer links del proyecto", "alwaysOutputData": true }, { "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];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2320, 288 ], "id": "1a9e76bd-20f8-481b-82c1-95d631e261ae", "name": "Code - Preparar fila Sheet" }, { "parameters": { "operation": "appendOrUpdate", "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" }, "columns": { "mappingMode": "defineBelow", "value": { "Año": "={{ $json[\"Año\"] }}", "Mes": "={{ $json[\"Mes\"] }}", "Tipo o nombre de proyecto": "={{ $json[\"Tipo o nombre de proyecto\"] }}", "Cliente": "={{ $json[\"Cliente\"] }}", "BU Solicita": "={{ $json[\"BU Solicita\"] }}", "Marca": "={{ $json[\"Marca\"] }}", "CM": "={{ $json[\"CM\"] }}", "Status": "={{ $json[\"Status\"] }}", "Comentarios": "={{ $json[\"Comentarios\"] }}", "Link de propuesta": "={{ $json[\"Link de propuesta\"] }}", "$ Interno Cargado": "={{ $json[\"$ Interno Cargado\"] }}", "Link del Brief": "={{ $json[\"Link del Brief\"] }}", "Link de artes finales": "={{ $json[\"Link de artes finales\"] }}", "Project ID": "={{ $json[\"Project ID\"] }}" }, "matchingColumns": [ "Project ID" ], "schema": [ { "id": "Año", "displayName": "Año", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Mes", "displayName": "Mes", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Tipo o nombre de proyecto", "displayName": "Tipo o nombre de proyecto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Cliente", "displayName": "Cliente", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "BU Solicita", "displayName": "BU Solicita", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Marca", "displayName": "Marca", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "CM", "displayName": "CM", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Status", "displayName": "Status", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Comentarios", "displayName": "Comentarios", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Link de propuesta", "displayName": "Link de propuesta", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "$ Interno Cargado", "displayName": "$ Interno Cargado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Link del Brief", "displayName": "Link del Brief", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Link de artes finales", "displayName": "Link de artes finales", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Project ID", "displayName": "Project ID", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": { "locationDefine": { "values": { "headerRow": 3, "firstDataRow": 4 } } } }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 2528, 288 ], "id": "d66bf9b4-9694-4f43-8dd6-4e9efe4eece8", "name": "Sheets - Upsert proyecto", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "httpMethod": "POST", "path": "tablero-cdc-sync-proyecto", "responseMode": "responseNode", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ 208, 272 ], "id": "b5d0c07b-8b2d-437c-9475-530f64e84247", "name": "Webhook", "webhookId": "4f237f0a-7607-4a8b-9020-11bc75a6ae32" }, { "parameters": { "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, 272 ], "id": "017aafcb-5f7a-407d-8224-caf365186b78", "name": "Code - Preparar project_id" }, { "parameters": { "respondWith": "json", "responseBody": "={{\n {\n ok: true,\n message: 'Proyecto sincronizado con Google Sheet',\n project_id: $('Code - Preparar project_id').first().json.project_id\n }\n}}", "options": { "responseCode": 200 } }, "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ 2736, 288 ], "id": "425a3374-03dc-4424-b089-3d86efbd565e", "name": "Respond to Webhook" }, { "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": 2692 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 160, -208 ], "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": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 976, 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\nconst matches = [];\n\nfor (const item of rows) {\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 matches.push(row);\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((row) => ({\n json: {\n ok: true,\n action: 'delete',\n project_id: projectId,\n found: true,\n row_number: row.row_number || row.rowNumber || row.__rowNumber || row.rowIndex,\n row,\n },\n}));" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1184, 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": [ 1616, 80 ], "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": [ 1824, 80 ], "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": [ 1392, 96 ], "id": "c281d09b-16da-450b-bcf8-0412f6658dcb", "name": "IF - Fila encontrada?" } ], "connections": { "Supabase - Leer proyecto": { "main": [ [ { "node": "Supabase - Leer links del proyecto", "type": "main", "index": 0 } ] ] }, "Supabase - Leer links del proyecto": { "main": [ [ { "node": "Code - Preparar fila Sheet", "type": "main", "index": 0 } ] ] }, "Code - Preparar fila Sheet": { "main": [ [ { "node": "Sheets - Upsert proyecto", "type": "main", "index": 0 } ] ] }, "Webhook": { "main": [ [ { "node": "Code - Preparar project_id", "type": "main", "index": 0 } ] ] }, "Code - Preparar project_id": { "main": [ [ { "node": "IF - Acción eliminar?", "type": "main", "index": 0 } ] ] }, "Sheets - Upsert proyecto": { "main": [ [ { "node": "Respond to Webhook", "type": "main", "index": 0 } ] ] }, "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 } ] ] } }, "settings": { "executionOrder": "v1", "binaryMode": "separate", "availableInMCP": false, "timeSavedMode": "fixed", "errorWorkflow": "puF4LUczoSz3hcek", "callerPolicy": "workflowsFromSameOwner" }, "staticData": null, "meta": { "templateCredsSetupCompleted": true }, "versionId": "b48ebc54-f0ab-47d7-a460-bc8dae472c82", "activeVersionId": "17638407-8c4f-4070-b040-a23f2750265e", "versionCounter": 121, "triggerCount": 1, "shared": [ { "updatedAt": "2026-05-27T18:36:03.452Z", "createdAt": "2026-05-27T18:36:03.452Z", "role": "workflow:owner", "workflowId": "d39DtIUTQzduqX0v", "projectId": "PJpTANzTXIFibWsW", "project": { "updatedAt": "2026-04-22T14:25:09.686Z", "createdAt": "2026-04-22T14:22:54.790Z", "id": "PJpTANzTXIFibWsW", "name": "Isaac Aracena ", "type": "personal", "icon": null, "description": null, "creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" } } ], "tags": [], "activeVersion": { "updatedAt": "2026-06-05T13:32:25.000Z", "createdAt": "2026-05-29T23:01:48.396Z", "versionId": "17638407-8c4f-4070-b040-a23f2750265e", "workflowId": "d39DtIUTQzduqX0v", "nodes": [ { "parameters": { "url": "=https://dbit.digitalcompass.agency/rest/v1/tablero_cdc_projects?id=eq.{{ $('Code - Preparar project_id').first().json.project_id }}&select=*", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Accept", "value": "application/json" }, { "name": "Content-Type", "value": "application/json" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 608, 112 ], "id": "5fdded5f-5a19-4f40-bc75-21ae92ac3df2", "name": "Supabase - Leer proyecto" }, { "parameters": { "url": "=https://dbit.digitalcompass.agency/rest/v1/tablero_cdc_project_links?project_id=eq.{{ $('Code - Preparar project_id').first().json.project_id }}&select=*&order=created_at.asc", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Accept", "value": "application/json" }, { "name": "Content-Type", "value": "application/json" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 816, 112 ], "id": "d2839f2d-af1f-49ab-bfd6-4c3b76f656ad", "name": "Supabase - Leer links del proyecto", "alwaysOutputData": true }, { "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];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1024, 112 ], "id": "1a9e76bd-20f8-481b-82c1-95d631e261ae", "name": "Code - Preparar fila Sheet" }, { "parameters": { "operation": "appendOrUpdate", "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" }, "columns": { "mappingMode": "defineBelow", "value": { "Año": "={{ $json[\"Año\"] }}", "Mes": "={{ $json[\"Mes\"] }}", "Tipo o nombre de proyecto": "={{ $json[\"Tipo o nombre de proyecto\"] }}", "Cliente": "={{ $json[\"Cliente\"] }}", "BU Solicita": "={{ $json[\"BU Solicita\"] }}", "Marca": "={{ $json[\"Marca\"] }}", "CM": "={{ $json[\"CM\"] }}", "Status": "={{ $json[\"Status\"] }}", "Comentarios": "={{ $json[\"Comentarios\"] }}", "Link de propuesta": "={{ $json[\"Link de propuesta\"] }}", "$ Interno Cargado": "={{ $json[\"$ Interno Cargado\"] }}", "Link del Brief": "={{ $json[\"Link del Brief\"] }}", "Link de artes finales": "={{ $json[\"Link de artes finales\"] }}", "Project ID": "={{ $json[\"Project ID\"] }}" }, "matchingColumns": [ "Project ID" ], "schema": [ { "id": "Año", "displayName": "Año", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Mes", "displayName": "Mes", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Tipo o nombre de proyecto", "displayName": "Tipo o nombre de proyecto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Cliente", "displayName": "Cliente", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "BU Solicita", "displayName": "BU Solicita", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Marca", "displayName": "Marca", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "CM", "displayName": "CM", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Status", "displayName": "Status", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Comentarios", "displayName": "Comentarios", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Link de propuesta", "displayName": "Link de propuesta", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "$ Interno Cargado", "displayName": "$ Interno Cargado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Link del Brief", "displayName": "Link del Brief", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Link de artes finales", "displayName": "Link de artes finales", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "Project ID", "displayName": "Project ID", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": { "locationDefine": { "values": { "headerRow": 3, "firstDataRow": 4 } } } }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 1232, 112 ], "id": "d66bf9b4-9694-4f43-8dd6-4e9efe4eece8", "name": "Sheets - Upsert proyecto", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "httpMethod": "POST", "path": "tablero-cdc-sync-proyecto", "responseMode": "responseNode", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ 208, 112 ], "id": "b5d0c07b-8b2d-437c-9475-530f64e84247", "name": "Webhook", "webhookId": "4f237f0a-7607-4a8b-9020-11bc75a6ae32" }, { "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];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 416, 112 ], "id": "017aafcb-5f7a-407d-8224-caf365186b78", "name": "Code - Preparar project_id" }, { "parameters": { "respondWith": "json", "responseBody": "={{\n {\n ok: true,\n message: 'Proyecto sincronizado con Google Sheet',\n project_id: $('Code - Preparar project_id').first().json.project_id\n }\n}}", "options": { "responseCode": 200 } }, "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ 1440, 112 ], "id": "425a3374-03dc-4424-b089-3d86efbd565e", "name": "Respond to Webhook" }, { "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 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 160, -208 ], "id": "16a4a31a-a288-45f5-86fd-be4d92a7a2c1", "name": "Sticky Note" } ], "connections": { "Supabase - Leer proyecto": { "main": [ [ { "node": "Supabase - Leer links del proyecto", "type": "main", "index": 0 } ] ] }, "Supabase - Leer links del proyecto": { "main": [ [ { "node": "Code - Preparar fila Sheet", "type": "main", "index": 0 } ] ] }, "Code - Preparar fila Sheet": { "main": [ [ { "node": "Sheets - Upsert proyecto", "type": "main", "index": 0 } ] ] }, "Webhook": { "main": [ [ { "node": "Code - Preparar project_id", "type": "main", "index": 0 } ] ] }, "Code - Preparar project_id": { "main": [ [ { "node": "Supabase - Leer proyecto", "type": "main", "index": 0 } ] ] }, "Sheets - Upsert proyecto": { "main": [ [ { "node": "Respond to Webhook", "type": "main", "index": 0 } ] ] } }, "authors": "Isaac Aracena", "name": "Tablero CDC - Sync Proyecto a Sheet WWEBHOOK", "description": "", "autosaved": true, "workflowPublishHistory": [ { "createdAt": "2026-05-29T23:03:35.864Z", "id": 788, "workflowId": "d39DtIUTQzduqX0v", "versionId": "17638407-8c4f-4070-b040-a23f2750265e", "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" } ] } }