Files
dev-tablero-cdc-sync-proyec…/dev-tablero-cdc-sync-proyecto-a-banco-fulgencio-ia.json
T

1476 lines
77 KiB
JSON

{
"updatedAt": "2026-06-16T19:33:25.439Z",
"createdAt": "2026-06-16T13:34:49.596Z",
"id": "aJH5uWZx3lUnNBbx",
"name": "DEV - Tablero CDC - Sync Proyecto a Banco Fulgencio IA",
"description": null,
"active": true,
"isArchived": false,
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "tablero-cdc-sync-fulgencio-ia",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
0,
0
],
"id": "b57d9cd2-3f7f-4e24-98e3-ef5ec131afc4",
"name": "Webhook",
"webhookId": "3b5cf1f9-a3e8-4511-82ce-188afabd5fe1"
},
{
"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": [
208,
0
],
"id": "2764c02c-8d8e-4edc-9dc3-48c0cd880b30",
"name": "Code - Preparar project_id"
},
{
"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": [
416,
0
],
"id": "8db634e3-f411-4701-9845-673b5db1121f",
"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": [
592,
0
],
"id": "6d151961-e0ae-4049-b3a2-88bae2494f8c",
"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 clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction getYear(value) {\n const date = value ? new Date(value) : 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 extractGoogleFileId(url) {\n const text = clean(url);\n\n if (!text) return '';\n\n const patterns = [\n /\\/d\\/([a-zA-Z0-9_-]+)/,\n /id=([a-zA-Z0-9_-]+)/,\n /\\/file\\/d\\/([a-zA-Z0-9_-]+)/,\n /\\/presentation\\/d\\/([a-zA-Z0-9_-]+)/,\n ];\n\n for (const pattern of patterns) {\n const match = text.match(pattern);\n if (match && match[1]) return match[1];\n }\n\n return '';\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 = links.filter(link =>\n String(link.link_type || '').toLowerCase() === 'proposal'\n);\n\nif (!proposalLinks.length) {\n return [\n {\n json: {\n ok: true,\n skipped: true,\n reason: 'El proyecto no tiene links de propuesta. No se escribe en Banco Fulgencio.',\n project_id: project.id,\n },\n },\n ];\n}\n\nreturn proposalLinks.map((link, index) => {\n const proposalUrl = clean(link.url);\n const fileId = extractGoogleFileId(proposalUrl);\n const syncKey = `${project.id}::${fileId || proposalUrl}`;\n\n return {\n json: {\n skipped: false,\n\n 'NOMBRE': project.title || '',\n 'TIPO DE ACCION': 'PENDIENTE CLASIFICAR',\n 'CLIENTE': project.client || '',\n 'MARCA': project.brand || '',\n 'PAIS': project.country || '',\n 'CANAL': '',\n 'AMBIENTE DE COMPRA (RE)': '',\n 'TÁCTICA PROMOCIONAL': '',\n 'APROBADA': '',\n 'ETIQUETAS': '',\n 'AÑO': getYear(project.created_at),\n 'Enlace a la propuesta': proposalUrl,\n 'Descripcion': project.description || project.title || '',\n 'file_id': fileId,\n 'nombre_archivo': '',\n 'mime_type': '',\n 'fuente_pais': 'Tablero CDC',\n 'confianza_pais': '',\n 'requiere_revision': '',\n 'procesado_ia': 'NO',\n 'ultima_actualizacion': new Date().toISOString(),\n 'motivos_revision': '',\n 'Enlaces a propuestas ejecutadas': '',\n\n 'tablero_sync_key': syncKey,\n 'tablero_project_id': project.id,\n 'tablero_origen': 'Tablero CDC',\n },\n };\n});"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
800,
0
],
"id": "ddfd7064-ad22-4a7c-aa8f-e400b132da83",
"name": "Code - Preparar filas Fulgencio"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "8b0c7244-a4a1-4fd9-b2eb-dc62b98fb7a3",
"leftValue": "={{ $json.skipped }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1008,
0
],
"id": "d6a1baa5-c035-4645-885e-d31b89a6ff2f",
"name": "IF - Tiene filas para escribir?"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"ok\": true,\n \"message\": \"Proyecto sincronizado con Banco Fulgencio\",\n \"source\": \"Tablero CDC\"\n}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
2432,
-112
],
"id": "875c95aa-c12e-40a1-b64c-4da83d0a1b06",
"name": "Respond to Webhook"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"ok\": true,\n \"skipped\": true,\n \"message\": \"Proyecto sin links de propuesta. No se escribió en Banco Fulgencio.\"\n}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1216,
96
],
"id": "3d315115-95b4-4b60-a174-2067bb5545d2",
"name": "Respond to Webhook1"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng",
"mode": "list",
"cachedResultName": "BANCO DE PROPUESTAS DE CDC PARA FULGENCIO FUMADO",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "propuestas",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"NOMBRE": "={{ $json[\"NOMBRE\"] }}",
"TIPO DE ACCION": "={{ $json[\"TIPO DE ACCION\"] }}",
"CLIENTE": "={{ $json[\"CLIENTE\"] }}",
"MARCA": "={{ $json[\"MARCA\"] }}",
"PAIS": "={{ $json[\"PAIS\"] }}",
"CANAL": "={{ $json[\"CANAL\"] }}",
"AMBIENTE DE COMPRA (RE)": "={{ $json[\"AMBIENTE DE COMPRA (RE)\"] }}",
"TÁCTICA PROMOCIONAL": "={{ $json[\"TÁCTICA PROMOCIONAL\"] }}",
"APROBADA": "={{ $json[\"APROBADA\"] }}",
"ETIQUETAS": "={{ $json[\"ETIQUETAS\"] }}",
"AÑO": "={{ $json[\"AÑO\"] }}",
"Enlace a la propuesta": "={{ $json[\"Enlace a la propuesta\"] }}",
"Descripcion": "={{ $json[\"Descripcion\"] }}",
"file_id": "={{ $json[\"file_id\"] }}",
"nombre_archivo": "={{ $json[\"nombre_archivo\"] }}",
"mime_type": "={{ $json[\"mime_type\"] }}",
"fuente_pais": "={{ $json[\"fuente_pais\"] }}",
"confianza_pais": "={{ $json[\"confianza_pais\"] }}",
"requiere_revision": "={{ $json[\"requiere_revision\"] }}",
"procesado_ia": "={{ $json[\"procesado_ia\"] }}",
"ultima_actualizacion": "={{ $json[\"ultima_actualizacion\"] }}",
"motivos_revision": "={{ $json[\"motivos_revision\"] }}",
"Enlaces a propuestas ejecutadas": "={{ $json[\"Enlaces a propuestas ejecutadas\"] }}",
"tablero_project_id": "={{ $json[\"tablero_project_id\"] }}",
"tablero_origen": "={{ $json[\"tablero_origen\"] }}",
"tablero_sync_key": "={{ $json[\"tablero_sync_key\"] }}"
},
"matchingColumns": [
"tablero_sync_key"
],
"schema": [
{
"id": "NOMBRE",
"displayName": "NOMBRE",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "TIPO DE ACCION",
"displayName": "TIPO DE ACCION",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "CLIENTE",
"displayName": "CLIENTE",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "MARCA",
"displayName": "MARCA",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "PAIS",
"displayName": "PAIS",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "CANAL",
"displayName": "CANAL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "AMBIENTE DE COMPRA (RE)",
"displayName": "AMBIENTE DE COMPRA (RE)",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "TÁCTICA PROMOCIONAL",
"displayName": "TÁCTICA PROMOCIONAL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "APROBADA",
"displayName": "APROBADA",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ETIQUETAS",
"displayName": "ETIQUETAS",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "AÑO",
"displayName": "AÑO",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Enlace a la propuesta",
"displayName": "Enlace a la propuesta",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Descripcion",
"displayName": "Descripcion",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "file_id",
"displayName": "file_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "nombre_archivo",
"displayName": "nombre_archivo",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "mime_type",
"displayName": "mime_type",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "fuente_pais",
"displayName": "fuente_pais",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "confianza_pais",
"displayName": "confianza_pais",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "requiere_revision",
"displayName": "requiere_revision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "procesado_ia",
"displayName": "procesado_ia",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ultima_actualizacion",
"displayName": "ultima_actualizacion",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "motivos_revision",
"displayName": "motivos_revision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Enlaces a propuestas ejecutadas",
"displayName": "Enlaces a propuestas ejecutadas",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "tablero_sync_key",
"displayName": "tablero_sync_key",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "tablero_project_id",
"displayName": "tablero_project_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "tablero_origen",
"displayName": "tablero_origen",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
2224,
-112
],
"id": "f285672d-8c71-4ebd-87ec-a8b83c4e1ce6",
"name": "Google Sheets - Upsert Banco Fulgencio",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
}
},
{
"parameters": {
"jsCode": "function clean(value) {\n return String(value ?? '').trim().replace(/\\s+/g, ' ');\n}\n\nconst item = $input.first().json || {};\n\nconst nombre = clean(item.NOMBRE);\nconst cliente = clean(item.CLIENTE);\nconst marca = clean(item.MARCA);\nconst pais = clean(item.PAIS);\nconst enlace = clean(item['Enlace a la propuesta']);\n\nreturn [\n {\n json: {\n ...item,\n\n // Campos compatibles con el flujo viejo de Fulgencio\n nombre_archivo: nombre || item.nombre_archivo || '',\n nombre_limpio: nombre || item.nombre_archivo || '',\n pais: pais || '',\n cliente_tablero: cliente || '',\n marca_tablero: marca || '',\n link_drive: enlace || '',\n mime_type: item.mime_type || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1264,
-112
],
"id": "bc209351-3c89-49fe-b891-3fd8d1fc5c4d",
"name": "Code - Preparar entrada IA Tablero"
},
{
"parameters": {
"text": "==Nombre del proyecto/propuesta: {{$json.nombre_limpio}}\nCliente indicado en Tablero CDC: {{$json.cliente_tablero}}\nMarca indicada en Tablero CDC: {{$json.marca_tablero}}\nPaís indicado en Tablero CDC: {{$json.pais}}\nTipo de archivo: {{$json.mime_type}}\nLink de propuesta: {{$json.link_drive}}",
"attributes": {
"attributes": [
{
"name": "nombre_propuesta",
"description": "Nombre limpio o comercial de la propuesta."
},
{
"name": "tipo_accion",
"description": "Tipo de acción o formato de propuesta. Ejemplos: campaña, evento, mueble, exhibidor, diseño gráfico, activación PDV, material POP, uniforme, promoción, stand. Si no se puede determinar, usar PENDIENTE."
},
{
"name": "canal",
"description": "Canal o lugar donde aplica la propuesta. Ejemplos: moderno, tradicional, online, PDV, no aplica. Si no se puede determinar, usar PENDIENTE."
},
{
"name": "tags",
"description": "Palabras clave separadas por comas para facilitar búsqueda. Ejemplos: campaña, corporativo, exhibidor, punto de venta, juegos, uniforme, material POP."
},
{
"name": "descripcion",
"description": "Párrafo profesional de 35 a 70 palabras. Debe iniciar preferiblemente con “La presentación muestra...”, “La pieza muestra...” o “La propuesta corresponde a...”. Debe mencionar cliente/marca, tipo de acción, concepto o tema principal y objetivo. No inventar detalles."
},
{
"name": "tactica_promocional",
"description": "Táctica promocional principal. Ejemplos: DEGUSTACIÓN, RULETA, SAMPLING - MUESTREO, PLINKO, WHATSAPP, LANDING PAGE, POP, PHOTOBOOTH, CANJE, SORTEO, PREMIOS INSTANTÁNEOS, JUEGO DIGITAL, TRIVIA, EXHIBICIÓN, IMPULSO o PENDIENTE."
}
]
},
"options": {
"systemPromptTemplate": "Analiza la siguiente propuesta usando la información disponible desde Tablero CDC.\n\nIMPORTANTE:\n- El país viene desde Tablero CDC. No lo cambies.\n- El cliente y la marca vienen desde Tablero CDC. Úsalos como referencia principal.\n- No determines APROBADA. Ese campo se guardará como PENDIENTE DE APROBACION.\n- No determines AMBIENTE DE COMPRA (RE). Ese campo se guardará como PENDIENTE.\n- Si no puedes determinar un campo con claridad, usa PENDIENTE.\n- No inventes detalles que no estén claros a partir del nombre, cliente, marca, país o link.\n- Extrae metadata para alimentar el banco de propuestas de Fulgencio Fumado.\n\nDevuelve estos campos:\n- nombre_propuesta\n- tipo_accion\n- canal\n- tags\n- descripcion\n- tactica_promocional\n\nReglas:\n1. tipo_accion puede inferirse del nombre y contexto. Ejemplos: campaña, evento, mueble, exhibidor, activación PDV, diseño gráfico, promoción, uniforme, stand, material POP.\n2. canal debe ser MODERNO, TRADICIONAL, ONLINE, PDV, NO APLICA o PENDIENTE cuando sea posible.\n3. tags debe ser una lista corta de palabras clave separadas por comas.\n4. descripcion debe ser un párrafo profesional de 35 a 70 palabras, similar al estilo usado en el banco actual.\n5. tactica_promocional debe ser una opción clara como DEGUSTACIÓN, RULETA, SAMPLING - MUESTREO, PLINKO, WHATSAPP, LANDING PAGE, POP, PHOTOBOOTH, CANJE, SORTEO, PREMIOS INSTANTÁNEOS, JUEGO DIGITAL, TRIVIA, EXHIBICIÓN, IMPULSO, o PENDIENTE."
}
},
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"typeVersion": 1.2,
"position": [
1664,
-112
],
"id": "28887e16-b735-4e11-8fe2-4e3270876c85",
"name": "Gemini - Extraer metadata Tablero"
},
{
"parameters": {
"modelName": "models/gemini-3.1-pro-preview",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1,
"position": [
1680,
128
],
"id": "4b4e6b14-50fc-4700-ad33-dcfc8a489770",
"name": "Gemini - Extraer metadata1",
"credentials": {
"googlePalmApi": {
"id": "jvsXYwL6IOoY2DBU",
"name": "Isaac - Gemini Api Pago"
}
}
},
{
"parameters": {
"jsCode": "const original = $('Code - Preparar entrada IA Tablero').item.json || {};\nconst incoming = $input.first().json || {};\nconst ai = incoming.output || incoming;\n\nfunction clean(value) {\n if (Array.isArray(value)) return value.map(clean).filter(Boolean).join(', ');\n return String(value ?? '').trim().replace(/\\s+/g, ' ');\n}\n\nfunction removeAccents(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction upper(value) {\n return removeAccents(value).toUpperCase();\n}\n\nfunction splitMulti(value) {\n return clean(value)\n .split(',')\n .map(v => upper(v))\n .map(v => v.trim())\n .filter(Boolean);\n}\n\nfunction unique(values) {\n return [...new Set(values.filter(Boolean))];\n}\n\nfunction joinMulti(values) {\n return unique(values).join(', ');\n}\n\nfunction normalizeTipoAccion(value) {\n const v = upper(value);\n\n const synonyms = {\n 'ACTIVACION PDV': 'ACTIVACION EN PDV',\n 'ACTIVACION EN PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'ACTIVACION PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'PDV': 'ACTIVACION EN PDV',\n 'DISEÑO GRAFICO': 'DISENO GRAFICO',\n 'DISEÑO GRÁFICO': 'DISENO GRAFICO',\n 'DISENO GRAFICO': 'DISENO GRAFICO',\n 'CAMPAÑA': 'CAMPAÑA',\n 'CAMPANA': 'CAMPAÑA',\n 'PROMOCION': 'PROMOCION',\n 'PROMOCIÓN': 'PROMOCION',\n 'MUEBLE': 'MUEBLE',\n 'EXHIBIDOR': 'MUEBLE',\n 'STAND': 'STAND',\n 'EVENTO': 'EVENTO',\n 'UNIFORME': 'UNIFORME',\n 'MATERIAL POP': 'DISENO GRAFICO',\n 'POP': 'DISENO GRAFICO'\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeCanal(value) {\n const v = upper(value);\n\n const synonyms = {\n 'PUNTO DE VENTA': 'PDV',\n 'PDV': 'PDV',\n 'CANAL MODERNO': 'MODERNO',\n 'MODERNO': 'MODERNO',\n 'CANAL TRADICIONAL': 'TRADICIONAL',\n 'TRADICIONAL': 'TRADICIONAL',\n 'DIGITAL': 'ONLINE',\n 'ONLINE': 'ONLINE',\n 'NO APLICA': 'NO APLICA',\n 'N/A': 'NO APLICA'\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeTactica(value) {\n const v = upper(value);\n\n const synonyms = {\n 'SAMPLING': 'SAMPLING - MUESTREO',\n 'MUESTREO': 'SAMPLING - MUESTREO',\n 'DEGUSTACION': 'DEGUSTACIÓN',\n 'DEGUSTACIÓN': 'DEGUSTACIÓN',\n 'RULETA DIGITAL': 'RULETA',\n 'RULETA': 'RULETA',\n 'PLINKO': 'PLINKO',\n 'WHATSAPP': 'WHATSAPP',\n 'LANDING': 'LANDING PAGE',\n 'LANDING PAGE': 'LANDING PAGE',\n 'MATERIAL POP': 'POP',\n 'POP': 'POP',\n 'JUEGO DIGITAL': 'JUEGO DIGITAL',\n 'JUEGOS DIGITALES': 'JUEGO DIGITAL',\n 'EXHIBICION': 'EXHIBICIÓN',\n 'EXHIBICIÓN': 'EXHIBICIÓN',\n 'IMPULSO': 'IMPULSO'\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeEtiqueta(value) {\n const v = upper(value);\n\n const synonyms = {\n 'ACTIVACION': 'ACTIVACION',\n 'ACTIVACIÓN': 'ACTIVACION',\n 'EXHIBICION': 'EXHIBICION',\n 'EXHIBICIÓN': 'EXHIBICION',\n 'EXHIBIDOR': 'EXHIBIDOR',\n 'DEGUSTACION': 'DEGUSTACION',\n 'DEGUSTACIÓN': 'DEGUSTACION',\n 'PROMOCION': 'PROMOCIÓN',\n 'PROMOCIÓN': 'PROMOCIÓN',\n 'DISENO': 'DISEÑO',\n 'DISEÑO': 'DISEÑO',\n 'CAMPANA': 'CAMPAÑA',\n 'CAMPAÑA': 'CAMPAÑA',\n 'JUEGO DIGITAL': 'JUEGOS DIGITALES',\n 'JUEGOS DIGITALES': 'JUEGOS DIGITALES',\n 'PUNTO DE VENTA': 'PUNTO DE VENTA',\n 'PDV': 'PUNTO DE VENTA',\n 'F1': 'JUEGOS',\n 'SIMULADOR': 'JUEGOS DIGITALES',\n 'EXPERIENCIA': 'ACTIVIDADES',\n 'CORPORATIVO': 'CORPORATIVO',\n 'UNIFORME': 'UNIFORMES',\n 'UNIFORMES': 'UNIFORMES',\n 'POP': 'PUNTO DE VENTA',\n 'MATERIAL POP': 'PUNTO DE VENTA'\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeOne(value, allowedList, fallback = 'PENDIENTE', normalizer = upper) {\n const raw = clean(value);\n if (!raw) return fallback;\n\n const mapped = normalizer(raw);\n return allowedList.includes(mapped) ? mapped : fallback;\n}\n\nfunction normalizeMulti(value, allowedList, fallback = 'PENDIENTE', unknownValue = 'OTRO', normalizer = upper) {\n const parts = splitMulti(value);\n if (!parts.length) return fallback;\n\n const result = [];\n\n for (const part of parts) {\n const mapped = normalizer(part);\n const expanded = String(mapped).split(',').map(v => v.trim()).filter(Boolean);\n\n for (const val of expanded) {\n if (allowedList.includes(val)) {\n result.push(val);\n } else {\n result.push(unknownValue);\n }\n }\n }\n\n const finalValues = unique(result);\n if (!finalValues.length) return fallback;\n\n return joinMulti(finalValues);\n}\n\nconst ALLOWED_TIPO_ACCION = [\n 'ACTIVACION EN PDV',\n 'IMPULSO DE VENTA',\n 'ACTIVACION FUERA DE PDV',\n 'EVENTO',\n 'KICK OFF',\n 'FERIA',\n 'STAND',\n 'MUEBLE',\n 'DISENO GRAFICO',\n 'DIGITAL',\n 'PUBLICIDAD',\n 'TUTORIAL',\n 'INFORMATIVA',\n 'LOGOTIPO',\n 'PROMOCION',\n 'UNIFORME',\n 'ACTIVACIÓN',\n 'INAUGURACIÓN',\n 'PARQUE',\n 'TROFEO',\n 'ANAQUEL',\n 'FOOD TRUCK',\n 'EMPAQUES',\n 'CAMPAÑA',\n 'QUICK COUNTER',\n 'DISEÑO',\n 'PPT',\n 'PENDIENTE',\n 'OTRO'\n];\n\nconst ALLOWED_CANAL = [\n 'MODERNO',\n 'TRADICIONAL',\n 'ONLINE',\n 'NO APLICA',\n 'PDV',\n 'PENDIENTE'\n];\n\nconst ALLOWED_TACTICA = [\n 'DEGUSTACIÓN',\n 'RULETA',\n 'SAMPLING - MUESTREO',\n 'PLINKO',\n 'WHATSAPP',\n 'LANDING PAGE',\n 'POP',\n 'PHOTOBOOTH',\n 'CANJE',\n 'SORTEO',\n 'PREMIOS INSTANTÁNEOS',\n 'JUEGO DIGITAL',\n 'TRIVIA',\n 'EXHIBICIÓN',\n 'IMPULSO',\n 'PENDIENTE',\n 'OTRO'\n];\n\nconst ALLOWED_ETIQUETAS = [\n 'ACTIVACION',\n 'ACTIVIDADES',\n 'BANDEJA',\n 'BTS',\n 'CORPORATIVO',\n 'DEGUSTACION',\n 'EXHIBICION',\n 'EXHIBIDOR',\n 'FUERZA DE VENTAS',\n 'JUEGOS',\n 'PREMIOS',\n 'PUNTO DE VENTA',\n 'REFERENCIAS',\n 'REUNIÓN',\n 'RULETA',\n 'SAMPLING',\n 'HALLOWEEN',\n 'LUCES NEON',\n 'INFORMATIVA',\n 'CARRITO DEGUSTACIÓN',\n 'RED SOCIAL/INFLUENCER',\n 'UNIFORMES',\n 'REGALIAS',\n 'STAND',\n 'CAPACITACIÓN',\n 'NAVIDAD',\n 'DÍA DE LAS MADRES',\n 'INDEPENDENCIA',\n 'LOGOTIPO',\n 'QUICK COUNTER',\n 'PPT',\n 'TEMPLATES',\n 'DISEÑO',\n 'PROMOCIÓN',\n 'MODERNO',\n 'MAYORISTA',\n 'PULPERÍAS',\n 'JUEGOS DIGITALES',\n 'WHATSAPP',\n 'CAMPAÑA',\n 'CONCURSO',\n 'REGALOS CORPORATIVOS',\n 'PENDIENTE',\n 'OTRO'\n];\n\nlet nombrePropuesta = clean(ai.nombre_propuesta || original.NOMBRE || original.nombre_limpio || '');\nif (!nombrePropuesta) nombrePropuesta = 'PENDIENTE';\n\nlet tipoAccion = normalizeMulti(\n ai.tipo_accion,\n ALLOWED_TIPO_ACCION,\n 'PENDIENTE',\n 'OTRO',\n normalizeTipoAccion\n);\n\nconst canal = normalizeOne(\n ai.canal,\n ALLOWED_CANAL,\n 'PENDIENTE',\n normalizeCanal\n);\n\nconst tacticaPromocional = normalizeMulti(\n ai.tactica_promocional,\n ALLOWED_TACTICA,\n 'PENDIENTE',\n 'OTRO',\n normalizeTactica\n);\n\nlet etiquetas = normalizeMulti(\n ai.tags,\n ALLOWED_ETIQUETAS,\n 'PENDIENTE',\n 'OTRO',\n normalizeEtiqueta\n);\n\nif (etiquetas.includes('OTRO') && etiquetas !== 'OTRO') {\n etiquetas = etiquetas\n .split(',')\n .map(v => v.trim())\n .filter(v => v !== 'OTRO')\n .join(', ');\n}\n\nconst ambienteCompra = 'PENDIENTE';\nconst aprobada = 'PENDIENTE DE APROBACION';\n\nlet descripcion = clean(ai.descripcion);\nif (!descripcion) {\n descripcion = clean(original.Descripcion || original.NOMBRE || '');\n}\n\nlet requiereRevision = false;\nconst revisionReasons = [];\n\nif (tipoAccion.includes('OTRO') || tipoAccion === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('tipo_accion_revisar');\n}\n\nif (canal === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('canal_revisar');\n}\n\nif (tacticaPromocional.includes('OTRO') || tacticaPromocional === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('tactica_revisar');\n}\n\nif (etiquetas.includes('OTRO') || etiquetas === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('etiquetas_revisar');\n}\n\nreturn [\n {\n json: {\n NOMBRE: nombrePropuesta,\n 'TIPO DE ACCION': tipoAccion,\n CLIENTE: original.CLIENTE || '',\n MARCA: original.MARCA || '',\n PAIS: original.PAIS || '',\n CANAL: canal,\n 'AMBIENTE DE COMPRA (RE)': ambienteCompra,\n 'TÁCTICA PROMOCIONAL': tacticaPromocional,\n APROBADA: aprobada,\n ETIQUETAS: etiquetas,\n 'AÑO': original['AÑO'] || '',\n 'Enlace a la propuesta': original['Enlace a la propuesta'] || '',\n Descripcion: descripcion,\n\n file_id: original.file_id || '',\n nombre_archivo: original.nombre_archivo || original.NOMBRE || '',\n mime_type: original.mime_type || '',\n fuente_pais: original.fuente_pais || 'Tablero CDC',\n confianza_pais: original.confianza_pais || '',\n requiere_revision: requiereRevision,\n motivos_revision: revisionReasons.join(', '),\n procesado_ia: true,\n ultima_actualizacion: new Date().toISOString(),\n 'Enlaces a propuestas ejecutadas': original['Enlaces a propuestas ejecutadas'] || '',\n\n tablero_sync_key: original.tablero_sync_key || '',\n tablero_project_id: original.tablero_project_id || '',\n tablero_origen: original.tablero_origen || 'Tablero CDC',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2016,
-112
],
"id": "a183d0af-b313-49ce-9f00-e619a4d576d2",
"name": "Code - Normalizar respuesta IA Tablero"
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Code - Preparar project_id",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar project_id": {
"main": [
[
{
"node": "Supabase - Leer proyecto",
"type": "main",
"index": 0
}
]
]
},
"Supabase - Leer proyecto": {
"main": [
[
{
"node": "Supabase - Leer links del proyecto",
"type": "main",
"index": 0
}
]
]
},
"Supabase - Leer links del proyecto": {
"main": [
[
{
"node": "Code - Preparar filas Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar filas Fulgencio": {
"main": [
[
{
"node": "IF - Tiene filas para escribir?",
"type": "main",
"index": 0
}
]
]
},
"IF - Tiene filas para escribir?": {
"main": [
[
{
"node": "Code - Preparar entrada IA Tablero",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook1",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets - Upsert Banco Fulgencio": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar entrada IA Tablero": {
"main": [
[
{
"node": "Gemini - Extraer metadata Tablero",
"type": "main",
"index": 0
}
]
]
},
"Gemini - Extraer metadata1": {
"ai_languageModel": [
[
{
"node": "Gemini - Extraer metadata Tablero",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Gemini - Extraer metadata Tablero": {
"main": [
[
{
"node": "Code - Normalizar respuesta IA Tablero",
"type": "main",
"index": 0
}
]
]
},
"Code - Normalizar respuesta IA Tablero": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"binaryMode": "separate",
"availableInMCP": false
},
"staticData": null,
"meta": {
"templateCredsSetupCompleted": true
},
"versionId": "cdbd1088-4537-4fdb-8666-2166d8001680",
"activeVersionId": "cdbd1088-4537-4fdb-8666-2166d8001680",
"versionCounter": 46,
"triggerCount": 1,
"shared": [
{
"updatedAt": "2026-06-16T13:34:49.599Z",
"createdAt": "2026-06-16T13:34:49.599Z",
"role": "workflow:owner",
"workflowId": "aJH5uWZx3lUnNBbx",
"projectId": "PJpTANzTXIFibWsW",
"project": {
"updatedAt": "2026-04-22T14:25:09.686Z",
"createdAt": "2026-04-22T14:22:54.790Z",
"id": "PJpTANzTXIFibWsW",
"name": "Isaac Aracena <iaracena@gomezleemarketing.com>",
"type": "personal",
"icon": null,
"description": null,
"creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
}
}
],
"tags": [],
"activeVersion": {
"updatedAt": "2026-06-16T19:33:28.000Z",
"createdAt": "2026-06-16T19:33:25.441Z",
"versionId": "cdbd1088-4537-4fdb-8666-2166d8001680",
"workflowId": "aJH5uWZx3lUnNBbx",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "tablero-cdc-sync-fulgencio-ia",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
0,
0
],
"id": "b57d9cd2-3f7f-4e24-98e3-ef5ec131afc4",
"name": "Webhook",
"webhookId": "3b5cf1f9-a3e8-4511-82ce-188afabd5fe1"
},
{
"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": [
208,
0
],
"id": "2764c02c-8d8e-4edc-9dc3-48c0cd880b30",
"name": "Code - Preparar project_id"
},
{
"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": [
416,
0
],
"id": "8db634e3-f411-4701-9845-673b5db1121f",
"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": [
592,
0
],
"id": "6d151961-e0ae-4049-b3a2-88bae2494f8c",
"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 clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction getYear(value) {\n const date = value ? new Date(value) : 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 extractGoogleFileId(url) {\n const text = clean(url);\n\n if (!text) return '';\n\n const patterns = [\n /\\/d\\/([a-zA-Z0-9_-]+)/,\n /id=([a-zA-Z0-9_-]+)/,\n /\\/file\\/d\\/([a-zA-Z0-9_-]+)/,\n /\\/presentation\\/d\\/([a-zA-Z0-9_-]+)/,\n ];\n\n for (const pattern of patterns) {\n const match = text.match(pattern);\n if (match && match[1]) return match[1];\n }\n\n return '';\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 = links.filter(link =>\n String(link.link_type || '').toLowerCase() === 'proposal'\n);\n\nif (!proposalLinks.length) {\n return [\n {\n json: {\n ok: true,\n skipped: true,\n reason: 'El proyecto no tiene links de propuesta. No se escribe en Banco Fulgencio.',\n project_id: project.id,\n },\n },\n ];\n}\n\nreturn proposalLinks.map((link, index) => {\n const proposalUrl = clean(link.url);\n const fileId = extractGoogleFileId(proposalUrl);\n const syncKey = `${project.id}::${fileId || proposalUrl}`;\n\n return {\n json: {\n skipped: false,\n\n 'NOMBRE': project.title || '',\n 'TIPO DE ACCION': 'PENDIENTE CLASIFICAR',\n 'CLIENTE': project.client || '',\n 'MARCA': project.brand || '',\n 'PAIS': project.country || '',\n 'CANAL': '',\n 'AMBIENTE DE COMPRA (RE)': '',\n 'TÁCTICA PROMOCIONAL': '',\n 'APROBADA': '',\n 'ETIQUETAS': '',\n 'AÑO': getYear(project.created_at),\n 'Enlace a la propuesta': proposalUrl,\n 'Descripcion': project.description || project.title || '',\n 'file_id': fileId,\n 'nombre_archivo': '',\n 'mime_type': '',\n 'fuente_pais': 'Tablero CDC',\n 'confianza_pais': '',\n 'requiere_revision': '',\n 'procesado_ia': 'NO',\n 'ultima_actualizacion': new Date().toISOString(),\n 'motivos_revision': '',\n 'Enlaces a propuestas ejecutadas': '',\n\n 'tablero_sync_key': syncKey,\n 'tablero_project_id': project.id,\n 'tablero_origen': 'Tablero CDC',\n },\n };\n});"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
800,
0
],
"id": "ddfd7064-ad22-4a7c-aa8f-e400b132da83",
"name": "Code - Preparar filas Fulgencio"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "8b0c7244-a4a1-4fd9-b2eb-dc62b98fb7a3",
"leftValue": "={{ $json.skipped }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1008,
0
],
"id": "d6a1baa5-c035-4645-885e-d31b89a6ff2f",
"name": "IF - Tiene filas para escribir?"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"ok\": true,\n \"message\": \"Proyecto sincronizado con Banco Fulgencio\",\n \"source\": \"Tablero CDC\"\n}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
2432,
-112
],
"id": "875c95aa-c12e-40a1-b64c-4da83d0a1b06",
"name": "Respond to Webhook"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"ok\": true,\n \"skipped\": true,\n \"message\": \"Proyecto sin links de propuesta. No se escribió en Banco Fulgencio.\"\n}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1216,
96
],
"id": "3d315115-95b4-4b60-a174-2067bb5545d2",
"name": "Respond to Webhook1"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng",
"mode": "list",
"cachedResultName": "BANCO DE PROPUESTAS DE CDC PARA FULGENCIO FUMADO",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "propuestas",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"NOMBRE": "={{ $json[\"NOMBRE\"] }}",
"TIPO DE ACCION": "={{ $json[\"TIPO DE ACCION\"] }}",
"CLIENTE": "={{ $json[\"CLIENTE\"] }}",
"MARCA": "={{ $json[\"MARCA\"] }}",
"PAIS": "={{ $json[\"PAIS\"] }}",
"CANAL": "={{ $json[\"CANAL\"] }}",
"AMBIENTE DE COMPRA (RE)": "={{ $json[\"AMBIENTE DE COMPRA (RE)\"] }}",
"TÁCTICA PROMOCIONAL": "={{ $json[\"TÁCTICA PROMOCIONAL\"] }}",
"APROBADA": "={{ $json[\"APROBADA\"] }}",
"ETIQUETAS": "={{ $json[\"ETIQUETAS\"] }}",
"AÑO": "={{ $json[\"AÑO\"] }}",
"Enlace a la propuesta": "={{ $json[\"Enlace a la propuesta\"] }}",
"Descripcion": "={{ $json[\"Descripcion\"] }}",
"file_id": "={{ $json[\"file_id\"] }}",
"nombre_archivo": "={{ $json[\"nombre_archivo\"] }}",
"mime_type": "={{ $json[\"mime_type\"] }}",
"fuente_pais": "={{ $json[\"fuente_pais\"] }}",
"confianza_pais": "={{ $json[\"confianza_pais\"] }}",
"requiere_revision": "={{ $json[\"requiere_revision\"] }}",
"procesado_ia": "={{ $json[\"procesado_ia\"] }}",
"ultima_actualizacion": "={{ $json[\"ultima_actualizacion\"] }}",
"motivos_revision": "={{ $json[\"motivos_revision\"] }}",
"Enlaces a propuestas ejecutadas": "={{ $json[\"Enlaces a propuestas ejecutadas\"] }}",
"tablero_project_id": "={{ $json[\"tablero_project_id\"] }}",
"tablero_origen": "={{ $json[\"tablero_origen\"] }}",
"tablero_sync_key": "={{ $json[\"tablero_sync_key\"] }}"
},
"matchingColumns": [
"tablero_sync_key"
],
"schema": [
{
"id": "NOMBRE",
"displayName": "NOMBRE",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "TIPO DE ACCION",
"displayName": "TIPO DE ACCION",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "CLIENTE",
"displayName": "CLIENTE",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "MARCA",
"displayName": "MARCA",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "PAIS",
"displayName": "PAIS",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "CANAL",
"displayName": "CANAL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "AMBIENTE DE COMPRA (RE)",
"displayName": "AMBIENTE DE COMPRA (RE)",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "TÁCTICA PROMOCIONAL",
"displayName": "TÁCTICA PROMOCIONAL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "APROBADA",
"displayName": "APROBADA",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ETIQUETAS",
"displayName": "ETIQUETAS",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "AÑO",
"displayName": "AÑO",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Enlace a la propuesta",
"displayName": "Enlace a la propuesta",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Descripcion",
"displayName": "Descripcion",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "file_id",
"displayName": "file_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "nombre_archivo",
"displayName": "nombre_archivo",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "mime_type",
"displayName": "mime_type",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "fuente_pais",
"displayName": "fuente_pais",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "confianza_pais",
"displayName": "confianza_pais",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "requiere_revision",
"displayName": "requiere_revision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "procesado_ia",
"displayName": "procesado_ia",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ultima_actualizacion",
"displayName": "ultima_actualizacion",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "motivos_revision",
"displayName": "motivos_revision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Enlaces a propuestas ejecutadas",
"displayName": "Enlaces a propuestas ejecutadas",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "tablero_sync_key",
"displayName": "tablero_sync_key",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "tablero_project_id",
"displayName": "tablero_project_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "tablero_origen",
"displayName": "tablero_origen",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
2224,
-112
],
"id": "f285672d-8c71-4ebd-87ec-a8b83c4e1ce6",
"name": "Google Sheets - Upsert Banco Fulgencio",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
}
},
{
"parameters": {
"jsCode": "function clean(value) {\n return String(value ?? '').trim().replace(/\\s+/g, ' ');\n}\n\nconst item = $input.first().json || {};\n\nconst nombre = clean(item.NOMBRE);\nconst cliente = clean(item.CLIENTE);\nconst marca = clean(item.MARCA);\nconst pais = clean(item.PAIS);\nconst enlace = clean(item['Enlace a la propuesta']);\n\nreturn [\n {\n json: {\n ...item,\n\n // Campos compatibles con el flujo viejo de Fulgencio\n nombre_archivo: nombre || item.nombre_archivo || '',\n nombre_limpio: nombre || item.nombre_archivo || '',\n pais: pais || '',\n cliente_tablero: cliente || '',\n marca_tablero: marca || '',\n link_drive: enlace || '',\n mime_type: item.mime_type || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1264,
-112
],
"id": "bc209351-3c89-49fe-b891-3fd8d1fc5c4d",
"name": "Code - Preparar entrada IA Tablero"
},
{
"parameters": {
"text": "==Nombre del proyecto/propuesta: {{$json.nombre_limpio}}\nCliente indicado en Tablero CDC: {{$json.cliente_tablero}}\nMarca indicada en Tablero CDC: {{$json.marca_tablero}}\nPaís indicado en Tablero CDC: {{$json.pais}}\nTipo de archivo: {{$json.mime_type}}\nLink de propuesta: {{$json.link_drive}}",
"attributes": {
"attributes": [
{
"name": "nombre_propuesta",
"description": "Nombre limpio o comercial de la propuesta."
},
{
"name": "tipo_accion",
"description": "Tipo de acción o formato de propuesta. Ejemplos: campaña, evento, mueble, exhibidor, diseño gráfico, activación PDV, material POP, uniforme, promoción, stand. Si no se puede determinar, usar PENDIENTE."
},
{
"name": "canal",
"description": "Canal o lugar donde aplica la propuesta. Ejemplos: moderno, tradicional, online, PDV, no aplica. Si no se puede determinar, usar PENDIENTE."
},
{
"name": "tags",
"description": "Palabras clave separadas por comas para facilitar búsqueda. Ejemplos: campaña, corporativo, exhibidor, punto de venta, juegos, uniforme, material POP."
},
{
"name": "descripcion",
"description": "Párrafo profesional de 35 a 70 palabras. Debe iniciar preferiblemente con “La presentación muestra...”, “La pieza muestra...” o “La propuesta corresponde a...”. Debe mencionar cliente/marca, tipo de acción, concepto o tema principal y objetivo. No inventar detalles."
},
{
"name": "tactica_promocional",
"description": "Táctica promocional principal. Ejemplos: DEGUSTACIÓN, RULETA, SAMPLING - MUESTREO, PLINKO, WHATSAPP, LANDING PAGE, POP, PHOTOBOOTH, CANJE, SORTEO, PREMIOS INSTANTÁNEOS, JUEGO DIGITAL, TRIVIA, EXHIBICIÓN, IMPULSO o PENDIENTE."
}
]
},
"options": {
"systemPromptTemplate": "Analiza la siguiente propuesta usando la información disponible desde Tablero CDC.\n\nIMPORTANTE:\n- El país viene desde Tablero CDC. No lo cambies.\n- El cliente y la marca vienen desde Tablero CDC. Úsalos como referencia principal.\n- No determines APROBADA. Ese campo se guardará como PENDIENTE DE APROBACION.\n- No determines AMBIENTE DE COMPRA (RE). Ese campo se guardará como PENDIENTE.\n- Si no puedes determinar un campo con claridad, usa PENDIENTE.\n- No inventes detalles que no estén claros a partir del nombre, cliente, marca, país o link.\n- Extrae metadata para alimentar el banco de propuestas de Fulgencio Fumado.\n\nDevuelve estos campos:\n- nombre_propuesta\n- tipo_accion\n- canal\n- tags\n- descripcion\n- tactica_promocional\n\nReglas:\n1. tipo_accion puede inferirse del nombre y contexto. Ejemplos: campaña, evento, mueble, exhibidor, activación PDV, diseño gráfico, promoción, uniforme, stand, material POP.\n2. canal debe ser MODERNO, TRADICIONAL, ONLINE, PDV, NO APLICA o PENDIENTE cuando sea posible.\n3. tags debe ser una lista corta de palabras clave separadas por comas.\n4. descripcion debe ser un párrafo profesional de 35 a 70 palabras, similar al estilo usado en el banco actual.\n5. tactica_promocional debe ser una opción clara como DEGUSTACIÓN, RULETA, SAMPLING - MUESTREO, PLINKO, WHATSAPP, LANDING PAGE, POP, PHOTOBOOTH, CANJE, SORTEO, PREMIOS INSTANTÁNEOS, JUEGO DIGITAL, TRIVIA, EXHIBICIÓN, IMPULSO, o PENDIENTE."
}
},
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"typeVersion": 1.2,
"position": [
1664,
-112
],
"id": "28887e16-b735-4e11-8fe2-4e3270876c85",
"name": "Gemini - Extraer metadata Tablero"
},
{
"parameters": {
"modelName": "models/gemini-3.1-pro-preview",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1,
"position": [
1680,
128
],
"id": "4b4e6b14-50fc-4700-ad33-dcfc8a489770",
"name": "Gemini - Extraer metadata1",
"credentials": {
"googlePalmApi": {
"id": "jvsXYwL6IOoY2DBU",
"name": "Isaac - Gemini Api Pago"
}
}
},
{
"parameters": {
"jsCode": "const original = $('Code - Preparar entrada IA Tablero').item.json || {};\nconst incoming = $input.first().json || {};\nconst ai = incoming.output || incoming;\n\nfunction clean(value) {\n if (Array.isArray(value)) return value.map(clean).filter(Boolean).join(', ');\n return String(value ?? '').trim().replace(/\\s+/g, ' ');\n}\n\nfunction removeAccents(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction upper(value) {\n return removeAccents(value).toUpperCase();\n}\n\nfunction splitMulti(value) {\n return clean(value)\n .split(',')\n .map(v => upper(v))\n .map(v => v.trim())\n .filter(Boolean);\n}\n\nfunction unique(values) {\n return [...new Set(values.filter(Boolean))];\n}\n\nfunction joinMulti(values) {\n return unique(values).join(', ');\n}\n\nfunction normalizeTipoAccion(value) {\n const v = upper(value);\n\n const synonyms = {\n 'ACTIVACION PDV': 'ACTIVACION EN PDV',\n 'ACTIVACION EN PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'ACTIVACION PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'PDV': 'ACTIVACION EN PDV',\n 'DISEÑO GRAFICO': 'DISENO GRAFICO',\n 'DISEÑO GRÁFICO': 'DISENO GRAFICO',\n 'DISENO GRAFICO': 'DISENO GRAFICO',\n 'CAMPAÑA': 'CAMPAÑA',\n 'CAMPANA': 'CAMPAÑA',\n 'PROMOCION': 'PROMOCION',\n 'PROMOCIÓN': 'PROMOCION',\n 'MUEBLE': 'MUEBLE',\n 'EXHIBIDOR': 'MUEBLE',\n 'STAND': 'STAND',\n 'EVENTO': 'EVENTO',\n 'UNIFORME': 'UNIFORME',\n 'MATERIAL POP': 'DISENO GRAFICO',\n 'POP': 'DISENO GRAFICO'\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeCanal(value) {\n const v = upper(value);\n\n const synonyms = {\n 'PUNTO DE VENTA': 'PDV',\n 'PDV': 'PDV',\n 'CANAL MODERNO': 'MODERNO',\n 'MODERNO': 'MODERNO',\n 'CANAL TRADICIONAL': 'TRADICIONAL',\n 'TRADICIONAL': 'TRADICIONAL',\n 'DIGITAL': 'ONLINE',\n 'ONLINE': 'ONLINE',\n 'NO APLICA': 'NO APLICA',\n 'N/A': 'NO APLICA'\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeTactica(value) {\n const v = upper(value);\n\n const synonyms = {\n 'SAMPLING': 'SAMPLING - MUESTREO',\n 'MUESTREO': 'SAMPLING - MUESTREO',\n 'DEGUSTACION': 'DEGUSTACIÓN',\n 'DEGUSTACIÓN': 'DEGUSTACIÓN',\n 'RULETA DIGITAL': 'RULETA',\n 'RULETA': 'RULETA',\n 'PLINKO': 'PLINKO',\n 'WHATSAPP': 'WHATSAPP',\n 'LANDING': 'LANDING PAGE',\n 'LANDING PAGE': 'LANDING PAGE',\n 'MATERIAL POP': 'POP',\n 'POP': 'POP',\n 'JUEGO DIGITAL': 'JUEGO DIGITAL',\n 'JUEGOS DIGITALES': 'JUEGO DIGITAL',\n 'EXHIBICION': 'EXHIBICIÓN',\n 'EXHIBICIÓN': 'EXHIBICIÓN',\n 'IMPULSO': 'IMPULSO'\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeEtiqueta(value) {\n const v = upper(value);\n\n const synonyms = {\n 'ACTIVACION': 'ACTIVACION',\n 'ACTIVACIÓN': 'ACTIVACION',\n 'EXHIBICION': 'EXHIBICION',\n 'EXHIBICIÓN': 'EXHIBICION',\n 'EXHIBIDOR': 'EXHIBIDOR',\n 'DEGUSTACION': 'DEGUSTACION',\n 'DEGUSTACIÓN': 'DEGUSTACION',\n 'PROMOCION': 'PROMOCIÓN',\n 'PROMOCIÓN': 'PROMOCIÓN',\n 'DISENO': 'DISEÑO',\n 'DISEÑO': 'DISEÑO',\n 'CAMPANA': 'CAMPAÑA',\n 'CAMPAÑA': 'CAMPAÑA',\n 'JUEGO DIGITAL': 'JUEGOS DIGITALES',\n 'JUEGOS DIGITALES': 'JUEGOS DIGITALES',\n 'PUNTO DE VENTA': 'PUNTO DE VENTA',\n 'PDV': 'PUNTO DE VENTA',\n 'F1': 'JUEGOS',\n 'SIMULADOR': 'JUEGOS DIGITALES',\n 'EXPERIENCIA': 'ACTIVIDADES',\n 'CORPORATIVO': 'CORPORATIVO',\n 'UNIFORME': 'UNIFORMES',\n 'UNIFORMES': 'UNIFORMES',\n 'POP': 'PUNTO DE VENTA',\n 'MATERIAL POP': 'PUNTO DE VENTA'\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeOne(value, allowedList, fallback = 'PENDIENTE', normalizer = upper) {\n const raw = clean(value);\n if (!raw) return fallback;\n\n const mapped = normalizer(raw);\n return allowedList.includes(mapped) ? mapped : fallback;\n}\n\nfunction normalizeMulti(value, allowedList, fallback = 'PENDIENTE', unknownValue = 'OTRO', normalizer = upper) {\n const parts = splitMulti(value);\n if (!parts.length) return fallback;\n\n const result = [];\n\n for (const part of parts) {\n const mapped = normalizer(part);\n const expanded = String(mapped).split(',').map(v => v.trim()).filter(Boolean);\n\n for (const val of expanded) {\n if (allowedList.includes(val)) {\n result.push(val);\n } else {\n result.push(unknownValue);\n }\n }\n }\n\n const finalValues = unique(result);\n if (!finalValues.length) return fallback;\n\n return joinMulti(finalValues);\n}\n\nconst ALLOWED_TIPO_ACCION = [\n 'ACTIVACION EN PDV',\n 'IMPULSO DE VENTA',\n 'ACTIVACION FUERA DE PDV',\n 'EVENTO',\n 'KICK OFF',\n 'FERIA',\n 'STAND',\n 'MUEBLE',\n 'DISENO GRAFICO',\n 'DIGITAL',\n 'PUBLICIDAD',\n 'TUTORIAL',\n 'INFORMATIVA',\n 'LOGOTIPO',\n 'PROMOCION',\n 'UNIFORME',\n 'ACTIVACIÓN',\n 'INAUGURACIÓN',\n 'PARQUE',\n 'TROFEO',\n 'ANAQUEL',\n 'FOOD TRUCK',\n 'EMPAQUES',\n 'CAMPAÑA',\n 'QUICK COUNTER',\n 'DISEÑO',\n 'PPT',\n 'PENDIENTE',\n 'OTRO'\n];\n\nconst ALLOWED_CANAL = [\n 'MODERNO',\n 'TRADICIONAL',\n 'ONLINE',\n 'NO APLICA',\n 'PDV',\n 'PENDIENTE'\n];\n\nconst ALLOWED_TACTICA = [\n 'DEGUSTACIÓN',\n 'RULETA',\n 'SAMPLING - MUESTREO',\n 'PLINKO',\n 'WHATSAPP',\n 'LANDING PAGE',\n 'POP',\n 'PHOTOBOOTH',\n 'CANJE',\n 'SORTEO',\n 'PREMIOS INSTANTÁNEOS',\n 'JUEGO DIGITAL',\n 'TRIVIA',\n 'EXHIBICIÓN',\n 'IMPULSO',\n 'PENDIENTE',\n 'OTRO'\n];\n\nconst ALLOWED_ETIQUETAS = [\n 'ACTIVACION',\n 'ACTIVIDADES',\n 'BANDEJA',\n 'BTS',\n 'CORPORATIVO',\n 'DEGUSTACION',\n 'EXHIBICION',\n 'EXHIBIDOR',\n 'FUERZA DE VENTAS',\n 'JUEGOS',\n 'PREMIOS',\n 'PUNTO DE VENTA',\n 'REFERENCIAS',\n 'REUNIÓN',\n 'RULETA',\n 'SAMPLING',\n 'HALLOWEEN',\n 'LUCES NEON',\n 'INFORMATIVA',\n 'CARRITO DEGUSTACIÓN',\n 'RED SOCIAL/INFLUENCER',\n 'UNIFORMES',\n 'REGALIAS',\n 'STAND',\n 'CAPACITACIÓN',\n 'NAVIDAD',\n 'DÍA DE LAS MADRES',\n 'INDEPENDENCIA',\n 'LOGOTIPO',\n 'QUICK COUNTER',\n 'PPT',\n 'TEMPLATES',\n 'DISEÑO',\n 'PROMOCIÓN',\n 'MODERNO',\n 'MAYORISTA',\n 'PULPERÍAS',\n 'JUEGOS DIGITALES',\n 'WHATSAPP',\n 'CAMPAÑA',\n 'CONCURSO',\n 'REGALOS CORPORATIVOS',\n 'PENDIENTE',\n 'OTRO'\n];\n\nlet nombrePropuesta = clean(ai.nombre_propuesta || original.NOMBRE || original.nombre_limpio || '');\nif (!nombrePropuesta) nombrePropuesta = 'PENDIENTE';\n\nlet tipoAccion = normalizeMulti(\n ai.tipo_accion,\n ALLOWED_TIPO_ACCION,\n 'PENDIENTE',\n 'OTRO',\n normalizeTipoAccion\n);\n\nconst canal = normalizeOne(\n ai.canal,\n ALLOWED_CANAL,\n 'PENDIENTE',\n normalizeCanal\n);\n\nconst tacticaPromocional = normalizeMulti(\n ai.tactica_promocional,\n ALLOWED_TACTICA,\n 'PENDIENTE',\n 'OTRO',\n normalizeTactica\n);\n\nlet etiquetas = normalizeMulti(\n ai.tags,\n ALLOWED_ETIQUETAS,\n 'PENDIENTE',\n 'OTRO',\n normalizeEtiqueta\n);\n\nif (etiquetas.includes('OTRO') && etiquetas !== 'OTRO') {\n etiquetas = etiquetas\n .split(',')\n .map(v => v.trim())\n .filter(v => v !== 'OTRO')\n .join(', ');\n}\n\nconst ambienteCompra = 'PENDIENTE';\nconst aprobada = 'PENDIENTE DE APROBACION';\n\nlet descripcion = clean(ai.descripcion);\nif (!descripcion) {\n descripcion = clean(original.Descripcion || original.NOMBRE || '');\n}\n\nlet requiereRevision = false;\nconst revisionReasons = [];\n\nif (tipoAccion.includes('OTRO') || tipoAccion === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('tipo_accion_revisar');\n}\n\nif (canal === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('canal_revisar');\n}\n\nif (tacticaPromocional.includes('OTRO') || tacticaPromocional === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('tactica_revisar');\n}\n\nif (etiquetas.includes('OTRO') || etiquetas === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('etiquetas_revisar');\n}\n\nreturn [\n {\n json: {\n NOMBRE: nombrePropuesta,\n 'TIPO DE ACCION': tipoAccion,\n CLIENTE: original.CLIENTE || '',\n MARCA: original.MARCA || '',\n PAIS: original.PAIS || '',\n CANAL: canal,\n 'AMBIENTE DE COMPRA (RE)': ambienteCompra,\n 'TÁCTICA PROMOCIONAL': tacticaPromocional,\n APROBADA: aprobada,\n ETIQUETAS: etiquetas,\n 'AÑO': original['AÑO'] || '',\n 'Enlace a la propuesta': original['Enlace a la propuesta'] || '',\n Descripcion: descripcion,\n\n file_id: original.file_id || '',\n nombre_archivo: original.nombre_archivo || original.NOMBRE || '',\n mime_type: original.mime_type || '',\n fuente_pais: original.fuente_pais || 'Tablero CDC',\n confianza_pais: original.confianza_pais || '',\n requiere_revision: requiereRevision,\n motivos_revision: revisionReasons.join(', '),\n procesado_ia: true,\n ultima_actualizacion: new Date().toISOString(),\n 'Enlaces a propuestas ejecutadas': original['Enlaces a propuestas ejecutadas'] || '',\n\n tablero_sync_key: original.tablero_sync_key || '',\n tablero_project_id: original.tablero_project_id || '',\n tablero_origen: original.tablero_origen || 'Tablero CDC',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2016,
-112
],
"id": "a183d0af-b313-49ce-9f00-e619a4d576d2",
"name": "Code - Normalizar respuesta IA Tablero"
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Code - Preparar project_id",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar project_id": {
"main": [
[
{
"node": "Supabase - Leer proyecto",
"type": "main",
"index": 0
}
]
]
},
"Supabase - Leer proyecto": {
"main": [
[
{
"node": "Supabase - Leer links del proyecto",
"type": "main",
"index": 0
}
]
]
},
"Supabase - Leer links del proyecto": {
"main": [
[
{
"node": "Code - Preparar filas Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar filas Fulgencio": {
"main": [
[
{
"node": "IF - Tiene filas para escribir?",
"type": "main",
"index": 0
}
]
]
},
"IF - Tiene filas para escribir?": {
"main": [
[
{
"node": "Code - Preparar entrada IA Tablero",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook1",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets - Upsert Banco Fulgencio": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar entrada IA Tablero": {
"main": [
[
{
"node": "Gemini - Extraer metadata Tablero",
"type": "main",
"index": 0
}
]
]
},
"Gemini - Extraer metadata1": {
"ai_languageModel": [
[
{
"node": "Gemini - Extraer metadata Tablero",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Gemini - Extraer metadata Tablero": {
"main": [
[
{
"node": "Code - Normalizar respuesta IA Tablero",
"type": "main",
"index": 0
}
]
]
},
"Code - Normalizar respuesta IA Tablero": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
}
},
"authors": "Isaac Aracena",
"name": "Version cdbd1088",
"description": "",
"autosaved": true,
"workflowPublishHistory": [
{
"createdAt": "2026-06-16T19:33:28.976Z",
"id": 1455,
"workflowId": "aJH5uWZx3lUnNBbx",
"versionId": "cdbd1088-4537-4fdb-8666-2166d8001680",
"event": "activated",
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
}
]
}
}