diff --git a/cdc-brief-glm-v2.json b/cdc-brief-glm-v2.json new file mode 100644 index 0000000..aaae3b8 --- /dev/null +++ b/cdc-brief-glm-v2.json @@ -0,0 +1,4137 @@ +{ + "updatedAt": "2026-06-04T00:42:57.459Z", + "createdAt": "2026-05-25T16:49:10.427Z", + "id": "Z8W67i7RfrhXRDFQ", + "name": "CDC Brief - GLM V2", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "cdc-brief-test-metadata", + "responseMode": "responseNode", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1488, + 688 + ], + "id": "3ea7e4f5-8bf3-4591-953f-e9a853e871fe", + "name": "Webhook", + "webhookId": "0459b7fe-a6fc-47cb-bcf1-afadb9587124" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "leftValue": "={{$json.type}} ", + "rightValue": "pdf_document", + "operator": { + "type": "string", + "operation": "equals" + }, + "id": "19494f94-55a5-4849-bdc0-e056140e371d" + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "PDF" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "9c4aa96c-5b3c-46bb-a2f7-a10da4c807be", + "leftValue": "={{$json.type}}", + "rightValue": "image", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Imagen" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "3d1a3c24-4fe2-4239-9b64-2a33c3043fb0", + "leftValue": "={{$('Code in JavaScript').item.json.type}}", + "rightValue": "video", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Video" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "29941bad-2e02-4c26-bccb-115fbb2c4b57", + "leftValue": "={{$json.type}}", + "rightValue": "audio", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Audio" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "8ff5d5cf-26e7-4516-a154-0d7fa8855012", + "leftValue": "={{$json.type}}", + "rightValue": "text", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Texto" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "437f4ff8-8556-4f8f-b935-a55eda65afde", + "leftValue": "={{$json.type}}", + "rightValue": "other", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "other" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.4, + "position": [ + -624, + 576 + ], + "id": "93f970e0-be35-4a98-9ecd-b962e6549589", + "name": "Switch" + }, + { + "parameters": { + "resource": "audio", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 800, + 688 + ], + "id": "7b3d0637-9b3c-4daf-8615-87455ab23905", + "name": "Analyze audio", + "retryOnFail": false, + "credentials": { + "googlePalmApi": { + "id": "6Jdcd9FONxG35YuJ", + "name": "Isaac - Gemini Api Gratis" + } + } + }, + { + "parameters": { + "numberInputs": 3 + }, + "type": "n8n-nodes-base.merge", + "typeVersion": 3.2, + "position": [ + 3056, + 736 + ], + "id": "5f1ad3e3-3ab2-40c8-a694-e7e5755322ce", + "name": "Merge" + }, + { + "parameters": { + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash-lite" + }, + "messages": { + "values": [ + { + "content": "=Actúa como un asistente de inteligencia artificial especializado en interpretación y estructuración de briefs creativos, estratégicos y comerciales.\n\nTu tarea es analizar todo el contenido recibido y transformarlo en un brief profesional, claro, accionable y listo para revisión interna, edición en Google Docs y representación visual en HTML.\n\nInstrucciones de trabajo:\n\n1. Captura e interpretación\n- Lee y consolida toda la información disponible.\n- Si existe texto derivado del audio, úsalo como contexto principal.\n- Si hay documentos, imágenes o referencias visuales, intégralos como soporte complementario.\n- Extrae entidades clave, objetivos, requerimientos, restricciones, entregables, fechas, audiencias, marcas, productos y cualquier dato relevante.\n- Distingue claramente entre:\n - Tipo de requerimiento = categoría general de la solicitud.\n - Tipo de entregable = pedido específico o salida concreta esperada.\n\n2. Detección de vacíos\n- Detecta campos obligatorios ausentes.\n- Identifica detalles técnicos faltantes.\n- Señala contradicciones o inconsistencias.\n- Marca información ambigua o poco clara.\n- Advierte posibles riesgos para el desarrollo creativo, estratégico o de producción.\n\n3. Enriquecimiento y estructuración\n- Reorganiza la información y conviértela en un brief profesional con redacción clara, precisa y ejecutiva.\n- Mejora la redacción sin inventar datos no proporcionados.\n- Cuando algo no esté disponible, indícalo como “No especificado” o “Pendiente de definir”.\n- Si no existe un nombre explícito para el proyecto, genera un nombre breve y profesional usando únicamente los datos reales disponibles, sin inventar información nueva.\n\n4. Reglas importantes de salida\n- No repitas información redundante.\n- No inventes datos.\n- Prioriza claridad, orden y utilidad práctica.\n- Redacta con tono profesional y ejecutivo.\n- La salida debe sentirse como un brief listo para revisión interna.\n- Debes incluir explícitamente los campos “Nombre del proyecto”, “Cliente”, “Marca”, “País”, “Tipo de requerimiento” y “Tipo de entregable”.\n- No mezcles “Tipo de requerimiento” con “Tipo de entregable”.\n- Si cliente y marca son la misma cosa, puedes repetir el valor en ambos campos.\n- Si no puedes identificar uno de los dos, usa “No especificado”.\n\n5. Genera la salida exactamente en este formato:\n\n1. Resumen ejecutivo\n- Explica de forma breve y profesional de qué trata la solicitud, cuál es el objetivo y qué se espera desarrollar.\n\n2. Información clave del brief\n- Nombre del proyecto\n- Cliente\n- Marca\n- País\n- Producto / servicio\n- Tipo de requerimiento\n- Tipo de entregable\n- Objetivo principal\n- Contexto / antecedente\n- Audiencia objetivo\n- Mensaje clave\n- Entregables solicitados\n- Canales / formatos\n- Requerimientos técnicos\n- Referencias creativas o visuales\n- Fechas o tiempos relevantes\n- Presupuesto\n- Restricciones o consideraciones especiales\n\n3. Vacíos detectados\n- Lista clara de campos faltantes, ambiguos o contradictorios.\n\n4. Riesgos o alertas\n- Señala riesgos que puedan afectar la ejecución, creatividad, tiempos, costos o claridad del proyecto.\n\n5. Recomendaciones rápidas\n- Sugiere qué información adicional conviene completar o reforzar antes de aprobar el brief final.\n\n6. Nivel de completitud\n- Indica un porcentaje estimado de completitud del brief según la información recibida.\n\nContenido recibido:\n{{$json.combined_content}}" + } + ] + }, + "builtInTools": {}, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 3440, + 752 + ], + "id": "2fb18bfd-e222-4107-a98c-fb6f3b6ab295", + "name": "Message a model", + "credentials": { + "googlePalmApi": { + "id": "6Jdcd9FONxG35YuJ", + "name": "Isaac - Gemini Api Gratis" + } + } + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "ddc91110-0c3b-4ee7-a973-322a3d1c7c1e", + "name": "type", + "value": "audio", + "type": "string" + }, + { + "id": "b667804d-e4b9-4225-9a6e-01d54067f0cf", + "name": "fileName", + "value": "={{$json.fileName || ''}}", + "type": "string" + }, + { + "id": "4023fd4f-95ce-4571-845b-c7555c4efc88", + "name": "content_summary", + "value": "={{ $json.content.parts[0].text || '' }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 1888, + 672 + ], + "id": "a6b8547f-cd75-49b4-bf2f-cbe205a0d465", + "name": "Edit Fields1" + }, + { + "parameters": { + "respondWith": "text", + "responseBody": "=Este es el enlace al folder con el contenido del brief:\n\nhttps://drive.google.com/drive/u/0/folders/{{ $('Create folder').first().json.id }}\n\nEn caso de algún vacío detectado, favor de ingresar al link y modificar el archivo.\n\nVacíos detectados:\n\n{{ $('Build Docs Requests').first().json.fields.VACIOS_DETECTADOS || \"Ninguno\" }}", + "options": {} + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.5, + "position": [ + 7520, + 688 + ], + "id": "fccc258d-d736-4d05-9f32-221921093e49", + "name": "Respond to Webhook" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 816, + 928 + ], + "id": "91da2758-fb1e-4a2e-ad21-e08b5c54a0a7", + "name": "Analyze an image", + "retryOnFail": false, + "credentials": { + "googlePalmApi": { + "id": "6Jdcd9FONxG35YuJ", + "name": "Isaac - Gemini Api Gratis" + } + } + }, + { + "parameters": { + "resource": "folder", + "name": "=Brief {{$now}}", + "driveId": { + "__rl": true, + "mode": "list", + "value": "My Drive" + }, + "folderId": { + "__rl": true, + "mode": "list", + "value": "root", + "cachedResultName": "/ (Root folder)" + }, + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + -1328, + 688 + ], + "id": "7de162cd-4388-436e-b870-1de9fafad3a8", + "name": "Create folder", + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "738bec56-f577-41da-a35f-dc8346893e10", + "name": "fileName", + "value": "={{ $('Switch').item.json.fileName || '' }}", + "type": "string" + }, + { + "id": "6df83b75-7e13-48db-a557-7ee85c52a2b2", + "name": "content_summary", + "value": "={{ $json.content?.parts?.[0]?.text || 'Imagen adjunta por el usuario. No se pudo generar una descripción automática en este intento.' }}", + "type": "string" + }, + { + "id": "1a1db20c-3dd7-4776-9d97-46bd31213a87", + "name": "type", + "value": "image", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2080, + 960 + ], + "id": "ba2df39a-fe08-4b73-9ece-d1c273a710dd", + "name": "Edit Fields2" + }, + { + "parameters": { + "resource": "folder", + "operation": "share", + "folderNoRootId": { + "__rl": true, + "value": "={{ $('Create folder').first().json.id }}", + "mode": "id" + }, + "permissionsUi": { + "permissionsValues": { + "role": "writer", + "type": "domain", + "domain": "gomezleemarketing.com" + } + }, + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + 7104, + 688 + ], + "id": "8217b8c5-f406-45af-b929-94b9aa60d387", + "name": "Share folder", + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "name": "={{ $json.fileName }}", + "driveId": { + "__rl": true, + "mode": "list", + "value": "My Drive" + }, + "folderId": { + "__rl": true, + "value": "={{ $json.folderId }}", + "mode": "id" + }, + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + 1200, + 1392 + ], + "id": "9b472aac-ac5e-4e33-bef7-c676de10d699", + "name": "Upload file", + "executeOnce": false, + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "sendTo": "gmarrero@gomezleemarketing.com, obrisceno@gomezleemarketing.com, boliva@gomezleemarketing.com", + "subject": "✏️ Nuevo Brief", + "message": "=
\n Enviado por {{ $('Webhook').first().json.body.submittedByName || 'No especificado' }}\n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n
\n\n
\n
\n\n
\n\n
\n
\n Se ha creado un nuevo brief\n
\n
\n\n
\n

\n Hola Gerardo,\n

\n\n

\n Te informo que se ha creado el siguiente brief:\n

\n\n
\n
\n Carpeta del brief\n
\n\n \n Abrir carpeta en Google Drive\n \n
\n\n
\n
\n
\n Datos del envío\n
\n
\n\n
\n
\n
\n Enviado por\n
\n\n
\n {{ $('Webhook').first().json.body.submittedByName || 'No especificado' }}\n
\n
\n\n \n\n
\n
\n Fecha límite de entrega\n
\n\n
\n {{ $('Preparar fecha de entrega').first().json.deliveryDateLabel || $('Preparar fecha de entrega').first().json.fechaEntregaTexto || $('Webhook').first().json.body.deliveryDateLabel || $('Webhook').first().json.body.fechaEntregaTexto || 'Por confirmar' }}\n
\n
\n
\n
\n\n

\n Gracias,\n

\n
\n\n
\n\n\n
\n
\n", + "options": { + "senderName": "📢 HEY" + } + }, + "type": "n8n-nodes-base.gmail", + "typeVersion": 2.2, + "position": [ + 7872, + 688 + ], + "id": "14e8c6c6-d1c1-48f1-80c6-efb34420f33d", + "name": "Send a message", + "webhookId": "2a920b9f-c697-4a4e-9c69-0bf0749c2661", + "credentials": { + "gmailOAuth2": { + "id": "cehQBe6X1BQLmcD9", + "name": "Gmail account - Isaac" + } + } + }, + { + "parameters": { + "operation": "copy", + "fileId": { + "__rl": true, + "value": "1UuCLClREoC_9OvT407B36nHJ1x4l418Q0dEwgGccws4", + "mode": "list", + "cachedResultName": "CDC_Brief_GLM_V2", + "cachedResultUrl": "https://docs.google.com/document/d/1UuCLClREoC_9OvT407B36nHJ1x4l418Q0dEwgGccws4/edit?usp=drivesdk" + }, + "name": "={{ $('Build Names').first().json.docName }}", + "sameFolder": false, + "driveId": { + "__rl": true, + "mode": "list", + "value": "My Drive" + }, + "folderId": { + "__rl": true, + "value": "={{ $('Create folder').first().json.id }}", + "mode": "id" + }, + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + 6432, + 688 + ], + "id": "e2dc1490-d155-4def-bb21-39195d6e8908", + "name": "Copy template", + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "jsCode": "const text = String($json.googleDocsText || '').replace(/\\r\\n/g, '\\n').trim();\nconst ANO = String(new Date().getFullYear());\n\nconst baseItems = $('Code in JavaScript').all();\n\nconst urlRegex = /https?:\\/\\/[^\\s<>\"')]+/gi;\nconst linksSet = new Set();\nconst attachmentsSet = new Set();\n\nfor (const item of baseItems) {\n const json = item.json || {};\n\n const textValue = String(json.text || '').trim();\n const matches = textValue.match(urlRegex) || [];\n for (const url of matches) {\n linksSet.add(url);\n }\n\n const fileName = String(json.fileName || '').trim();\n if (fileName) {\n attachmentsSet.add(fileName);\n }\n}\n\nconst support = {\n enlacesReferenciaText: linksSet.size\n ? Array.from(linksSet).map(url => `• ${url}`).join('\\n')\n : 'No se compartieron enlaces de referencia online.',\n\n adjuntosRecibidosText: attachmentsSet.size\n ? Array.from(attachmentsSet).map(name => `• ${name}`).join('\\n')\n : 'No se recibieron adjuntos adicionales.'\n};\n\nfunction getNodeJson(nodeName) {\n try {\n return $(nodeName).first().json || {};\n } catch (error) {\n return {};\n }\n}\n\nfunction sectionBetween(startRe, endRe) {\n const lines = text.split('\\n');\n let start = -1;\n let end = lines.length;\n\n for (let i = 0; i < lines.length; i++) {\n if (startRe.test(lines[i].trim())) {\n start = i + 1;\n break;\n }\n }\n\n if (start === -1) return '';\n\n if (endRe) {\n for (let i = start; i < lines.length; i++) {\n if (endRe.test(lines[i].trim())) {\n end = i;\n break;\n }\n }\n }\n\n return lines.slice(start, end).join('\\n').trim();\n}\n\nfunction normalizeLabel(str) {\n return String(str || '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction cleanValue(value, fallback = 'No especificado') {\n const lines = String(value || '')\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n\n return lines.length ? lines.join('\\n') : fallback;\n}\n\nfunction formatMultiline(value, fallback = 'No especificado') {\n const lines = String(value || '')\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n\n if (!lines.length) return fallback;\n\n return lines\n .map(line => {\n if (/^[•*\\-–]\\s*/.test(line)) {\n return `• ${line.replace(/^[•*\\-–]\\s*/, '').trim()}`;\n }\n return line;\n })\n .join('\\n');\n}\n\nfunction bullets(sectionText, fallback = 'No especificado') {\n const items = String(sectionText || '')\n .split('\\n')\n .map(line => line.replace(/^\\s*[•*\\-–]\\s*/, '').trim())\n .filter(Boolean);\n\n if (!items.length) return fallback;\n\n return items.map(item => `• ${item}`).join('\\n');\n}\n\nfunction completion(sectionText) {\n const match = String(sectionText || '').match(/(\\d{1,3}\\s*%)/);\n if (match) return match[1].replace(/\\s+/g, '');\n\n const cleaned = String(sectionText || '').trim();\n return cleaned || 'No especificado';\n}\n\nfunction buildFechasTiemposRelevantes(fechaEntrega, fechasDetectadas) {\n const fecha = String(fechaEntrega || '').trim() || 'Por confirmar';\n const fechas = String(fechasDetectadas || '').trim();\n\n const entregaLine = `• Fecha límite de entrega: ${fecha}.`;\n\n const fechasNormalizadas = normalizeLabel(fechas)\n .replace(/[.:;]+$/g, '')\n .trim();\n\n const noHayFechasDetectadas =\n !fechas ||\n fechasNormalizadas === 'no especificado' ||\n fechasNormalizadas === 'no especificada' ||\n fechasNormalizadas === 'no se especifico' ||\n fechasNormalizadas === 'no se especifica' ||\n fechasNormalizadas === 'no indicado' ||\n fechasNormalizadas === 'no indicada';\n\n if (noHayFechasDetectadas) {\n return entregaLine;\n }\n\n return `${entregaLine}\\n${fechas}`;\n}\n\nconst fechaEntregaNode = getNodeJson('Preparar fecha de entrega');\n\nconst fechaEntrega =\n fechaEntregaNode.deliveryDateLabel ||\n fechaEntregaNode.fechaEntregaTexto ||\n 'Por confirmar';\n\nconst infoSection = sectionBetween(\n /^2\\.\\s*Informaci[oó]n clave del brief$/i,\n /^3\\.\\s*Vac[ií]os\\s+detectados$/i\n);\n\nconst labelMap = {\n 'Nombre del proyecto': 'NOMBRE_PROYECTO',\n 'Cliente': 'CLIENTE',\n 'Marca': 'MARCA',\n 'País': 'PAIS',\n 'Pais': 'PAIS',\n 'Producto / servicio': 'PRODUCTO_SERVICIO',\n 'Tipo de requerimiento': 'TIPO_REQUERIMIENTO',\n 'Tipo de entregable': 'TIPO_ENTREGABLE',\n 'Objetivo principal': 'OBJETIVO_PRINCIPAL',\n 'Contexto / antecedente': 'CONTEXTO_ANTECEDENTE',\n 'Audiencia objetivo': 'AUDIENCIA_OBJETIVO',\n 'Mensaje clave': 'MENSAJE_CLAVE',\n 'Entregables solicitados': 'ENTREGABLES',\n 'Canales / formatos': 'CANALES_FORMATOS',\n 'Requerimientos técnicos': 'REQUERIMIENTOS_TECNICOS',\n 'Referencias creativas o visuales': 'REFERENCIAS_VISUALES',\n 'Fechas o tiempos relevantes': 'FECHAS_TIEMPOS_RELEVANTES',\n 'Fechas / tiempos relevantes': 'FECHAS_TIEMPOS_RELEVANTES',\n 'Presupuesto': 'PRESUPUESTO',\n 'Restricciones o consideraciones especiales': 'RESTRICCIONES'\n};\n\nconst normalizedLabelMap = Object.fromEntries(\n Object.entries(labelMap).map(([label, key]) => [normalizeLabel(label), key])\n);\n\nconst info = {};\nlet currentKey = null;\n\nfor (const rawLine of infoSection.split('\\n')) {\n const line = rawLine.trim();\n if (!line) continue;\n\n const match = line.match(/^(?:[•*\\-–]\\s*)?(.+?):\\s*(.*)$/);\n\n if (match) {\n const labelCandidate = normalizeLabel(match[1]);\n const mappedKey = normalizedLabelMap[labelCandidate];\n\n if (mappedKey) {\n currentKey = mappedKey;\n info[currentKey] = match[2].trim();\n continue;\n }\n }\n\n if (currentKey) {\n info[currentKey] = [info[currentKey], line].filter(Boolean).join('\\n');\n }\n}\n\nconst CLIENTE = cleanValue(info.CLIENTE);\nconst MARCA = cleanValue(info.MARCA);\n\nfunction buildMarcaCliente(cliente, marca) {\n const c = cleanValue(cliente);\n const m = cleanValue(marca);\n\n if (c === 'No especificado' && m === 'No especificado') return 'No especificado';\n if (c === 'No especificado') return m;\n if (m === 'No especificado') return c;\n\n const nc = normalizeLabel(c);\n const nm = normalizeLabel(m);\n\n if (nc === nm) return c.length >= m.length ? c : m;\n\n if (nc.includes(nm) || nm.includes(nc)) {\n return c.length >= m.length ? c : m;\n }\n\n return `${m} / ${c}`;\n}\n\nconst MARCA_CLIENTE = buildMarcaCliente(CLIENTE, MARCA);\n\nconst fechasDetectadas = formatMultiline(info.FECHAS_TIEMPOS_RELEVANTES);\n\nconst fields = {\n ANO,\n NOMBRE_PROYECTO: cleanValue(info.NOMBRE_PROYECTO),\n CLIENTE,\n MARCA,\n MARCA_CLIENTE,\n\n PAIS: cleanValue(info.PAIS),\n RESUMEN_EJECUTIVO: sectionBetween(\n /^1\\.\\s*Resumen ejecutivo$/i,\n /^2\\.\\s*Informaci[oó]n clave del brief$/i\n ) || 'No especificado',\n\n PRODUCTO_SERVICIO: cleanValue(info.PRODUCTO_SERVICIO),\n TIPO_REQUERIMIENTO: cleanValue(info.TIPO_REQUERIMIENTO),\n TIPO_ENTREGABLE: cleanValue(info.TIPO_ENTREGABLE),\n OBJETIVO_PRINCIPAL: cleanValue(info.OBJETIVO_PRINCIPAL),\n CONTEXTO_ANTECEDENTE: cleanValue(info.CONTEXTO_ANTECEDENTE),\n AUDIENCIA_OBJETIVO: cleanValue(info.AUDIENCIA_OBJETIVO),\n MENSAJE_CLAVE: cleanValue(info.MENSAJE_CLAVE),\n\n ENTREGABLES: formatMultiline(info.ENTREGABLES),\n CANALES_FORMATOS: formatMultiline(info.CANALES_FORMATOS),\n REQUERIMIENTOS_TECNICOS: formatMultiline(info.REQUERIMIENTOS_TECNICOS),\n REFERENCIAS_VISUALES: formatMultiline(info.REFERENCIAS_VISUALES),\n\n FECHAS_TIEMPOS_RELEVANTES: buildFechasTiemposRelevantes(\n fechaEntrega,\n fechasDetectadas\n ),\n\n PRESUPUESTO: cleanValue(info.PRESUPUESTO),\n RESTRICCIONES: formatMultiline(info.RESTRICCIONES),\n\n VACIOS_DETECTADOS: bullets(\n sectionBetween(\n /^3\\.\\s*Vac[ií]os\\s+detectados$/i,\n /^4\\.\\s*Riesgos o alertas$/i\n )\n ),\n\n RIESGOS_ALERTAS: bullets(\n sectionBetween(\n /^4\\.\\s*Riesgos o alertas$/i,\n /^5\\.\\s*Recomendaciones rápidas$/i\n )\n ),\n\n RECOMENDACIONES_RAPIDAS: bullets(\n sectionBetween(\n /^5\\.\\s*Recomendaciones rápidas$/i,\n /^6\\.\\s*Nivel de completitud$/i\n )\n ),\n\n NIVEL_COMPLETITUD: completion(\n sectionBetween(\n /^6\\.\\s*Nivel de completitud$/i,\n null\n )\n ),\n\n ENLACES_REFERENCIA: support.enlacesReferenciaText || 'No se compartieron enlaces de referencia online.',\n ADJUNTOS_RECIBIDOS: support.adjuntosRecibidosText || 'No se recibieron adjuntos adicionales.'\n};\n\nconst requests = Object.entries(fields).map(([key, value]) => ({\n replaceAllText: {\n containsText: {\n text: `{{${key}}}`,\n matchCase: true,\n },\n replaceText: value,\n },\n}));\n\nreturn [\n {\n json: {\n fields,\n requests,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5328, + 688 + ], + "id": "57bef68a-7648-489c-8ffb-a7e5652a80af", + "name": "Build Docs Requests" + }, + { + "parameters": { + "method": "POST", + "url": "=https://docs.googleapis.com/v1/documents/{{ $('Copy template').first().json.id }}:batchUpdate", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "googleDocsOAuth2Api", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { \"requests\": $('Build Docs Requests').first().json.requests } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 6784, + 688 + ], + "id": "377a0388-d30c-4735-971e-9b4b79a07bea", + "name": "HTTP Request", + "credentials": { + "googleDocsOAuth2Api": { + "id": "2bGzUIxSb9LeUeNO", + "name": "Isaac - Google Docs" + } + } + }, + { + "parameters": { + "jsCode": "const f = $json.fields || {};\n\nfunction cleanPart(value, fallback = 'No especificado') {\n const text = String(value || '').trim();\n return text || fallback;\n}\n\nfunction safePart(value, fallback = 'No especificado') {\n return cleanPart(value, fallback)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[\\\\/:*?\"<>|]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/\\s/g, '-');\n}\n\nconst ano = safePart(f.ANO);\nconst cliente = safePart(f.CLIENTE);\nconst marca = safePart(f.MARCA);\nconst pais = safePart(f.PAIS);\nconst tipoRequerimiento = safePart(f.TIPO_REQUERIMIENTO);\n\nconst baseName = [\n ano,\n cliente,\n marca,\n pais,\n tipoRequerimiento\n].join('_');\n\nreturn [\n {\n json: {\n folderName: baseName,\n docName: baseName,\n htmlName: `${baseName}.html`,\n pdfName: `${baseName}.pdf`,\n projectName: cleanPart(f.NOMBRE_PROYECTO),\n fields: f\n }\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5600, + 688 + ], + "id": "d69b87b6-d228-42ba-b43a-aec0b527bf09", + "name": "Build Names" + }, + { + "parameters": { + "operation": "update", + "fileId": { + "__rl": true, + "value": "={{ $('Create folder').first().json.id }}", + "mode": "id" + }, + "newUpdatedFileName": "={{ $('Build Names').first().json.folderName }}", + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + 5936, + 688 + ], + "id": "c04d4432-4081-437b-b9a5-2d28387ba2d5", + "name": "Rename folder", + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "content": "ENTRADA DEL FLUJO\n\n• Recibe la solicitud del frontend\n• Crea carpeta base en Google Drive\n• Esa carpeta guardará brief y adjuntos\n\nEntradas:\nvoz, imágenes, PDFs y otros archivos", + "height": 400, + "width": 304, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1504, + 480 + ], + "id": "c1fd447e-d16c-45d3-9d9b-6a382517ae8a", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "CLASIFICACIÓN Y ENRUTAMIENTO\n\n• Identifica tipo de entrada\n• Separa: PDF, imagen, audio, texto y otros\n• Prepara cada rama para su procesamiento\n• También detecta enlaces en texto", + "height": 512, + "width": 692, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1088, + 400 + ], + "id": "c4e13e49-10d1-4b5f-aff3-e3485aa6e333", + "name": "Sticky Note1" + }, + { + "parameters": { + "content": "GUARDADO DE ADJUNTOS\n\n• Guarda los archivos originales del usuario\n• Incluye PDFs, imágenes y otros soportes\n• No todos se analizan\n• Sí quedan preservados en la carpeta final", + "height": 320, + "width": 672, + "color": "#433D3D" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 720, + 1232 + ], + "id": "4dce857e-1292-43c5-b5dc-a95f33a1d9dd", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "ANÁLISIS DE INSUMOS\n\n• PDF: se analiza con Gemini\n• Audio: se transcribe / resume\n• Imagen: se interpreta\n• Si no hay PDF, el flujo continúa\n• Cada salida se normaliza en Edit Fields", + "height": 1216, + "width": 2752, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -320, + -32 + ], + "id": "d96b5f33-53f1-4d3d-a146-bf3fcaf3a166", + "name": "Sticky Note3" + }, + { + "parameters": { + "content": "CONSOLIDACIÓN DEL CONTEXTO\n\n• Une documento, audio e imagen\n• Construye un contexto único\n• Envía todo al modelo\n• El modelo genera el brief estructurado", + "height": 576, + "width": 2000, + "color": "#9E2929" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 2704, + 480 + ], + "id": "5ea54f56-8749-46a2-aff5-04920de7f51c", + "name": "Sticky Note4" + }, + { + "parameters": { + "content": "POSTPROCESO DEL BRIEF\n\n• Limpia la salida del modelo\n• Extrae campos del brief\n• Construye placeholders\n• Genera nombres finales\n• Renombra la carpeta creada al inicio", + "height": 352, + "width": 1252 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 4816, + 496 + ], + "id": "1206c7e8-be96-45ce-acf7-2f21d61608a5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "SALIDA FINAL\n\n• Copia la plantilla oficial\n• Inserta la información del brief\n• Comparte la carpeta\n• Devuelve el enlace por webhook\n• Envía el correo", + "height": 384, + "width": 2340, + "color": "#3F360C" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 6192, + 496 + ], + "id": "b7587368-4628-4885-a1c1-c0be75e4dba5", + "name": "Sticky Note6" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "f34e6067-5572-44ef-bac9-738212957d5c", + "leftValue": "={{ $json.pdfMissing || false }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 48, + 208 + ], + "id": "2779ddc8-7631-417e-8800-c4a604014b4b", + "name": "If PDF missing" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "be678498-4e99-45bf-acb7-b7ade22fabb5", + "name": "fileName", + "value": "''", + "type": "string" + }, + { + "id": "e9a77032-adbd-462c-bb2e-20f267496a8e", + "name": "content_summary", + "value": "No se adjuntó PDF. Continúa el flujo con audio e imagen.", + "type": "string" + }, + { + "id": "bf85223a-197a-4a28-b3bf-064dc0fe8300", + "name": "type", + "value": "document", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 912, + -16 + ], + "id": "72c12649-c5c8-47d3-a571-cbb3f73ceae6", + "name": "PDF opcional omitido" + }, + { + "parameters": { + "resource": "document", + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 448, + 352 + ], + "id": "0b5b8ccb-ebcc-4b12-984b-a336264bd4ae", + "name": "Analyze document", + "credentials": { + "googlePalmApi": { + "id": "6Jdcd9FONxG35YuJ", + "name": "Isaac - Gemini Api Gratis" + } + } + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "0b8567da-0fd5-45c3-9f4e-071036d665bb", + "name": "fileName", + "value": "={{ $json.fileName || $('Switch').item.json.fileName || '' }}", + "type": "string" + }, + { + "id": "7af7edfc-6c2a-4183-ac0f-f5e16e925fff", + "name": "content_summary", + "value": "={{ $json.content?.parts?.[0]?.text || $json.content_summary || '' }}", + "type": "string" + }, + { + "id": "6447843f-472f-4249-ac49-bf63ed6cb04c", + "name": "type", + "value": "document", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 1952, + 176 + ], + "id": "f5280257-72a5-4721-8073-013e65b187ba", + "name": "Edit Fields documento" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\nreturn items;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -96, + 208 + ], + "id": "62375717-01d5-4dc3-b106-7304ebe20c3d", + "name": "Procesar documentos" + }, + { + "parameters": { + "jsCode": "const webhookItems = $('Webhook').all();\nconst out = [];\n\nlet hasPdfDocument = false;\n\nfor (const webhookItem of webhookItems) {\n const json = webhookItem.json || {};\n const body = json.body || {};\n const binary = webhookItem.binary || {};\n\n const text =\n body.text ||\n body.message ||\n body.caption ||\n body.prompt ||\n json.text ||\n json.message ||\n '';\n\n if (typeof text === 'string' && text.trim() !== '') {\n out.push({\n json: {\n type: 'text',\n text: text.trim(),\n }\n });\n }\n\n for (const key of Object.keys(binary)) {\n const file = binary[key];\n const mime = String(file.mimeType || '').toLowerCase();\n const ext = String(file.fileExtension || '').toLowerCase();\n const fileName = String(file.fileName || '').toLowerCase();\n\n let type = 'attachment_only';\n\n const isImage = mime.startsWith('image/');\n const isAudio = mime.startsWith('audio/');\n const isPdf =\n mime.includes('pdf') ||\n ext === 'pdf' ||\n fileName.endsWith('.pdf');\n\n if (isImage) {\n type = 'image';\n } else if (isAudio) {\n type = 'audio';\n } else if (isPdf) {\n type = 'pdf_document';\n hasPdfDocument = true;\n } else {\n type = 'attachment_only';\n }\n\n out.push({\n json: {\n type,\n binaryKey: key,\n mimeType: file.mimeType || '',\n fileName: file.fileName || '',\n fileExtension: file.fileExtension || '',\n fileSize: file.fileSize || '',\n },\n binary: {\n data: file\n }\n });\n }\n}\n\nif (!hasPdfDocument) {\n out.push({\n json: {\n type: 'pdf_document',\n pdfMissing: true,\n fileName: '',\n content_summary: 'No se adjuntó PDF. Continúa el flujo con audio e imagen.'\n }\n });\n}\n\nreturn out;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -800, + 688 + ], + "id": "4ee568a2-916f-47ec-8517-a2b3ba65a9cc", + "name": "Code in JavaScript" + }, + { + "parameters": { + "jsCode": "const folderId = $('Create folder').first().json.id;\nconst items = $input.all();\n\nconst output = [];\n\nfor (const item of items) {\n const binaries = item.binary || {};\n\n for (const key of Object.keys(binaries)) {\n const file = binaries[key];\n\n output.push({\n json: {\n folderId,\n fileName: file.fileName || key,\n mimeType: file.mimeType || '',\n },\n binary: {\n data: file\n }\n });\n }\n}\n\nreturn output;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 784, + 1392 + ], + "id": "aaab3f40-a654-4289-8325-d7eab6cd73dd", + "name": "Code in JavaScript3" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\n\nif (!items.length) {\n return [{\n json: {\n combined_content: 'No se recibió contenido para resumir.'\n }\n }];\n}\n\nconst combined = items.map((item, i) => {\n const type = item.json.type || 'other';\n const fileName = item.json.fileName || '';\n const summary = item.json.content_summary || '';\n\n return [\n `Entrada ${i + 1}`,\n `Tipo: ${type}`,\n fileName ? `Archivo: ${fileName}` : null,\n `Contenido:`,\n summary\n ].filter(Boolean).join('\\n');\n}).join('\\n\\n====================\\n\\n');\n\nreturn [\n {\n json: {\n combined_content: combined\n }\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3216, + 752 + ], + "id": "cf149c50-8821-4bb9-bcd3-451fefdbdb8f", + "name": "Code in JavaScript1" + }, + { + "parameters": { + "jsCode": "const raw = $('Message a model').item.json.content.parts[0].text || '';\n\nlet formatted = raw\n .replace(/\\r\\n/g, '\\n')\n .replace(/^---$/gm, '')\n .replace(/^# (.*)$/gm, '\\n$1\\n')\n .replace(/^## (.*)$/gm, '\\n$1\\n')\n .replace(/^### (.*)$/gm, '\\n$1\\n')\n .replace(/\\*\\*(.*?)\\*\\*/g, '$1')\n .replace(/^\\*\\s+/gm, '• ')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n\nformatted = `BRIEF PROFESIONAL\\n\\n${formatted}`;\n\nreturn [\n {\n json: {\n googleDocsText: formatted\n }\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4896, + 688 + ], + "id": "ccd4c5dd-fdd4-4a94-bc57-fea7c40e30f9", + "name": "Code in JavaScript4" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\n\nreturn items.map((item, index) => ({\n json: {\n ...item.json,\n _fallbackIndex: index,\n },\n binary: item.binary,\n}));" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 240, + 288 + ], + "id": "5ab6e790-bdd8-47db-ad31-ded716007eb1", + "name": "Backup document input" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\n\nreturn items.map((item, index) => ({\n json: {\n ...item.json,\n _fallbackIndex: index,\n },\n binary: item.binary,\n}));" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 576, + 640 + ], + "id": "b38d2104-40d0-465f-8ed8-2f20d1828037", + "name": "Backup audio input" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\n\nreturn items.map((item, index) => ({\n json: {\n ...item.json,\n _fallbackIndex: index,\n },\n binary: item.binary,\n}));" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 624, + 928 + ], + "id": "4938540d-2300-4dab-ba47-476ba38ac879", + "name": "Backup image input" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "52ff398d-d5fa-4329-8755-03578f1f3965", + "leftValue": "={{ !$json.content?.parts?.[0]?.text }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1040, + 944 + ], + "id": "3484866a-ae5e-4d99-bfa5-b93828aa4902", + "name": "IF imagen gratis falló" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-3.1-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-3.1-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 1584, + 768 + ], + "id": "4c653881-cb9a-451a-8d26-51424ebef3bd", + "name": "Analyze an image - Pago", + "retryOnFail": false, + "credentials": { + "googlePalmApi": { + "id": "jvsXYwL6IOoY2DBU", + "name": "Isaac - Gemini Api Pago" + } + } + }, + { + "parameters": { + "jsCode": "const failedItems = $input.all();\nconst originalItems = $('Backup image input').all();\n\nreturn failedItems.map((failedItem, index) => {\n let originalIndex = failedItem.json?._fallbackIndex;\n\n if (originalIndex === undefined && failedItem.pairedItem) {\n if (Array.isArray(failedItem.pairedItem)) {\n originalIndex = failedItem.pairedItem[0]?.item;\n } else {\n originalIndex = failedItem.pairedItem.item;\n }\n }\n\n if (originalIndex === undefined) {\n originalIndex = index;\n }\n\n const original = originalItems[originalIndex] || originalItems[index] || originalItems[0];\n\n if (!original?.binary?.data) {\n throw new Error(`No se encontró binary.data para recuperar la imagen en el índice ${originalIndex}`);\n }\n\n return {\n json: {\n ...original.json,\n fallbackUsed: true,\n fallbackReason: failedItem.json?.error || 'Error en API gratis',\n },\n binary: original.binary,\n };\n});" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1280, + 768 + ], + "id": "11b28396-2d34-4978-b562-5190d20908bf", + "name": "Recuperar binario imagen" + }, + { + "parameters": { + "resource": "audio", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-3.1-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-3.1-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 1520, + 464 + ], + "id": "90fa8b77-56ae-4f57-b268-ba5b74a9d5f0", + "name": "Analyze audio - Pago", + "retryOnFail": false, + "credentials": { + "googlePalmApi": { + "id": "jvsXYwL6IOoY2DBU", + "name": "Isaac - Gemini Api Pago" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "a0d47fed-e52c-4882-bd89-c7833b214111", + "leftValue": "={{ !$json.content?.parts?.[0]?.text }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 976, + 656 + ], + "id": "6096066c-9335-4c00-8b4f-b36936ad0f42", + "name": "If audio gratis falló" + }, + { + "parameters": { + "jsCode": "const failedItems = $input.all();\nconst originalItems = $('Backup audio input').all();\n\nreturn failedItems.map((failedItem, index) => {\n let originalIndex = failedItem.json?._fallbackIndex;\n\n if (originalIndex === undefined && failedItem.pairedItem) {\n if (Array.isArray(failedItem.pairedItem)) {\n originalIndex = failedItem.pairedItem[0]?.item;\n } else {\n originalIndex = failedItem.pairedItem.item;\n }\n }\n\n if (originalIndex === undefined) {\n originalIndex = index;\n }\n\n const original = originalItems[originalIndex] || originalItems[index] || originalItems[0];\n\n if (!original?.binary?.data) {\n throw new Error(`No se encontró binary.data para recuperar el audio en el índice ${originalIndex}`);\n }\n\n return {\n json: {\n ...original.json,\n fallbackUsed: true,\n fallbackReason: failedItem.json?.error || 'Error en API gratis',\n },\n binary: original.binary,\n };\n});" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1312, + 464 + ], + "id": "e10409e3-1d67-4c8e-800b-7d9dcfd14ca6", + "name": "Recuperar binario audio" + }, + { + "parameters": { + "resource": "document", + "modelId": { + "__rl": true, + "value": "models/gemini-3.1-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-3.1-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 1104, + 160 + ], + "id": "49feacc4-b313-4c20-913f-05fe2246abf3", + "name": "Analyze document - Pago", + "credentials": { + "googlePalmApi": { + "id": "jvsXYwL6IOoY2DBU", + "name": "Isaac - Gemini Api Pago" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "033b7aa0-7c40-4f6a-9056-83094b6451f0", + "leftValue": "={{ !$json.content?.parts?.[0]?.text }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 640, + 352 + ], + "id": "92a589b2-52b9-442b-93b8-fda2ed0a00c3", + "name": "IF documento gratis falló" + }, + { + "parameters": { + "jsCode": "const failedItems = $input.all();\nconst originalItems = $('Backup document input').all();\n\nreturn failedItems.map((failedItem, index) => {\n let originalIndex = failedItem.json?._fallbackIndex;\n\n if (originalIndex === undefined && failedItem.pairedItem) {\n if (Array.isArray(failedItem.pairedItem)) {\n originalIndex = failedItem.pairedItem[0]?.item;\n } else {\n originalIndex = failedItem.pairedItem.item;\n }\n }\n\n if (originalIndex === undefined) {\n originalIndex = index;\n }\n\n const original = originalItems[originalIndex] || originalItems[index] || originalItems[0];\n\n if (!original?.binary?.data) {\n throw new Error(`No se encontró binary.data para recuperar el documento en el índice ${originalIndex}`);\n }\n\n return {\n json: {\n ...original.json,\n fallbackUsed: true,\n fallbackReason: failedItem.json?.error || 'Error en API gratis',\n },\n binary: original.binary,\n };\n});" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 880, + 176 + ], + "id": "5a3884bb-fb6f-4bfa-89bd-1b281cb7555b", + "name": "Recuperar binario documento" + }, + { + "parameters": { + "modelId": { + "__rl": true, + "value": "models/gemini-3.1-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-3.1-flash-lite" + }, + "messages": { + "values": [ + { + "content": "=Actúa como un asistente de inteligencia artificial especializado en interpretación y estructuración de briefs creativos, estratégicos y comerciales.\n\nTu tarea es analizar todo el contenido recibido y transformarlo en un brief profesional, claro, accionable y listo para revisión interna, edición en Google Docs y representación visual en HTML.\n\nInstrucciones de trabajo:\n\n1. Captura e interpretación\n- Lee y consolida toda la información disponible.\n- Si existe texto derivado del audio, úsalo como contexto principal.\n- Si hay documentos, imágenes o referencias visuales, intégralos como soporte complementario.\n- Extrae entidades clave, objetivos, requerimientos, restricciones, entregables, fechas, audiencias, marcas, productos y cualquier dato relevante.\n- Distingue claramente entre:\n - Tipo de requerimiento = categoría general de la solicitud.\n - Tipo de entregable = pedido específico o salida concreta esperada.\n\n2. Detección de vacíos\n- Detecta campos obligatorios ausentes.\n- Identifica detalles técnicos faltantes.\n- Señala contradicciones o inconsistencias.\n- Marca información ambigua o poco clara.\n- Advierte posibles riesgos para el desarrollo creativo, estratégico o de producción.\n\n3. Enriquecimiento y estructuración\n- Reorganiza la información y conviértela en un brief profesional con redacción clara, precisa y ejecutiva.\n- Mejora la redacción sin inventar datos no proporcionados.\n- Cuando algo no esté disponible, indícalo como “No especificado” o “Pendiente de definir”.\n- Si no existe un nombre explícito para el proyecto, genera un nombre breve y profesional usando únicamente los datos reales disponibles, sin inventar información nueva.\n\n4. Reglas importantes de salida\n- No repitas información redundante.\n- No inventes datos.\n- Prioriza claridad, orden y utilidad práctica.\n- Redacta con tono profesional y ejecutivo.\n- La salida debe sentirse como un brief listo para revisión interna.\n- Debes incluir explícitamente los campos “Nombre del proyecto”, “Cliente”, “Marca”, “País”, “Tipo de requerimiento” y “Tipo de entregable”.\n- No mezcles “Tipo de requerimiento” con “Tipo de entregable”.\n- Si cliente y marca son la misma cosa, puedes repetir el valor en ambos campos.\n- Si no puedes identificar uno de los dos, usa “No especificado”.\n\n5. Genera la salida exactamente en este formato:\n\n1. Resumen ejecutivo\n- Explica de forma breve y profesional de qué trata la solicitud, cuál es el objetivo y qué se espera desarrollar.\n\n2. Información clave del brief\n- Nombre del proyecto\n- Cliente\n- Marca\n- País\n- Producto / servicio\n- Tipo de requerimiento\n- Tipo de entregable\n- Objetivo principal\n- Contexto / antecedente\n- Audiencia objetivo\n- Mensaje clave\n- Entregables solicitados\n- Canales / formatos\n- Requerimientos técnicos\n- Referencias creativas o visuales\n- Fechas o tiempos relevantes\n- Presupuesto\n- Restricciones o consideraciones especiales\n\n3. Vacíos detectados\n- Lista clara de campos faltantes, ambiguos o contradictorios.\n\n4. Riesgos o alertas\n- Señala riesgos que puedan afectar la ejecución, creatividad, tiempos, costos o claridad del proyecto.\n\n5. Recomendaciones rápidas\n- Sugiere qué información adicional conviene completar o reforzar antes de aprobar el brief final.\n\n6. Nivel de completitud\n- Indica un porcentaje estimado de completitud del brief según la información recibida.\n\nContenido recibido:\n{{$json.combined_content}}" + } + ] + }, + "builtInTools": {}, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 4128, + 592 + ], + "id": "a3c326a3-4e0b-4ae6-8a0f-6eca61648af3", + "name": "Message a model - Pago", + "credentials": { + "googlePalmApi": { + "id": "jvsXYwL6IOoY2DBU", + "name": "Isaac - Gemini Api Pago" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "0ccf935c-14e8-4e1b-b7db-cb994c970733", + "leftValue": "={{ !$json.content?.parts?.[0]?.text }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3872, + 736 + ], + "id": "0e8aa2ac-ef86-4451-adcb-d6bda7c07146", + "name": "If" + }, + { + "parameters": { + "operation": "append", + "documentId": { + "__rl": true, + "value": "1h_d4l4FYkLvyy_Zp7bEQFKpy5hx6pVj9xZ98mjMHHSU", + "mode": "list", + "cachedResultName": "CDC Brief - Base para Fulgencio", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1h_d4l4FYkLvyy_Zp7bEQFKpy5hx6pVj9xZ98mjMHHSU/edit?usp=drivesdk" + }, + "sheetName": { + "__rl": true, + "value": "gid=0", + "mode": "list", + "cachedResultName": "Briefs", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1h_d4l4FYkLvyy_Zp7bEQFKpy5hx6pVj9xZ98mjMHHSU/edit#gid=0" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "fecha_creacion": "={{ $json.fecha_creacion }}", + "brief_id": "={{ $json.brief_id }}", + "nombre_carpeta": "={{ $json.nombre_carpeta }}", + "enviado_por": "={{ $json.enviado_por }}", + "correo": "={{ $json.correo }}", + "link_carpeta_drive": "={{ $json.link_carpeta_drive }}", + "link_documento_brief": "={{ $json.link_documento_brief }}", + "estado": "={{ $json.estado }}", + "cantidad_audios": "={{ $json.cantidad_audios }}", + "cantidad_imagenes": "={{ $json.cantidad_imagenes }}", + "cantidad_documentos": "={{ $json.cantidad_documentos }}", + "cantidad_links": "={{ $json.cantidad_links }}", + "resumen_chatbot": "={{ $json.resumen_chatbot }}", + "texto_brief": "={{ $json.texto_brief }}", + "texto_busqueda": "={{ $json.texto_busqueda }}", + "fecha_entrega": "={{ $('Preparar fecha de entrega').first().json.deliveryDateLabel }}" + }, + "matchingColumns": [], + "schema": [ + { + "id": "fecha_creacion", + "displayName": "fecha_creacion", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "brief_id", + "displayName": "brief_id", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "nombre_carpeta", + "displayName": "nombre_carpeta", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "enviado_por", + "displayName": "enviado_por", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "correo", + "displayName": "correo", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "link_carpeta_drive", + "displayName": "link_carpeta_drive", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "link_documento_brief", + "displayName": "link_documento_brief", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "estado", + "displayName": "estado", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "cantidad_audios", + "displayName": "cantidad_audios", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "cantidad_imagenes", + "displayName": "cantidad_imagenes", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "cantidad_documentos", + "displayName": "cantidad_documentos", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "cantidad_links", + "displayName": "cantidad_links", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "resumen_chatbot", + "displayName": "resumen_chatbot", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "texto_brief", + "displayName": "texto_brief", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "texto_busqueda", + "displayName": "texto_busqueda", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "fecha_entrega", + "displayName": "fecha_entrega", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": false + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "options": {} + }, + "type": "n8n-nodes-base.googleSheets", + "typeVersion": 4.7, + "position": [ + 8320, + 688 + ], + "id": "2b332c13-0621-4eb0-a9b7-27e820963746", + "name": "Agregar fila a Sheet Fulgencio", + "retryOnFail": false, + "alwaysOutputData": true, + "credentials": { + "googleSheetsOAuth2Api": { + "id": "K0hDZh3a85MpOHCs", + "name": "Google Sheets account 2" + } + }, + "onError": "continueRegularOutput" + }, + { + "parameters": { + "jsCode": "function getNodeJson(nodeName) {\n try {\n return $(nodeName).first().json || {};\n } catch (error) {\n return {};\n }\n}\n\nfunction getNodeBinary(nodeName) {\n try {\n return $(nodeName).first().binary || {};\n } catch (error) {\n return {};\n }\n}\n\nfunction cleanText(value) {\n return String(value || '')\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction limitText(value, maxLength = 45000) {\n const text = String(value || '');\n return text.length > maxLength ? text.slice(0, maxLength) : text;\n}\n\nfunction parseLinks(value) {\n if (!value) return [];\n\n if (Array.isArray(value)) {\n return value.filter(Boolean).map(String);\n }\n\n return String(value)\n .split(/\\n|,|\\s+/)\n .map(link => link.trim())\n .filter(link => /^https?:\\/\\//i.test(link));\n}\n\nconst webhookItem = getNodeJson('Webhook');\nconst webhookBinary = getNodeBinary('Webhook');\n\nconst body =\n webhookItem.body && Object.keys(webhookItem.body).length\n ? webhookItem.body\n : webhookItem;\n\nconst folderNode = getNodeJson('Create folder');\nconst docNode = getNodeJson('Copy template');\nconst finalTextNode = getNodeJson('Code in JavaScript4');\nconst fechaEntregaNode = getNodeJson('Preparar fecha de entrega');\n\nconst folderId = folderNode.id || '';\nconst folderName = folderNode.name || '';\nconst docId = docNode.id || '';\n\nconst folderUrl = folderId\n ? `https://drive.google.com/drive/u/0/folders/${folderId}`\n : '';\n\nconst docUrl = docId\n ? `https://docs.google.com/document/d/${docId}/edit`\n : '';\n\nconst submittedByName =\n body.submittedByName ||\n body.displayName ||\n body.name ||\n body.email ||\n 'No especificado';\n\nconst submittedByEmail =\n body.submittedByEmail ||\n body.email ||\n 'No especificado';\n\nconst fechaEntrega =\n fechaEntregaNode.deliveryDateLabel ||\n fechaEntregaNode.fechaEntregaTexto ||\n body.deliveryDateLabel ||\n body.fechaEntregaTexto ||\n 'Por confirmar';\n\nconst finalBriefText =\n finalTextNode.googleDocsText ||\n finalTextNode.text ||\n finalTextNode.content ||\n '';\n\nconst files = Object.values(webhookBinary || {});\n\nconst audioCount = files.filter(file =>\n String(file.mimeType || '').startsWith('audio/')\n).length;\n\nconst imageCount = files.filter(file =>\n String(file.mimeType || '').startsWith('image/')\n).length;\n\nconst documentCount = files.filter(file => {\n const mime = String(file.mimeType || '');\n return (\n mime.includes('pdf') ||\n mime.includes('word') ||\n mime.includes('excel') ||\n mime.includes('spreadsheet') ||\n mime.includes('presentation') ||\n mime.includes('powerpoint') ||\n mime.includes('officedocument')\n );\n}).length;\n\nconst links = [\n ...parseLinks(body.links),\n ...parseLinks(body.enlaces),\n ...parseLinks(body.referenceLinks),\n ...parseLinks(body.referencias),\n ...parseLinks(body.urls),\n];\n\nconst fechaCreacion = new Date().toISOString();\n\nconst resumenChatbot = cleanText(finalBriefText).slice(0, 1200);\n\nconst textoBusqueda = [\n `Brief CDC`,\n `Fecha: ${fechaCreacion}`,\n `Fecha límite de entrega: ${fechaEntrega}`,\n `Enviado por: ${submittedByName}`,\n `Correo: ${submittedByEmail}`,\n `Carpeta: ${folderUrl}`,\n `Documento: ${docUrl}`,\n `Nombre carpeta: ${folderName}`,\n '',\n finalBriefText,\n].join('\\n');\n\nreturn [\n {\n json: {\n fecha_creacion: fechaCreacion,\n brief_id: folderId || `${Date.now()}`,\n nombre_carpeta: folderName,\n enviado_por: submittedByName,\n correo: submittedByEmail,\n fecha_entrega: fechaEntrega,\n link_carpeta_drive: folderUrl,\n link_documento_brief: docUrl,\n estado: 'Nuevo',\n cantidad_audios: audioCount,\n cantidad_imagenes: imageCount,\n cantidad_documentos: documentCount,\n cantidad_links: links.length,\n resumen_chatbot: resumenChatbot,\n texto_brief: limitText(finalBriefText),\n texto_busqueda: limitText(textoBusqueda),\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 8112, + 688 + ], + "id": "b44ed7d4-7333-44b6-b46f-d3d50a922eae", + "name": "Preparar fila para Sheet Fulgencio" + }, + { + "parameters": { + "jsCode": "const inputItem = $input.first();\nconst inputJson = inputItem.json || {};\n\nconst webhookJson = $('Webhook').first().json || {};\nconst body = webhookJson.body || {};\n\nfunction formatDateEs(dateString) {\n if (!dateString) return '';\n\n const value = String(dateString).trim();\n\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n return value;\n }\n\n const [year, month, day] = value.split('-');\n const date = new Date(Number(year), Number(month) - 1, Number(day));\n\n if (Number.isNaN(date.getTime())) {\n return value;\n }\n\n return date.toLocaleDateString('es-DO', {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n });\n}\n\nconst deliveryDateRaw =\n body.deliveryDate ||\n body.fechaEntrega ||\n '';\n\nconst deliveryDateStatusRaw =\n body.deliveryDateStatus ||\n body.fechaEntregaEstado ||\n '';\n\nconst deliveryDateLabelFromFrontend =\n body.deliveryDateLabel ||\n body.fechaEntregaTexto ||\n '';\n\nconst hasDate =\n deliveryDateRaw !== undefined &&\n deliveryDateRaw !== null &&\n String(deliveryDateRaw).trim() !== '';\n\nconst normalizedStatus = String(deliveryDateStatusRaw || '').toLowerCase();\nconst normalizedLabel = String(deliveryDateLabelFromFrontend || '').toLowerCase();\n\nconst isPending =\n !hasDate &&\n (\n normalizedStatus === 'pending' ||\n normalizedStatus === 'por_confirmar' ||\n normalizedStatus === 'por confirmar' ||\n normalizedLabel === 'por confirmar'\n );\n\nconst deliveryDateLabel = isPending\n ? 'Por confirmar'\n : hasDate\n ? formatDateEs(deliveryDateRaw)\n : deliveryDateLabelFromFrontend || 'Por confirmar';\n\nreturn [\n {\n json: {\n ...inputJson,\n deliveryDateRaw,\n deliveryDateStatus: isPending ? 'pending' : 'defined',\n deliveryDateLabel,\n fechaEntregaTexto: deliveryDateLabel,\n fechaEntregaEstado: isPending ? 'por_confirmar' : 'definida',\n },\n binary: inputItem.binary,\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -1008, + 688 + ], + "id": "dabcfbdd-a1db-4241-950e-6259a27586b9", + "name": "Preparar fecha de entrega" + }, + { + "parameters": { + "jsCode": "const item = $input.first();\nconst json = item.json || {};\n\nconst fechaNode = $('Preparar fecha de entrega').first().json || {};\n\nconst deliveryDateLabel =\n fechaNode.deliveryDateLabel ||\n fechaNode.fechaEntregaTexto ||\n json.deliveryDateLabel ||\n json.fechaEntregaTexto ||\n 'Por confirmar';\n\nconst deliveryLine = `• Fecha límite de entrega: ${deliveryDateLabel}.`;\n\nlet googleDocsText = json.googleDocsText || '';\n\nif (!googleDocsText) {\n return [\n {\n json: {\n ...json,\n deliveryDateLabel,\n fechaEntregaTexto: deliveryDateLabel,\n },\n binary: item.binary,\n },\n ];\n}\n\n// Evita duplicar la fecha si el nodo se ejecuta más de una vez\ngoogleDocsText = googleDocsText.replace(\n /^\\s*[•-]?\\s*Fecha límite de entrega:.*$/gim,\n ''\n);\n\n// Inserta la fecha justo debajo de \"Fechas / tiempos relevantes\"\nconst fechasHeadingRegex = /(Fechas\\s*\\/\\s*tiempos\\s+relevantes\\s*:?\\s*)/i;\n\nif (fechasHeadingRegex.test(googleDocsText)) {\n googleDocsText = googleDocsText.replace(\n fechasHeadingRegex,\n `$1\\n${deliveryLine}\\n`\n );\n} else {\n // Si por alguna razón la sección no aparece, la agrega al final\n googleDocsText += `\\n\\nFechas / tiempos relevantes\\n${deliveryLine}`;\n}\n\n// Limpieza básica de saltos de línea excesivos\ngoogleDocsText = googleDocsText\n .replace(/\\n{4,}/g, '\\n\\n\\n')\n .trim();\n\nreturn [\n {\n json: {\n ...json,\n googleDocsText,\n deliveryDateLabel,\n fechaEntregaTexto: deliveryDateLabel,\n },\n binary: item.binary,\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5104, + 688 + ], + "id": "084305a2-38dd-4b3f-b900-6a3f7c26c69c", + "name": "Agregar fecha al Google Doc" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Create folder", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch": { + "main": [ + [ + { + "node": "Procesar documentos", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Backup image input", + "type": "main", + "index": 0 + } + ], + [], + [ + { + "node": "Backup audio input", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze audio": { + "main": [ + [ + { + "node": "If audio gratis falló", + "type": "main", + "index": 0 + } + ] + ] + }, + "Merge": { + "main": [ + [ + { + "node": "Code in JavaScript1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 1 + } + ] + ] + }, + "Message a model": { + "main": [ + [ + { + "node": "If", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze an image": { + "main": [ + [ + { + "node": "IF imagen gratis falló", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create folder": { + "main": [ + [ + { + "node": "Preparar fecha de entrega", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields2": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 2 + } + ] + ] + }, + "Share folder": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "Respond to Webhook": { + "main": [ + [ + { + "node": "Send a message", + "type": "main", + "index": 0 + } + ] + ] + }, + "Copy template": { + "main": [ + [ + { + "node": "HTTP Request", + "type": "main", + "index": 0 + } + ] + ] + }, + "Build Docs Requests": { + "main": [ + [ + { + "node": "Build Names", + "type": "main", + "index": 0 + } + ] + ] + }, + "HTTP Request": { + "main": [ + [ + { + "node": "Share folder", + "type": "main", + "index": 0 + } + ] + ] + }, + "Build Names": { + "main": [ + [ + { + "node": "Rename folder", + "type": "main", + "index": 0 + } + ] + ] + }, + "Rename folder": { + "main": [ + [ + { + "node": "Copy template", + "type": "main", + "index": 0 + } + ] + ] + }, + "If PDF missing": { + "main": [ + [ + { + "node": "PDF opcional omitido", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Backup document input", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze document": { + "main": [ + [ + { + "node": "IF documento gratis falló", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields documento": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 0 + } + ] + ] + }, + "PDF opcional omitido": { + "main": [ + [ + { + "node": "Edit Fields documento", + "type": "main", + "index": 0 + } + ] + ] + }, + "Procesar documentos": { + "main": [ + [ + { + "node": "If PDF missing", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Code in JavaScript3", + "type": "main", + "index": 0 + }, + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript3": { + "main": [ + [ + { + "node": "Upload file", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript1": { + "main": [ + [ + { + "node": "Message a model", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript4": { + "main": [ + [ + { + "node": "Agregar fecha al Google Doc", + "type": "main", + "index": 0 + } + ] + ] + }, + "Backup document input": { + "main": [ + [ + { + "node": "Analyze document", + "type": "main", + "index": 0 + } + ] + ] + }, + "Backup audio input": { + "main": [ + [ + { + "node": "Analyze audio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Backup image input": { + "main": [ + [ + { + "node": "Analyze an image", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF imagen gratis falló": { + "main": [ + [ + { + "node": "Recuperar binario imagen", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Recuperar binario imagen": { + "main": [ + [ + { + "node": "Analyze an image - Pago", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze an image - Pago": { + "main": [ + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "If audio gratis falló": { + "main": [ + [ + { + "node": "Recuperar binario audio", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Recuperar binario audio": { + "main": [ + [ + { + "node": "Analyze audio - Pago", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze audio - Pago": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF documento gratis falló": { + "main": [ + [ + { + "node": "Recuperar binario documento", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Edit Fields documento", + "type": "main", + "index": 0 + } + ] + ] + }, + "Recuperar binario documento": { + "main": [ + [ + { + "node": "Analyze document - Pago", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze document - Pago": { + "main": [ + [ + { + "node": "Edit Fields documento", + "type": "main", + "index": 0 + } + ] + ] + }, + "If": { + "main": [ + [ + { + "node": "Message a model - Pago", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code in JavaScript4", + "type": "main", + "index": 0 + } + ] + ] + }, + "Message a model - Pago": { + "main": [ + [ + { + "node": "Code in JavaScript4", + "type": "main", + "index": 0 + } + ] + ] + }, + "Send a message": { + "main": [ + [ + { + "node": "Preparar fila para Sheet Fulgencio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar fila para Sheet Fulgencio": { + "main": [ + [ + { + "node": "Agregar fila a Sheet Fulgencio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar fecha de entrega": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Agregar fecha al Google Doc": { + "main": [ + [ + { + "node": "Build Docs Requests", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "timeSavedMode": "fixed", + "errorWorkflow": "puF4LUczoSz3hcek", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "versionId": "b4841850-e8b9-493f-a1fb-7070136942be", + "activeVersionId": "b4841850-e8b9-493f-a1fb-7070136942be", + "versionCounter": 115, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-05-25T16:49:10.432Z", + "createdAt": "2026-05-25T16:49:10.432Z", + "role": "workflow:owner", + "workflowId": "Z8W67i7RfrhXRDFQ", + "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-01T20:03:46.000Z", + "createdAt": "2026-06-01T20:03:24.005Z", + "versionId": "b4841850-e8b9-493f-a1fb-7070136942be", + "workflowId": "Z8W67i7RfrhXRDFQ", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "cdc-brief-test-metadata", + "responseMode": "responseNode", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1488, + 688 + ], + "id": "3ea7e4f5-8bf3-4591-953f-e9a853e871fe", + "name": "Webhook", + "webhookId": "0459b7fe-a6fc-47cb-bcf1-afadb9587124" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "leftValue": "={{$json.type}} ", + "rightValue": "pdf_document", + "operator": { + "type": "string", + "operation": "equals" + }, + "id": "19494f94-55a5-4849-bdc0-e056140e371d" + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "PDF" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "9c4aa96c-5b3c-46bb-a2f7-a10da4c807be", + "leftValue": "={{$json.type}}", + "rightValue": "image", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Imagen" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "3d1a3c24-4fe2-4239-9b64-2a33c3043fb0", + "leftValue": "={{$('Code in JavaScript').item.json.type}}", + "rightValue": "video", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Video" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "29941bad-2e02-4c26-bccb-115fbb2c4b57", + "leftValue": "={{$json.type}}", + "rightValue": "audio", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Audio" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "8ff5d5cf-26e7-4516-a154-0d7fa8855012", + "leftValue": "={{$json.type}}", + "rightValue": "text", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Texto" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "437f4ff8-8556-4f8f-b935-a55eda65afde", + "leftValue": "={{$json.type}}", + "rightValue": "other", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "other" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.4, + "position": [ + -624, + 576 + ], + "id": "93f970e0-be35-4a98-9ecd-b962e6549589", + "name": "Switch" + }, + { + "parameters": { + "resource": "audio", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 800, + 688 + ], + "id": "7b3d0637-9b3c-4daf-8615-87455ab23905", + "name": "Analyze audio", + "retryOnFail": false, + "credentials": { + "googlePalmApi": { + "id": "6Jdcd9FONxG35YuJ", + "name": "Isaac - Gemini Api Gratis" + } + } + }, + { + "parameters": { + "numberInputs": 3 + }, + "type": "n8n-nodes-base.merge", + "typeVersion": 3.2, + "position": [ + 3056, + 736 + ], + "id": "5f1ad3e3-3ab2-40c8-a694-e7e5755322ce", + "name": "Merge" + }, + { + "parameters": { + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash-lite" + }, + "messages": { + "values": [ + { + "content": "=Actúa como un asistente de inteligencia artificial especializado en interpretación y estructuración de briefs creativos, estratégicos y comerciales.\n\nTu tarea es analizar todo el contenido recibido y transformarlo en un brief profesional, claro, accionable y listo para revisión interna, edición en Google Docs y representación visual en HTML.\n\nInstrucciones de trabajo:\n\n1. Captura e interpretación\n- Lee y consolida toda la información disponible.\n- Si existe texto derivado del audio, úsalo como contexto principal.\n- Si hay documentos, imágenes o referencias visuales, intégralos como soporte complementario.\n- Extrae entidades clave, objetivos, requerimientos, restricciones, entregables, fechas, audiencias, marcas, productos y cualquier dato relevante.\n- Distingue claramente entre:\n - Tipo de requerimiento = categoría general de la solicitud.\n - Tipo de entregable = pedido específico o salida concreta esperada.\n\n2. Detección de vacíos\n- Detecta campos obligatorios ausentes.\n- Identifica detalles técnicos faltantes.\n- Señala contradicciones o inconsistencias.\n- Marca información ambigua o poco clara.\n- Advierte posibles riesgos para el desarrollo creativo, estratégico o de producción.\n\n3. Enriquecimiento y estructuración\n- Reorganiza la información y conviértela en un brief profesional con redacción clara, precisa y ejecutiva.\n- Mejora la redacción sin inventar datos no proporcionados.\n- Cuando algo no esté disponible, indícalo como “No especificado” o “Pendiente de definir”.\n- Si no existe un nombre explícito para el proyecto, genera un nombre breve y profesional usando únicamente los datos reales disponibles, sin inventar información nueva.\n\n4. Reglas importantes de salida\n- No repitas información redundante.\n- No inventes datos.\n- Prioriza claridad, orden y utilidad práctica.\n- Redacta con tono profesional y ejecutivo.\n- La salida debe sentirse como un brief listo para revisión interna.\n- Debes incluir explícitamente los campos “Nombre del proyecto”, “Cliente”, “Marca”, “País”, “Tipo de requerimiento” y “Tipo de entregable”.\n- No mezcles “Tipo de requerimiento” con “Tipo de entregable”.\n- Si cliente y marca son la misma cosa, puedes repetir el valor en ambos campos.\n- Si no puedes identificar uno de los dos, usa “No especificado”.\n\n5. Genera la salida exactamente en este formato:\n\n1. Resumen ejecutivo\n- Explica de forma breve y profesional de qué trata la solicitud, cuál es el objetivo y qué se espera desarrollar.\n\n2. Información clave del brief\n- Nombre del proyecto\n- Cliente\n- Marca\n- País\n- Producto / servicio\n- Tipo de requerimiento\n- Tipo de entregable\n- Objetivo principal\n- Contexto / antecedente\n- Audiencia objetivo\n- Mensaje clave\n- Entregables solicitados\n- Canales / formatos\n- Requerimientos técnicos\n- Referencias creativas o visuales\n- Fechas o tiempos relevantes\n- Presupuesto\n- Restricciones o consideraciones especiales\n\n3. Vacíos detectados\n- Lista clara de campos faltantes, ambiguos o contradictorios.\n\n4. Riesgos o alertas\n- Señala riesgos que puedan afectar la ejecución, creatividad, tiempos, costos o claridad del proyecto.\n\n5. Recomendaciones rápidas\n- Sugiere qué información adicional conviene completar o reforzar antes de aprobar el brief final.\n\n6. Nivel de completitud\n- Indica un porcentaje estimado de completitud del brief según la información recibida.\n\nContenido recibido:\n{{$json.combined_content}}" + } + ] + }, + "builtInTools": {}, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 3440, + 752 + ], + "id": "2fb18bfd-e222-4107-a98c-fb6f3b6ab295", + "name": "Message a model", + "credentials": { + "googlePalmApi": { + "id": "6Jdcd9FONxG35YuJ", + "name": "Isaac - Gemini Api Gratis" + } + } + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "ddc91110-0c3b-4ee7-a973-322a3d1c7c1e", + "name": "type", + "value": "audio", + "type": "string" + }, + { + "id": "b667804d-e4b9-4225-9a6e-01d54067f0cf", + "name": "fileName", + "value": "={{$json.fileName || ''}}", + "type": "string" + }, + { + "id": "4023fd4f-95ce-4571-845b-c7555c4efc88", + "name": "content_summary", + "value": "={{ $json.content.parts[0].text || '' }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 1888, + 672 + ], + "id": "a6b8547f-cd75-49b4-bf2f-cbe205a0d465", + "name": "Edit Fields1" + }, + { + "parameters": { + "respondWith": "text", + "responseBody": "=Este es el enlace al folder con el contenido del brief:\n\nhttps://drive.google.com/drive/u/0/folders/{{ $('Create folder').first().json.id }}\n\nEn caso de algún vacío detectado, favor de ingresar al link y modificar el archivo.\n\nVacíos detectados:\n\n{{ $('Build Docs Requests').first().json.fields.VACIOS_DETECTADOS || \"Ninguno\" }}", + "options": {} + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.5, + "position": [ + 7520, + 688 + ], + "id": "fccc258d-d736-4d05-9f32-221921093e49", + "name": "Respond to Webhook" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 816, + 928 + ], + "id": "91da2758-fb1e-4a2e-ad21-e08b5c54a0a7", + "name": "Analyze an image", + "retryOnFail": false, + "credentials": { + "googlePalmApi": { + "id": "6Jdcd9FONxG35YuJ", + "name": "Isaac - Gemini Api Gratis" + } + } + }, + { + "parameters": { + "resource": "folder", + "name": "=Brief {{$now}}", + "driveId": { + "__rl": true, + "mode": "list", + "value": "My Drive" + }, + "folderId": { + "__rl": true, + "mode": "list", + "value": "root", + "cachedResultName": "/ (Root folder)" + }, + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + -1328, + 688 + ], + "id": "7de162cd-4388-436e-b870-1de9fafad3a8", + "name": "Create folder", + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "738bec56-f577-41da-a35f-dc8346893e10", + "name": "fileName", + "value": "={{ $('Switch').item.json.fileName || '' }}", + "type": "string" + }, + { + "id": "6df83b75-7e13-48db-a557-7ee85c52a2b2", + "name": "content_summary", + "value": "={{ $json.content?.parts?.[0]?.text || 'Imagen adjunta por el usuario. No se pudo generar una descripción automática en este intento.' }}", + "type": "string" + }, + { + "id": "1a1db20c-3dd7-4776-9d97-46bd31213a87", + "name": "type", + "value": "image", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2080, + 960 + ], + "id": "ba2df39a-fe08-4b73-9ece-d1c273a710dd", + "name": "Edit Fields2" + }, + { + "parameters": { + "resource": "folder", + "operation": "share", + "folderNoRootId": { + "__rl": true, + "value": "={{ $('Create folder').first().json.id }}", + "mode": "id" + }, + "permissionsUi": { + "permissionsValues": { + "role": "writer", + "type": "domain", + "domain": "gomezleemarketing.com" + } + }, + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + 7104, + 688 + ], + "id": "8217b8c5-f406-45af-b929-94b9aa60d387", + "name": "Share folder", + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "name": "={{ $json.fileName }}", + "driveId": { + "__rl": true, + "mode": "list", + "value": "My Drive" + }, + "folderId": { + "__rl": true, + "value": "={{ $json.folderId }}", + "mode": "id" + }, + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + 1200, + 1392 + ], + "id": "9b472aac-ac5e-4e33-bef7-c676de10d699", + "name": "Upload file", + "executeOnce": false, + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "sendTo": "gmarrero@gomezleemarketing.com, obrisceno@gomezleemarketing.com, boliva@gomezleemarketing.com", + "subject": "✏️ Nuevo Brief", + "message": "=
\n Enviado por {{ $('Webhook').first().json.body.submittedByName || 'No especificado' }}\n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ \n
\n\n
\n
\n\n
\n\n
\n
\n Se ha creado un nuevo brief\n
\n
\n\n
\n

\n Hola Gerardo,\n

\n\n

\n Te informo que se ha creado el siguiente brief:\n

\n\n
\n
\n Carpeta del brief\n
\n\n \n Abrir carpeta en Google Drive\n \n
\n\n
\n
\n
\n Datos del envío\n
\n
\n\n
\n
\n
\n Enviado por\n
\n\n
\n {{ $('Webhook').first().json.body.submittedByName || 'No especificado' }}\n
\n
\n\n \n\n
\n
\n Fecha límite de entrega\n
\n\n
\n {{ $('Preparar fecha de entrega').first().json.deliveryDateLabel || $('Preparar fecha de entrega').first().json.fechaEntregaTexto || $('Webhook').first().json.body.deliveryDateLabel || $('Webhook').first().json.body.fechaEntregaTexto || 'Por confirmar' }}\n
\n
\n
\n
\n\n

\n Gracias,\n

\n
\n\n
\n\n\n
\n
\n", + "options": { + "senderName": "📢 HEY" + } + }, + "type": "n8n-nodes-base.gmail", + "typeVersion": 2.2, + "position": [ + 7872, + 688 + ], + "id": "14e8c6c6-d1c1-48f1-80c6-efb34420f33d", + "name": "Send a message", + "webhookId": "2a920b9f-c697-4a4e-9c69-0bf0749c2661", + "credentials": { + "gmailOAuth2": { + "id": "cehQBe6X1BQLmcD9", + "name": "Gmail account - Isaac" + } + } + }, + { + "parameters": { + "operation": "copy", + "fileId": { + "__rl": true, + "value": "1UuCLClREoC_9OvT407B36nHJ1x4l418Q0dEwgGccws4", + "mode": "list", + "cachedResultName": "CDC_Brief_GLM_V2", + "cachedResultUrl": "https://docs.google.com/document/d/1UuCLClREoC_9OvT407B36nHJ1x4l418Q0dEwgGccws4/edit?usp=drivesdk" + }, + "name": "={{ $('Build Names').first().json.docName }}", + "sameFolder": false, + "driveId": { + "__rl": true, + "mode": "list", + "value": "My Drive" + }, + "folderId": { + "__rl": true, + "value": "={{ $('Create folder').first().json.id }}", + "mode": "id" + }, + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + 6432, + 688 + ], + "id": "e2dc1490-d155-4def-bb21-39195d6e8908", + "name": "Copy template", + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "jsCode": "const text = String($json.googleDocsText || '').replace(/\\r\\n/g, '\\n').trim();\nconst ANO = String(new Date().getFullYear());\n\nconst baseItems = $('Code in JavaScript').all();\n\nconst urlRegex = /https?:\\/\\/[^\\s<>\"')]+/gi;\nconst linksSet = new Set();\nconst attachmentsSet = new Set();\n\nfor (const item of baseItems) {\n const json = item.json || {};\n\n const textValue = String(json.text || '').trim();\n const matches = textValue.match(urlRegex) || [];\n for (const url of matches) {\n linksSet.add(url);\n }\n\n const fileName = String(json.fileName || '').trim();\n if (fileName) {\n attachmentsSet.add(fileName);\n }\n}\n\nconst support = {\n enlacesReferenciaText: linksSet.size\n ? Array.from(linksSet).map(url => `• ${url}`).join('\\n')\n : 'No se compartieron enlaces de referencia online.',\n\n adjuntosRecibidosText: attachmentsSet.size\n ? Array.from(attachmentsSet).map(name => `• ${name}`).join('\\n')\n : 'No se recibieron adjuntos adicionales.'\n};\n\nfunction getNodeJson(nodeName) {\n try {\n return $(nodeName).first().json || {};\n } catch (error) {\n return {};\n }\n}\n\nfunction sectionBetween(startRe, endRe) {\n const lines = text.split('\\n');\n let start = -1;\n let end = lines.length;\n\n for (let i = 0; i < lines.length; i++) {\n if (startRe.test(lines[i].trim())) {\n start = i + 1;\n break;\n }\n }\n\n if (start === -1) return '';\n\n if (endRe) {\n for (let i = start; i < lines.length; i++) {\n if (endRe.test(lines[i].trim())) {\n end = i;\n break;\n }\n }\n }\n\n return lines.slice(start, end).join('\\n').trim();\n}\n\nfunction normalizeLabel(str) {\n return String(str || '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction cleanValue(value, fallback = 'No especificado') {\n const lines = String(value || '')\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n\n return lines.length ? lines.join('\\n') : fallback;\n}\n\nfunction formatMultiline(value, fallback = 'No especificado') {\n const lines = String(value || '')\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n\n if (!lines.length) return fallback;\n\n return lines\n .map(line => {\n if (/^[•*\\-–]\\s*/.test(line)) {\n return `• ${line.replace(/^[•*\\-–]\\s*/, '').trim()}`;\n }\n return line;\n })\n .join('\\n');\n}\n\nfunction bullets(sectionText, fallback = 'No especificado') {\n const items = String(sectionText || '')\n .split('\\n')\n .map(line => line.replace(/^\\s*[•*\\-–]\\s*/, '').trim())\n .filter(Boolean);\n\n if (!items.length) return fallback;\n\n return items.map(item => `• ${item}`).join('\\n');\n}\n\nfunction completion(sectionText) {\n const match = String(sectionText || '').match(/(\\d{1,3}\\s*%)/);\n if (match) return match[1].replace(/\\s+/g, '');\n\n const cleaned = String(sectionText || '').trim();\n return cleaned || 'No especificado';\n}\n\nfunction buildFechasTiemposRelevantes(fechaEntrega, fechasDetectadas) {\n const fecha = String(fechaEntrega || '').trim() || 'Por confirmar';\n const fechas = String(fechasDetectadas || '').trim();\n\n const entregaLine = `• Fecha límite de entrega: ${fecha}.`;\n\n const fechasNormalizadas = normalizeLabel(fechas)\n .replace(/[.:;]+$/g, '')\n .trim();\n\n const noHayFechasDetectadas =\n !fechas ||\n fechasNormalizadas === 'no especificado' ||\n fechasNormalizadas === 'no especificada' ||\n fechasNormalizadas === 'no se especifico' ||\n fechasNormalizadas === 'no se especifica' ||\n fechasNormalizadas === 'no indicado' ||\n fechasNormalizadas === 'no indicada';\n\n if (noHayFechasDetectadas) {\n return entregaLine;\n }\n\n return `${entregaLine}\\n${fechas}`;\n}\n\nconst fechaEntregaNode = getNodeJson('Preparar fecha de entrega');\n\nconst fechaEntrega =\n fechaEntregaNode.deliveryDateLabel ||\n fechaEntregaNode.fechaEntregaTexto ||\n 'Por confirmar';\n\nconst infoSection = sectionBetween(\n /^2\\.\\s*Informaci[oó]n clave del brief$/i,\n /^3\\.\\s*Vac[ií]os\\s+detectados$/i\n);\n\nconst labelMap = {\n 'Nombre del proyecto': 'NOMBRE_PROYECTO',\n 'Cliente': 'CLIENTE',\n 'Marca': 'MARCA',\n 'País': 'PAIS',\n 'Pais': 'PAIS',\n 'Producto / servicio': 'PRODUCTO_SERVICIO',\n 'Tipo de requerimiento': 'TIPO_REQUERIMIENTO',\n 'Tipo de entregable': 'TIPO_ENTREGABLE',\n 'Objetivo principal': 'OBJETIVO_PRINCIPAL',\n 'Contexto / antecedente': 'CONTEXTO_ANTECEDENTE',\n 'Audiencia objetivo': 'AUDIENCIA_OBJETIVO',\n 'Mensaje clave': 'MENSAJE_CLAVE',\n 'Entregables solicitados': 'ENTREGABLES',\n 'Canales / formatos': 'CANALES_FORMATOS',\n 'Requerimientos técnicos': 'REQUERIMIENTOS_TECNICOS',\n 'Referencias creativas o visuales': 'REFERENCIAS_VISUALES',\n 'Fechas o tiempos relevantes': 'FECHAS_TIEMPOS_RELEVANTES',\n 'Fechas / tiempos relevantes': 'FECHAS_TIEMPOS_RELEVANTES',\n 'Presupuesto': 'PRESUPUESTO',\n 'Restricciones o consideraciones especiales': 'RESTRICCIONES'\n};\n\nconst normalizedLabelMap = Object.fromEntries(\n Object.entries(labelMap).map(([label, key]) => [normalizeLabel(label), key])\n);\n\nconst info = {};\nlet currentKey = null;\n\nfor (const rawLine of infoSection.split('\\n')) {\n const line = rawLine.trim();\n if (!line) continue;\n\n const match = line.match(/^(?:[•*\\-–]\\s*)?(.+?):\\s*(.*)$/);\n\n if (match) {\n const labelCandidate = normalizeLabel(match[1]);\n const mappedKey = normalizedLabelMap[labelCandidate];\n\n if (mappedKey) {\n currentKey = mappedKey;\n info[currentKey] = match[2].trim();\n continue;\n }\n }\n\n if (currentKey) {\n info[currentKey] = [info[currentKey], line].filter(Boolean).join('\\n');\n }\n}\n\nconst CLIENTE = cleanValue(info.CLIENTE);\nconst MARCA = cleanValue(info.MARCA);\n\nfunction buildMarcaCliente(cliente, marca) {\n const c = cleanValue(cliente);\n const m = cleanValue(marca);\n\n if (c === 'No especificado' && m === 'No especificado') return 'No especificado';\n if (c === 'No especificado') return m;\n if (m === 'No especificado') return c;\n\n const nc = normalizeLabel(c);\n const nm = normalizeLabel(m);\n\n if (nc === nm) return c.length >= m.length ? c : m;\n\n if (nc.includes(nm) || nm.includes(nc)) {\n return c.length >= m.length ? c : m;\n }\n\n return `${m} / ${c}`;\n}\n\nconst MARCA_CLIENTE = buildMarcaCliente(CLIENTE, MARCA);\n\nconst fechasDetectadas = formatMultiline(info.FECHAS_TIEMPOS_RELEVANTES);\n\nconst fields = {\n ANO,\n NOMBRE_PROYECTO: cleanValue(info.NOMBRE_PROYECTO),\n CLIENTE,\n MARCA,\n MARCA_CLIENTE,\n\n PAIS: cleanValue(info.PAIS),\n RESUMEN_EJECUTIVO: sectionBetween(\n /^1\\.\\s*Resumen ejecutivo$/i,\n /^2\\.\\s*Informaci[oó]n clave del brief$/i\n ) || 'No especificado',\n\n PRODUCTO_SERVICIO: cleanValue(info.PRODUCTO_SERVICIO),\n TIPO_REQUERIMIENTO: cleanValue(info.TIPO_REQUERIMIENTO),\n TIPO_ENTREGABLE: cleanValue(info.TIPO_ENTREGABLE),\n OBJETIVO_PRINCIPAL: cleanValue(info.OBJETIVO_PRINCIPAL),\n CONTEXTO_ANTECEDENTE: cleanValue(info.CONTEXTO_ANTECEDENTE),\n AUDIENCIA_OBJETIVO: cleanValue(info.AUDIENCIA_OBJETIVO),\n MENSAJE_CLAVE: cleanValue(info.MENSAJE_CLAVE),\n\n ENTREGABLES: formatMultiline(info.ENTREGABLES),\n CANALES_FORMATOS: formatMultiline(info.CANALES_FORMATOS),\n REQUERIMIENTOS_TECNICOS: formatMultiline(info.REQUERIMIENTOS_TECNICOS),\n REFERENCIAS_VISUALES: formatMultiline(info.REFERENCIAS_VISUALES),\n\n FECHAS_TIEMPOS_RELEVANTES: buildFechasTiemposRelevantes(\n fechaEntrega,\n fechasDetectadas\n ),\n\n PRESUPUESTO: cleanValue(info.PRESUPUESTO),\n RESTRICCIONES: formatMultiline(info.RESTRICCIONES),\n\n VACIOS_DETECTADOS: bullets(\n sectionBetween(\n /^3\\.\\s*Vac[ií]os\\s+detectados$/i,\n /^4\\.\\s*Riesgos o alertas$/i\n )\n ),\n\n RIESGOS_ALERTAS: bullets(\n sectionBetween(\n /^4\\.\\s*Riesgos o alertas$/i,\n /^5\\.\\s*Recomendaciones rápidas$/i\n )\n ),\n\n RECOMENDACIONES_RAPIDAS: bullets(\n sectionBetween(\n /^5\\.\\s*Recomendaciones rápidas$/i,\n /^6\\.\\s*Nivel de completitud$/i\n )\n ),\n\n NIVEL_COMPLETITUD: completion(\n sectionBetween(\n /^6\\.\\s*Nivel de completitud$/i,\n null\n )\n ),\n\n ENLACES_REFERENCIA: support.enlacesReferenciaText || 'No se compartieron enlaces de referencia online.',\n ADJUNTOS_RECIBIDOS: support.adjuntosRecibidosText || 'No se recibieron adjuntos adicionales.'\n};\n\nconst requests = Object.entries(fields).map(([key, value]) => ({\n replaceAllText: {\n containsText: {\n text: `{{${key}}}`,\n matchCase: true,\n },\n replaceText: value,\n },\n}));\n\nreturn [\n {\n json: {\n fields,\n requests,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5328, + 688 + ], + "id": "57bef68a-7648-489c-8ffb-a7e5652a80af", + "name": "Build Docs Requests" + }, + { + "parameters": { + "method": "POST", + "url": "=https://docs.googleapis.com/v1/documents/{{ $('Copy template').first().json.id }}:batchUpdate", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "googleDocsOAuth2Api", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { \"requests\": $('Build Docs Requests').first().json.requests } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 6784, + 688 + ], + "id": "377a0388-d30c-4735-971e-9b4b79a07bea", + "name": "HTTP Request", + "credentials": { + "googleDocsOAuth2Api": { + "id": "2bGzUIxSb9LeUeNO", + "name": "Isaac - Google Docs" + } + } + }, + { + "parameters": { + "jsCode": "const f = $json.fields || {};\n\nfunction cleanPart(value, fallback = 'No especificado') {\n const text = String(value || '').trim();\n return text || fallback;\n}\n\nfunction safePart(value, fallback = 'No especificado') {\n return cleanPart(value, fallback)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[\\\\/:*?\"<>|]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/\\s/g, '-');\n}\n\nconst ano = safePart(f.ANO);\nconst cliente = safePart(f.CLIENTE);\nconst marca = safePart(f.MARCA);\nconst pais = safePart(f.PAIS);\nconst tipoRequerimiento = safePart(f.TIPO_REQUERIMIENTO);\n\nconst baseName = [\n ano,\n cliente,\n marca,\n pais,\n tipoRequerimiento\n].join('_');\n\nreturn [\n {\n json: {\n folderName: baseName,\n docName: baseName,\n htmlName: `${baseName}.html`,\n pdfName: `${baseName}.pdf`,\n projectName: cleanPart(f.NOMBRE_PROYECTO),\n fields: f\n }\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5600, + 688 + ], + "id": "d69b87b6-d228-42ba-b43a-aec0b527bf09", + "name": "Build Names" + }, + { + "parameters": { + "operation": "update", + "fileId": { + "__rl": true, + "value": "={{ $('Create folder').first().json.id }}", + "mode": "id" + }, + "newUpdatedFileName": "={{ $('Build Names').first().json.folderName }}", + "options": {} + }, + "type": "n8n-nodes-base.googleDrive", + "typeVersion": 3, + "position": [ + 5936, + 688 + ], + "id": "c04d4432-4081-437b-b9a5-2d28387ba2d5", + "name": "Rename folder", + "credentials": { + "googleDriveOAuth2Api": { + "id": "g23xdGLZRzBGqKgH", + "name": "Isaac - Google Drive" + } + } + }, + { + "parameters": { + "content": "ENTRADA DEL FLUJO\n\n• Recibe la solicitud del frontend\n• Crea carpeta base en Google Drive\n• Esa carpeta guardará brief y adjuntos\n\nEntradas:\nvoz, imágenes, PDFs y otros archivos", + "height": 400, + "width": 304, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1504, + 480 + ], + "id": "c1fd447e-d16c-45d3-9d9b-6a382517ae8a", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "CLASIFICACIÓN Y ENRUTAMIENTO\n\n• Identifica tipo de entrada\n• Separa: PDF, imagen, audio, texto y otros\n• Prepara cada rama para su procesamiento\n• También detecta enlaces en texto", + "height": 512, + "width": 692, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1088, + 400 + ], + "id": "c4e13e49-10d1-4b5f-aff3-e3485aa6e333", + "name": "Sticky Note1" + }, + { + "parameters": { + "content": "GUARDADO DE ADJUNTOS\n\n• Guarda los archivos originales del usuario\n• Incluye PDFs, imágenes y otros soportes\n• No todos se analizan\n• Sí quedan preservados en la carpeta final", + "height": 320, + "width": 672, + "color": "#433D3D" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 720, + 1232 + ], + "id": "4dce857e-1292-43c5-b5dc-a95f33a1d9dd", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "ANÁLISIS DE INSUMOS\n\n• PDF: se analiza con Gemini\n• Audio: se transcribe / resume\n• Imagen: se interpreta\n• Si no hay PDF, el flujo continúa\n• Cada salida se normaliza en Edit Fields", + "height": 1216, + "width": 2752, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -320, + -32 + ], + "id": "d96b5f33-53f1-4d3d-a146-bf3fcaf3a166", + "name": "Sticky Note3" + }, + { + "parameters": { + "content": "CONSOLIDACIÓN DEL CONTEXTO\n\n• Une documento, audio e imagen\n• Construye un contexto único\n• Envía todo al modelo\n• El modelo genera el brief estructurado", + "height": 576, + "width": 2000, + "color": "#9E2929" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 2704, + 480 + ], + "id": "5ea54f56-8749-46a2-aff5-04920de7f51c", + "name": "Sticky Note4" + }, + { + "parameters": { + "content": "POSTPROCESO DEL BRIEF\n\n• Limpia la salida del modelo\n• Extrae campos del brief\n• Construye placeholders\n• Genera nombres finales\n• Renombra la carpeta creada al inicio", + "height": 352, + "width": 1252 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 4816, + 496 + ], + "id": "1206c7e8-be96-45ce-acf7-2f21d61608a5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "SALIDA FINAL\n\n• Copia la plantilla oficial\n• Inserta la información del brief\n• Comparte la carpeta\n• Devuelve el enlace por webhook\n• Envía el correo", + "height": 384, + "width": 2340, + "color": "#3F360C" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 6192, + 496 + ], + "id": "b7587368-4628-4885-a1c1-c0be75e4dba5", + "name": "Sticky Note6" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "f34e6067-5572-44ef-bac9-738212957d5c", + "leftValue": "={{ $json.pdfMissing || false }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 48, + 208 + ], + "id": "2779ddc8-7631-417e-8800-c4a604014b4b", + "name": "If PDF missing" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "be678498-4e99-45bf-acb7-b7ade22fabb5", + "name": "fileName", + "value": "''", + "type": "string" + }, + { + "id": "e9a77032-adbd-462c-bb2e-20f267496a8e", + "name": "content_summary", + "value": "No se adjuntó PDF. Continúa el flujo con audio e imagen.", + "type": "string" + }, + { + "id": "bf85223a-197a-4a28-b3bf-064dc0fe8300", + "name": "type", + "value": "document", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 912, + -16 + ], + "id": "72c12649-c5c8-47d3-a571-cbb3f73ceae6", + "name": "PDF opcional omitido" + }, + { + "parameters": { + "resource": "document", + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 448, + 352 + ], + "id": "0b5b8ccb-ebcc-4b12-984b-a336264bd4ae", + "name": "Analyze document", + "credentials": { + "googlePalmApi": { + "id": "6Jdcd9FONxG35YuJ", + "name": "Isaac - Gemini Api Gratis" + } + } + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "0b8567da-0fd5-45c3-9f4e-071036d665bb", + "name": "fileName", + "value": "={{ $json.fileName || $('Switch').item.json.fileName || '' }}", + "type": "string" + }, + { + "id": "7af7edfc-6c2a-4183-ac0f-f5e16e925fff", + "name": "content_summary", + "value": "={{ $json.content?.parts?.[0]?.text || $json.content_summary || '' }}", + "type": "string" + }, + { + "id": "6447843f-472f-4249-ac49-bf63ed6cb04c", + "name": "type", + "value": "document", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 1952, + 176 + ], + "id": "f5280257-72a5-4721-8073-013e65b187ba", + "name": "Edit Fields documento" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\nreturn items;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -96, + 208 + ], + "id": "62375717-01d5-4dc3-b106-7304ebe20c3d", + "name": "Procesar documentos" + }, + { + "parameters": { + "jsCode": "const webhookItems = $('Webhook').all();\nconst out = [];\n\nlet hasPdfDocument = false;\n\nfor (const webhookItem of webhookItems) {\n const json = webhookItem.json || {};\n const body = json.body || {};\n const binary = webhookItem.binary || {};\n\n const text =\n body.text ||\n body.message ||\n body.caption ||\n body.prompt ||\n json.text ||\n json.message ||\n '';\n\n if (typeof text === 'string' && text.trim() !== '') {\n out.push({\n json: {\n type: 'text',\n text: text.trim(),\n }\n });\n }\n\n for (const key of Object.keys(binary)) {\n const file = binary[key];\n const mime = String(file.mimeType || '').toLowerCase();\n const ext = String(file.fileExtension || '').toLowerCase();\n const fileName = String(file.fileName || '').toLowerCase();\n\n let type = 'attachment_only';\n\n const isImage = mime.startsWith('image/');\n const isAudio = mime.startsWith('audio/');\n const isPdf =\n mime.includes('pdf') ||\n ext === 'pdf' ||\n fileName.endsWith('.pdf');\n\n if (isImage) {\n type = 'image';\n } else if (isAudio) {\n type = 'audio';\n } else if (isPdf) {\n type = 'pdf_document';\n hasPdfDocument = true;\n } else {\n type = 'attachment_only';\n }\n\n out.push({\n json: {\n type,\n binaryKey: key,\n mimeType: file.mimeType || '',\n fileName: file.fileName || '',\n fileExtension: file.fileExtension || '',\n fileSize: file.fileSize || '',\n },\n binary: {\n data: file\n }\n });\n }\n}\n\nif (!hasPdfDocument) {\n out.push({\n json: {\n type: 'pdf_document',\n pdfMissing: true,\n fileName: '',\n content_summary: 'No se adjuntó PDF. Continúa el flujo con audio e imagen.'\n }\n });\n}\n\nreturn out;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -800, + 688 + ], + "id": "4ee568a2-916f-47ec-8517-a2b3ba65a9cc", + "name": "Code in JavaScript" + }, + { + "parameters": { + "jsCode": "const folderId = $('Create folder').first().json.id;\nconst items = $input.all();\n\nconst output = [];\n\nfor (const item of items) {\n const binaries = item.binary || {};\n\n for (const key of Object.keys(binaries)) {\n const file = binaries[key];\n\n output.push({\n json: {\n folderId,\n fileName: file.fileName || key,\n mimeType: file.mimeType || '',\n },\n binary: {\n data: file\n }\n });\n }\n}\n\nreturn output;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 784, + 1392 + ], + "id": "aaab3f40-a654-4289-8325-d7eab6cd73dd", + "name": "Code in JavaScript3" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\n\nif (!items.length) {\n return [{\n json: {\n combined_content: 'No se recibió contenido para resumir.'\n }\n }];\n}\n\nconst combined = items.map((item, i) => {\n const type = item.json.type || 'other';\n const fileName = item.json.fileName || '';\n const summary = item.json.content_summary || '';\n\n return [\n `Entrada ${i + 1}`,\n `Tipo: ${type}`,\n fileName ? `Archivo: ${fileName}` : null,\n `Contenido:`,\n summary\n ].filter(Boolean).join('\\n');\n}).join('\\n\\n====================\\n\\n');\n\nreturn [\n {\n json: {\n combined_content: combined\n }\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3216, + 752 + ], + "id": "cf149c50-8821-4bb9-bcd3-451fefdbdb8f", + "name": "Code in JavaScript1" + }, + { + "parameters": { + "jsCode": "const raw = $('Message a model').item.json.content.parts[0].text || '';\n\nlet formatted = raw\n .replace(/\\r\\n/g, '\\n')\n .replace(/^---$/gm, '')\n .replace(/^# (.*)$/gm, '\\n$1\\n')\n .replace(/^## (.*)$/gm, '\\n$1\\n')\n .replace(/^### (.*)$/gm, '\\n$1\\n')\n .replace(/\\*\\*(.*?)\\*\\*/g, '$1')\n .replace(/^\\*\\s+/gm, '• ')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n\nformatted = `BRIEF PROFESIONAL\\n\\n${formatted}`;\n\nreturn [\n {\n json: {\n googleDocsText: formatted\n }\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4896, + 688 + ], + "id": "ccd4c5dd-fdd4-4a94-bc57-fea7c40e30f9", + "name": "Code in JavaScript4" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\n\nreturn items.map((item, index) => ({\n json: {\n ...item.json,\n _fallbackIndex: index,\n },\n binary: item.binary,\n}));" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 240, + 288 + ], + "id": "5ab6e790-bdd8-47db-ad31-ded716007eb1", + "name": "Backup document input" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\n\nreturn items.map((item, index) => ({\n json: {\n ...item.json,\n _fallbackIndex: index,\n },\n binary: item.binary,\n}));" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 576, + 640 + ], + "id": "b38d2104-40d0-465f-8ed8-2f20d1828037", + "name": "Backup audio input" + }, + { + "parameters": { + "jsCode": "const items = $input.all();\n\nreturn items.map((item, index) => ({\n json: {\n ...item.json,\n _fallbackIndex: index,\n },\n binary: item.binary,\n}));" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 624, + 928 + ], + "id": "4938540d-2300-4dab-ba47-476ba38ac879", + "name": "Backup image input" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "52ff398d-d5fa-4329-8755-03578f1f3965", + "leftValue": "={{ !$json.content?.parts?.[0]?.text }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1040, + 944 + ], + "id": "3484866a-ae5e-4d99-bfa5-b93828aa4902", + "name": "IF imagen gratis falló" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-3.1-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-3.1-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 1584, + 768 + ], + "id": "4c653881-cb9a-451a-8d26-51424ebef3bd", + "name": "Analyze an image - Pago", + "retryOnFail": false, + "credentials": { + "googlePalmApi": { + "id": "jvsXYwL6IOoY2DBU", + "name": "Isaac - Gemini Api Pago" + } + } + }, + { + "parameters": { + "jsCode": "const failedItems = $input.all();\nconst originalItems = $('Backup image input').all();\n\nreturn failedItems.map((failedItem, index) => {\n let originalIndex = failedItem.json?._fallbackIndex;\n\n if (originalIndex === undefined && failedItem.pairedItem) {\n if (Array.isArray(failedItem.pairedItem)) {\n originalIndex = failedItem.pairedItem[0]?.item;\n } else {\n originalIndex = failedItem.pairedItem.item;\n }\n }\n\n if (originalIndex === undefined) {\n originalIndex = index;\n }\n\n const original = originalItems[originalIndex] || originalItems[index] || originalItems[0];\n\n if (!original?.binary?.data) {\n throw new Error(`No se encontró binary.data para recuperar la imagen en el índice ${originalIndex}`);\n }\n\n return {\n json: {\n ...original.json,\n fallbackUsed: true,\n fallbackReason: failedItem.json?.error || 'Error en API gratis',\n },\n binary: original.binary,\n };\n});" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1280, + 768 + ], + "id": "11b28396-2d34-4978-b562-5190d20908bf", + "name": "Recuperar binario imagen" + }, + { + "parameters": { + "resource": "audio", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-3.1-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-3.1-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 1520, + 464 + ], + "id": "90fa8b77-56ae-4f57-b268-ba5b74a9d5f0", + "name": "Analyze audio - Pago", + "retryOnFail": false, + "credentials": { + "googlePalmApi": { + "id": "jvsXYwL6IOoY2DBU", + "name": "Isaac - Gemini Api Pago" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "a0d47fed-e52c-4882-bd89-c7833b214111", + "leftValue": "={{ !$json.content?.parts?.[0]?.text }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 976, + 656 + ], + "id": "6096066c-9335-4c00-8b4f-b36936ad0f42", + "name": "If audio gratis falló" + }, + { + "parameters": { + "jsCode": "const failedItems = $input.all();\nconst originalItems = $('Backup audio input').all();\n\nreturn failedItems.map((failedItem, index) => {\n let originalIndex = failedItem.json?._fallbackIndex;\n\n if (originalIndex === undefined && failedItem.pairedItem) {\n if (Array.isArray(failedItem.pairedItem)) {\n originalIndex = failedItem.pairedItem[0]?.item;\n } else {\n originalIndex = failedItem.pairedItem.item;\n }\n }\n\n if (originalIndex === undefined) {\n originalIndex = index;\n }\n\n const original = originalItems[originalIndex] || originalItems[index] || originalItems[0];\n\n if (!original?.binary?.data) {\n throw new Error(`No se encontró binary.data para recuperar el audio en el índice ${originalIndex}`);\n }\n\n return {\n json: {\n ...original.json,\n fallbackUsed: true,\n fallbackReason: failedItem.json?.error || 'Error en API gratis',\n },\n binary: original.binary,\n };\n});" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1312, + 464 + ], + "id": "e10409e3-1d67-4c8e-800b-7d9dcfd14ca6", + "name": "Recuperar binario audio" + }, + { + "parameters": { + "resource": "document", + "modelId": { + "__rl": true, + "value": "models/gemini-3.1-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-3.1-flash-lite" + }, + "inputType": "binary", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 1104, + 160 + ], + "id": "49feacc4-b313-4c20-913f-05fe2246abf3", + "name": "Analyze document - Pago", + "credentials": { + "googlePalmApi": { + "id": "jvsXYwL6IOoY2DBU", + "name": "Isaac - Gemini Api Pago" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "033b7aa0-7c40-4f6a-9056-83094b6451f0", + "leftValue": "={{ !$json.content?.parts?.[0]?.text }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 640, + 352 + ], + "id": "92a589b2-52b9-442b-93b8-fda2ed0a00c3", + "name": "IF documento gratis falló" + }, + { + "parameters": { + "jsCode": "const failedItems = $input.all();\nconst originalItems = $('Backup document input').all();\n\nreturn failedItems.map((failedItem, index) => {\n let originalIndex = failedItem.json?._fallbackIndex;\n\n if (originalIndex === undefined && failedItem.pairedItem) {\n if (Array.isArray(failedItem.pairedItem)) {\n originalIndex = failedItem.pairedItem[0]?.item;\n } else {\n originalIndex = failedItem.pairedItem.item;\n }\n }\n\n if (originalIndex === undefined) {\n originalIndex = index;\n }\n\n const original = originalItems[originalIndex] || originalItems[index] || originalItems[0];\n\n if (!original?.binary?.data) {\n throw new Error(`No se encontró binary.data para recuperar el documento en el índice ${originalIndex}`);\n }\n\n return {\n json: {\n ...original.json,\n fallbackUsed: true,\n fallbackReason: failedItem.json?.error || 'Error en API gratis',\n },\n binary: original.binary,\n };\n});" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 880, + 176 + ], + "id": "5a3884bb-fb6f-4bfa-89bd-1b281cb7555b", + "name": "Recuperar binario documento" + }, + { + "parameters": { + "modelId": { + "__rl": true, + "value": "models/gemini-3.1-flash-lite", + "mode": "list", + "cachedResultName": "models/gemini-3.1-flash-lite" + }, + "messages": { + "values": [ + { + "content": "=Actúa como un asistente de inteligencia artificial especializado en interpretación y estructuración de briefs creativos, estratégicos y comerciales.\n\nTu tarea es analizar todo el contenido recibido y transformarlo en un brief profesional, claro, accionable y listo para revisión interna, edición en Google Docs y representación visual en HTML.\n\nInstrucciones de trabajo:\n\n1. Captura e interpretación\n- Lee y consolida toda la información disponible.\n- Si existe texto derivado del audio, úsalo como contexto principal.\n- Si hay documentos, imágenes o referencias visuales, intégralos como soporte complementario.\n- Extrae entidades clave, objetivos, requerimientos, restricciones, entregables, fechas, audiencias, marcas, productos y cualquier dato relevante.\n- Distingue claramente entre:\n - Tipo de requerimiento = categoría general de la solicitud.\n - Tipo de entregable = pedido específico o salida concreta esperada.\n\n2. Detección de vacíos\n- Detecta campos obligatorios ausentes.\n- Identifica detalles técnicos faltantes.\n- Señala contradicciones o inconsistencias.\n- Marca información ambigua o poco clara.\n- Advierte posibles riesgos para el desarrollo creativo, estratégico o de producción.\n\n3. Enriquecimiento y estructuración\n- Reorganiza la información y conviértela en un brief profesional con redacción clara, precisa y ejecutiva.\n- Mejora la redacción sin inventar datos no proporcionados.\n- Cuando algo no esté disponible, indícalo como “No especificado” o “Pendiente de definir”.\n- Si no existe un nombre explícito para el proyecto, genera un nombre breve y profesional usando únicamente los datos reales disponibles, sin inventar información nueva.\n\n4. Reglas importantes de salida\n- No repitas información redundante.\n- No inventes datos.\n- Prioriza claridad, orden y utilidad práctica.\n- Redacta con tono profesional y ejecutivo.\n- La salida debe sentirse como un brief listo para revisión interna.\n- Debes incluir explícitamente los campos “Nombre del proyecto”, “Cliente”, “Marca”, “País”, “Tipo de requerimiento” y “Tipo de entregable”.\n- No mezcles “Tipo de requerimiento” con “Tipo de entregable”.\n- Si cliente y marca son la misma cosa, puedes repetir el valor en ambos campos.\n- Si no puedes identificar uno de los dos, usa “No especificado”.\n\n5. Genera la salida exactamente en este formato:\n\n1. Resumen ejecutivo\n- Explica de forma breve y profesional de qué trata la solicitud, cuál es el objetivo y qué se espera desarrollar.\n\n2. Información clave del brief\n- Nombre del proyecto\n- Cliente\n- Marca\n- País\n- Producto / servicio\n- Tipo de requerimiento\n- Tipo de entregable\n- Objetivo principal\n- Contexto / antecedente\n- Audiencia objetivo\n- Mensaje clave\n- Entregables solicitados\n- Canales / formatos\n- Requerimientos técnicos\n- Referencias creativas o visuales\n- Fechas o tiempos relevantes\n- Presupuesto\n- Restricciones o consideraciones especiales\n\n3. Vacíos detectados\n- Lista clara de campos faltantes, ambiguos o contradictorios.\n\n4. Riesgos o alertas\n- Señala riesgos que puedan afectar la ejecución, creatividad, tiempos, costos o claridad del proyecto.\n\n5. Recomendaciones rápidas\n- Sugiere qué información adicional conviene completar o reforzar antes de aprobar el brief final.\n\n6. Nivel de completitud\n- Indica un porcentaje estimado de completitud del brief según la información recibida.\n\nContenido recibido:\n{{$json.combined_content}}" + } + ] + }, + "builtInTools": {}, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1.1, + "position": [ + 4128, + 592 + ], + "id": "a3c326a3-4e0b-4ae6-8a0f-6eca61648af3", + "name": "Message a model - Pago", + "credentials": { + "googlePalmApi": { + "id": "jvsXYwL6IOoY2DBU", + "name": "Isaac - Gemini Api Pago" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "0ccf935c-14e8-4e1b-b7db-cb994c970733", + "leftValue": "={{ !$json.content?.parts?.[0]?.text }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3872, + 736 + ], + "id": "0e8aa2ac-ef86-4451-adcb-d6bda7c07146", + "name": "If" + }, + { + "parameters": { + "operation": "append", + "documentId": { + "__rl": true, + "value": "1h_d4l4FYkLvyy_Zp7bEQFKpy5hx6pVj9xZ98mjMHHSU", + "mode": "list", + "cachedResultName": "CDC Brief - Base para Fulgencio", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1h_d4l4FYkLvyy_Zp7bEQFKpy5hx6pVj9xZ98mjMHHSU/edit?usp=drivesdk" + }, + "sheetName": { + "__rl": true, + "value": "gid=0", + "mode": "list", + "cachedResultName": "Briefs", + "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1h_d4l4FYkLvyy_Zp7bEQFKpy5hx6pVj9xZ98mjMHHSU/edit#gid=0" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "fecha_creacion": "={{ $json.fecha_creacion }}", + "brief_id": "={{ $json.brief_id }}", + "nombre_carpeta": "={{ $json.nombre_carpeta }}", + "enviado_por": "={{ $json.enviado_por }}", + "correo": "={{ $json.correo }}", + "link_carpeta_drive": "={{ $json.link_carpeta_drive }}", + "link_documento_brief": "={{ $json.link_documento_brief }}", + "estado": "={{ $json.estado }}", + "cantidad_audios": "={{ $json.cantidad_audios }}", + "cantidad_imagenes": "={{ $json.cantidad_imagenes }}", + "cantidad_documentos": "={{ $json.cantidad_documentos }}", + "cantidad_links": "={{ $json.cantidad_links }}", + "resumen_chatbot": "={{ $json.resumen_chatbot }}", + "texto_brief": "={{ $json.texto_brief }}", + "texto_busqueda": "={{ $json.texto_busqueda }}", + "fecha_entrega": "={{ $('Preparar fecha de entrega').first().json.deliveryDateLabel }}" + }, + "matchingColumns": [], + "schema": [ + { + "id": "fecha_creacion", + "displayName": "fecha_creacion", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "brief_id", + "displayName": "brief_id", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "nombre_carpeta", + "displayName": "nombre_carpeta", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "enviado_por", + "displayName": "enviado_por", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "correo", + "displayName": "correo", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "link_carpeta_drive", + "displayName": "link_carpeta_drive", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "link_documento_brief", + "displayName": "link_documento_brief", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "estado", + "displayName": "estado", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "cantidad_audios", + "displayName": "cantidad_audios", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "cantidad_imagenes", + "displayName": "cantidad_imagenes", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "cantidad_documentos", + "displayName": "cantidad_documentos", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "cantidad_links", + "displayName": "cantidad_links", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "resumen_chatbot", + "displayName": "resumen_chatbot", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "texto_brief", + "displayName": "texto_brief", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "texto_busqueda", + "displayName": "texto_busqueda", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true + }, + { + "id": "fecha_entrega", + "displayName": "fecha_entrega", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": false + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "options": {} + }, + "type": "n8n-nodes-base.googleSheets", + "typeVersion": 4.7, + "position": [ + 8320, + 688 + ], + "id": "2b332c13-0621-4eb0-a9b7-27e820963746", + "name": "Agregar fila a Sheet Fulgencio", + "retryOnFail": false, + "alwaysOutputData": true, + "credentials": { + "googleSheetsOAuth2Api": { + "id": "K0hDZh3a85MpOHCs", + "name": "Google Sheets account 2" + } + }, + "onError": "continueRegularOutput" + }, + { + "parameters": { + "jsCode": "function getNodeJson(nodeName) {\n try {\n return $(nodeName).first().json || {};\n } catch (error) {\n return {};\n }\n}\n\nfunction getNodeBinary(nodeName) {\n try {\n return $(nodeName).first().binary || {};\n } catch (error) {\n return {};\n }\n}\n\nfunction cleanText(value) {\n return String(value || '')\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction limitText(value, maxLength = 45000) {\n const text = String(value || '');\n return text.length > maxLength ? text.slice(0, maxLength) : text;\n}\n\nfunction parseLinks(value) {\n if (!value) return [];\n\n if (Array.isArray(value)) {\n return value.filter(Boolean).map(String);\n }\n\n return String(value)\n .split(/\\n|,|\\s+/)\n .map(link => link.trim())\n .filter(link => /^https?:\\/\\//i.test(link));\n}\n\nconst webhookItem = getNodeJson('Webhook');\nconst webhookBinary = getNodeBinary('Webhook');\n\nconst body =\n webhookItem.body && Object.keys(webhookItem.body).length\n ? webhookItem.body\n : webhookItem;\n\nconst folderNode = getNodeJson('Create folder');\nconst docNode = getNodeJson('Copy template');\nconst finalTextNode = getNodeJson('Code in JavaScript4');\nconst fechaEntregaNode = getNodeJson('Preparar fecha de entrega');\n\nconst folderId = folderNode.id || '';\nconst folderName = folderNode.name || '';\nconst docId = docNode.id || '';\n\nconst folderUrl = folderId\n ? `https://drive.google.com/drive/u/0/folders/${folderId}`\n : '';\n\nconst docUrl = docId\n ? `https://docs.google.com/document/d/${docId}/edit`\n : '';\n\nconst submittedByName =\n body.submittedByName ||\n body.displayName ||\n body.name ||\n body.email ||\n 'No especificado';\n\nconst submittedByEmail =\n body.submittedByEmail ||\n body.email ||\n 'No especificado';\n\nconst fechaEntrega =\n fechaEntregaNode.deliveryDateLabel ||\n fechaEntregaNode.fechaEntregaTexto ||\n body.deliveryDateLabel ||\n body.fechaEntregaTexto ||\n 'Por confirmar';\n\nconst finalBriefText =\n finalTextNode.googleDocsText ||\n finalTextNode.text ||\n finalTextNode.content ||\n '';\n\nconst files = Object.values(webhookBinary || {});\n\nconst audioCount = files.filter(file =>\n String(file.mimeType || '').startsWith('audio/')\n).length;\n\nconst imageCount = files.filter(file =>\n String(file.mimeType || '').startsWith('image/')\n).length;\n\nconst documentCount = files.filter(file => {\n const mime = String(file.mimeType || '');\n return (\n mime.includes('pdf') ||\n mime.includes('word') ||\n mime.includes('excel') ||\n mime.includes('spreadsheet') ||\n mime.includes('presentation') ||\n mime.includes('powerpoint') ||\n mime.includes('officedocument')\n );\n}).length;\n\nconst links = [\n ...parseLinks(body.links),\n ...parseLinks(body.enlaces),\n ...parseLinks(body.referenceLinks),\n ...parseLinks(body.referencias),\n ...parseLinks(body.urls),\n];\n\nconst fechaCreacion = new Date().toISOString();\n\nconst resumenChatbot = cleanText(finalBriefText).slice(0, 1200);\n\nconst textoBusqueda = [\n `Brief CDC`,\n `Fecha: ${fechaCreacion}`,\n `Fecha límite de entrega: ${fechaEntrega}`,\n `Enviado por: ${submittedByName}`,\n `Correo: ${submittedByEmail}`,\n `Carpeta: ${folderUrl}`,\n `Documento: ${docUrl}`,\n `Nombre carpeta: ${folderName}`,\n '',\n finalBriefText,\n].join('\\n');\n\nreturn [\n {\n json: {\n fecha_creacion: fechaCreacion,\n brief_id: folderId || `${Date.now()}`,\n nombre_carpeta: folderName,\n enviado_por: submittedByName,\n correo: submittedByEmail,\n fecha_entrega: fechaEntrega,\n link_carpeta_drive: folderUrl,\n link_documento_brief: docUrl,\n estado: 'Nuevo',\n cantidad_audios: audioCount,\n cantidad_imagenes: imageCount,\n cantidad_documentos: documentCount,\n cantidad_links: links.length,\n resumen_chatbot: resumenChatbot,\n texto_brief: limitText(finalBriefText),\n texto_busqueda: limitText(textoBusqueda),\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 8112, + 688 + ], + "id": "b44ed7d4-7333-44b6-b46f-d3d50a922eae", + "name": "Preparar fila para Sheet Fulgencio" + }, + { + "parameters": { + "jsCode": "const inputItem = $input.first();\nconst inputJson = inputItem.json || {};\n\nconst webhookJson = $('Webhook').first().json || {};\nconst body = webhookJson.body || {};\n\nfunction formatDateEs(dateString) {\n if (!dateString) return '';\n\n const value = String(dateString).trim();\n\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n return value;\n }\n\n const [year, month, day] = value.split('-');\n const date = new Date(Number(year), Number(month) - 1, Number(day));\n\n if (Number.isNaN(date.getTime())) {\n return value;\n }\n\n return date.toLocaleDateString('es-DO', {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n });\n}\n\nconst deliveryDateRaw =\n body.deliveryDate ||\n body.fechaEntrega ||\n '';\n\nconst deliveryDateStatusRaw =\n body.deliveryDateStatus ||\n body.fechaEntregaEstado ||\n '';\n\nconst deliveryDateLabelFromFrontend =\n body.deliveryDateLabel ||\n body.fechaEntregaTexto ||\n '';\n\nconst hasDate =\n deliveryDateRaw !== undefined &&\n deliveryDateRaw !== null &&\n String(deliveryDateRaw).trim() !== '';\n\nconst normalizedStatus = String(deliveryDateStatusRaw || '').toLowerCase();\nconst normalizedLabel = String(deliveryDateLabelFromFrontend || '').toLowerCase();\n\nconst isPending =\n !hasDate &&\n (\n normalizedStatus === 'pending' ||\n normalizedStatus === 'por_confirmar' ||\n normalizedStatus === 'por confirmar' ||\n normalizedLabel === 'por confirmar'\n );\n\nconst deliveryDateLabel = isPending\n ? 'Por confirmar'\n : hasDate\n ? formatDateEs(deliveryDateRaw)\n : deliveryDateLabelFromFrontend || 'Por confirmar';\n\nreturn [\n {\n json: {\n ...inputJson,\n deliveryDateRaw,\n deliveryDateStatus: isPending ? 'pending' : 'defined',\n deliveryDateLabel,\n fechaEntregaTexto: deliveryDateLabel,\n fechaEntregaEstado: isPending ? 'por_confirmar' : 'definida',\n },\n binary: inputItem.binary,\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -1008, + 688 + ], + "id": "dabcfbdd-a1db-4241-950e-6259a27586b9", + "name": "Preparar fecha de entrega" + }, + { + "parameters": { + "jsCode": "const item = $input.first();\nconst json = item.json || {};\n\nconst fechaNode = $('Preparar fecha de entrega').first().json || {};\n\nconst deliveryDateLabel =\n fechaNode.deliveryDateLabel ||\n fechaNode.fechaEntregaTexto ||\n json.deliveryDateLabel ||\n json.fechaEntregaTexto ||\n 'Por confirmar';\n\nconst deliveryLine = `• Fecha límite de entrega: ${deliveryDateLabel}.`;\n\nlet googleDocsText = json.googleDocsText || '';\n\nif (!googleDocsText) {\n return [\n {\n json: {\n ...json,\n deliveryDateLabel,\n fechaEntregaTexto: deliveryDateLabel,\n },\n binary: item.binary,\n },\n ];\n}\n\n// Evita duplicar la fecha si el nodo se ejecuta más de una vez\ngoogleDocsText = googleDocsText.replace(\n /^\\s*[•-]?\\s*Fecha límite de entrega:.*$/gim,\n ''\n);\n\n// Inserta la fecha justo debajo de \"Fechas / tiempos relevantes\"\nconst fechasHeadingRegex = /(Fechas\\s*\\/\\s*tiempos\\s+relevantes\\s*:?\\s*)/i;\n\nif (fechasHeadingRegex.test(googleDocsText)) {\n googleDocsText = googleDocsText.replace(\n fechasHeadingRegex,\n `$1\\n${deliveryLine}\\n`\n );\n} else {\n // Si por alguna razón la sección no aparece, la agrega al final\n googleDocsText += `\\n\\nFechas / tiempos relevantes\\n${deliveryLine}`;\n}\n\n// Limpieza básica de saltos de línea excesivos\ngoogleDocsText = googleDocsText\n .replace(/\\n{4,}/g, '\\n\\n\\n')\n .trim();\n\nreturn [\n {\n json: {\n ...json,\n googleDocsText,\n deliveryDateLabel,\n fechaEntregaTexto: deliveryDateLabel,\n },\n binary: item.binary,\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5104, + 688 + ], + "id": "084305a2-38dd-4b3f-b900-6a3f7c26c69c", + "name": "Agregar fecha al Google Doc" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Create folder", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch": { + "main": [ + [ + { + "node": "Procesar documentos", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Backup image input", + "type": "main", + "index": 0 + } + ], + [], + [ + { + "node": "Backup audio input", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze audio": { + "main": [ + [ + { + "node": "If audio gratis falló", + "type": "main", + "index": 0 + } + ] + ] + }, + "Merge": { + "main": [ + [ + { + "node": "Code in JavaScript1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields1": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 1 + } + ] + ] + }, + "Message a model": { + "main": [ + [ + { + "node": "If", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze an image": { + "main": [ + [ + { + "node": "IF imagen gratis falló", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create folder": { + "main": [ + [ + { + "node": "Preparar fecha de entrega", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields2": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 2 + } + ] + ] + }, + "Share folder": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "Respond to Webhook": { + "main": [ + [ + { + "node": "Send a message", + "type": "main", + "index": 0 + } + ] + ] + }, + "Copy template": { + "main": [ + [ + { + "node": "HTTP Request", + "type": "main", + "index": 0 + } + ] + ] + }, + "Build Docs Requests": { + "main": [ + [ + { + "node": "Build Names", + "type": "main", + "index": 0 + } + ] + ] + }, + "HTTP Request": { + "main": [ + [ + { + "node": "Share folder", + "type": "main", + "index": 0 + } + ] + ] + }, + "Build Names": { + "main": [ + [ + { + "node": "Rename folder", + "type": "main", + "index": 0 + } + ] + ] + }, + "Rename folder": { + "main": [ + [ + { + "node": "Copy template", + "type": "main", + "index": 0 + } + ] + ] + }, + "If PDF missing": { + "main": [ + [ + { + "node": "PDF opcional omitido", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Backup document input", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze document": { + "main": [ + [ + { + "node": "IF documento gratis falló", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields documento": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 0 + } + ] + ] + }, + "PDF opcional omitido": { + "main": [ + [ + { + "node": "Edit Fields documento", + "type": "main", + "index": 0 + } + ] + ] + }, + "Procesar documentos": { + "main": [ + [ + { + "node": "If PDF missing", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Code in JavaScript3", + "type": "main", + "index": 0 + }, + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript3": { + "main": [ + [ + { + "node": "Upload file", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript1": { + "main": [ + [ + { + "node": "Message a model", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript4": { + "main": [ + [ + { + "node": "Agregar fecha al Google Doc", + "type": "main", + "index": 0 + } + ] + ] + }, + "Backup document input": { + "main": [ + [ + { + "node": "Analyze document", + "type": "main", + "index": 0 + } + ] + ] + }, + "Backup audio input": { + "main": [ + [ + { + "node": "Analyze audio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Backup image input": { + "main": [ + [ + { + "node": "Analyze an image", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF imagen gratis falló": { + "main": [ + [ + { + "node": "Recuperar binario imagen", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Recuperar binario imagen": { + "main": [ + [ + { + "node": "Analyze an image - Pago", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze an image - Pago": { + "main": [ + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "If audio gratis falló": { + "main": [ + [ + { + "node": "Recuperar binario audio", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Recuperar binario audio": { + "main": [ + [ + { + "node": "Analyze audio - Pago", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze audio - Pago": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF documento gratis falló": { + "main": [ + [ + { + "node": "Recuperar binario documento", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Edit Fields documento", + "type": "main", + "index": 0 + } + ] + ] + }, + "Recuperar binario documento": { + "main": [ + [ + { + "node": "Analyze document - Pago", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze document - Pago": { + "main": [ + [ + { + "node": "Edit Fields documento", + "type": "main", + "index": 0 + } + ] + ] + }, + "If": { + "main": [ + [ + { + "node": "Message a model - Pago", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Code in JavaScript4", + "type": "main", + "index": 0 + } + ] + ] + }, + "Message a model - Pago": { + "main": [ + [ + { + "node": "Code in JavaScript4", + "type": "main", + "index": 0 + } + ] + ] + }, + "Send a message": { + "main": [ + [ + { + "node": "Preparar fila para Sheet Fulgencio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar fila para Sheet Fulgencio": { + "main": [ + [ + { + "node": "Agregar fila a Sheet Fulgencio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar fecha de entrega": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Agregar fecha al Google Doc": { + "main": [ + [ + { + "node": "Build Docs Requests", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Isaac Aracena", + "name": "CDC Brief - GLM V2", + "description": "", + "autosaved": true, + "workflowPublishHistory": [ + { + "createdAt": "2026-06-01T20:03:46.368Z", + "id": 960, + "workflowId": "Z8W67i7RfrhXRDFQ", + "versionId": "b4841850-e8b9-493f-a1fb-7070136942be", + "event": "activated", + "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" + }, + { + "createdAt": "2026-06-04T00:42:57.585Z", + "id": 1080, + "workflowId": "Z8W67i7RfrhXRDFQ", + "versionId": "b4841850-e8b9-493f-a1fb-7070136942be", + "event": "activated", + "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" + }, + { + "createdAt": "2026-06-04T00:42:57.545Z", + "id": 1079, + "workflowId": "Z8W67i7RfrhXRDFQ", + "versionId": "b4841850-e8b9-493f-a1fb-7070136942be", + "event": "deactivated", + "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" + } + ] + } +} \ No newline at end of file