{ "updatedAt": "2026-06-09T03:06:01.252Z", "createdAt": "2026-06-02T14:16:46.194Z", "id": "4eCPQcE3FDIll8bW", "name": "TEST - Telegram - Propuestas Ejecutadas", "description": null, "active": true, "isArchived": false, "nodes": [ { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const update = $json || {};\nconst message =\n update.message ||\n update.edited_message ||\n update.channel_post ||\n {};\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst chatId = clean(message.chat?.id);\nconst userId = clean(message.from?.id);\nconst firstName = clean(message.from?.first_name);\nconst lastName = clean(message.from?.last_name);\nconst username = clean(message.from?.username);\n\nconst managerNombre =\n [firstName, lastName].filter(Boolean).join(' ') ||\n username ||\n 'Usuario Telegram';\n\nlet messageType = 'unknown';\nlet texto = clean(message.text || message.caption);\nlet mediaSourceId = '';\nlet mediaMimeType = '';\nlet mediaFileName = '';\n\nif (message.voice) {\n messageType = 'audio';\n mediaSourceId = clean(message.voice.file_id);\n mediaMimeType = clean(message.voice.mime_type || 'audio/ogg');\n mediaFileName = `nota_voz_${message.message_id || Date.now()}.ogg`;\n} else if (message.audio) {\n messageType = 'audio';\n mediaSourceId = clean(message.audio.file_id);\n mediaMimeType = clean(message.audio.mime_type || 'audio/mpeg');\n mediaFileName =\n clean(message.audio.file_name) ||\n `audio_${message.message_id || Date.now()}.mp3`;\n} else if (message.photo && Array.isArray(message.photo) && message.photo.length) {\n messageType = 'image';\n const bestPhoto = message.photo[message.photo.length - 1];\n mediaSourceId = clean(bestPhoto.file_id);\n mediaMimeType = 'image/jpeg';\n mediaFileName = `imagen_${message.message_id || Date.now()}.jpg`;\n} else if (message.video) {\n messageType = 'video';\n mediaSourceId = clean(message.video.file_id);\n mediaMimeType = clean(message.video.mime_type || 'video/mp4');\n mediaFileName =\n clean(message.video.file_name) ||\n `video_${message.message_id || Date.now()}.mp4`;\n} else if (texto) {\n messageType = 'text';\n}\n\nfunction detectarAccion(text, type) {\n const t = norm(text);\n\n if (\n t === '/START' ||\n t === 'INICIAR' ||\n t === 'INICIAR REPORTE' ||\n t === '2' ||\n t === 'PROPUESTAS EJECUTADAS'\n ) {\n return 'INICIAR';\n }\n\n if (t === 'CANCELAR' || t === 'CANCELAR REPORTE') {\n return 'CANCELAR';\n }\n\n if (\n t === 'FOTOS LISTAS' ||\n t === 'IMAGENES LISTAS' ||\n t === 'IMÁGENES LISTAS'\n ) {\n return 'FOTOS_LISTAS';\n }\n\n if (\n t === 'SIN VIDEO' ||\n t === 'NO TENGO VIDEO' ||\n t === 'NO HAY VIDEO'\n ) {\n return 'SIN_VIDEO';\n }\n\n if (t === 'LISTO' || t === 'FINALIZAR' || t === 'PROCESAR') {\n return 'LISTO';\n }\n\n if (type === 'audio') return 'MEDIA_AUDIO';\n if (type === 'image') return 'MEDIA_IMAGEN';\n if (type === 'video') return 'MEDIA_VIDEO';\n\n return 'MENSAJE';\n}\n\nconst accionFlujo = detectarAccion(texto, messageType);\nconst tieneMedia = ['audio', 'image', 'video'].includes(messageType);\n\nreturn {\n json: {\n event_id: clean(message.message_id) || `TG-${Date.now()}`,\n fecha_recepcion: new Date().toISOString(),\n\n canal_origen: 'TELEGRAM',\n\n // Usaremos el user ID de Telegram como identificador del manager\n // en esta versión de prueba para reutilizar la lógica existente.\n manager_telefono: userId,\n manager_nombre: managerNombre,\n usuario_id_origen: userId,\n\n telegram_chat_id: chatId,\n telegram_user_id: userId,\n\n from_me: false,\n\n message_type: messageType,\n texto,\n accion_flujo: accionFlujo,\n\n tiene_media: tieneMedia,\n media_count: tieneMedia ? 1 : 0,\n\n media_source_id: mediaSourceId,\n media_mime_type: mediaMimeType,\n media_file_name: mediaFileName,\n\n raw_preview: JSON.stringify(update).slice(0, 1500),\n raw_event: update,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -7712, -832 ], "id": "f2eb6fde-d967-407b-8eb2-b77390e098b9", "name": "Code - Normalizar evento WhatsApp" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "f4d8feac-ea04-4689-806a-42d4e97d451b", "leftValue": "={{ $json.from_me }}", "rightValue": "", "operator": { "type": "boolean", "operation": "false", "singleValue": true } }, { "id": "758ddbe7-3ec0-4a00-8812-7fcad7c3767d", "leftValue": "={{ $json.manager_telefono }}", "rightValue": "", "operator": { "type": "string", "operation": "notEmpty", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -7520, -832 ], "id": "66ec0b50-31fa-4da6-aeeb-5757d2863428", "name": "IF - Mensaje entrante válido" }, { "parameters": { "rules": { "values": [ { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "leftValue": "={{ $json.accion_flujo }}", "rightValue": "INICIAR", "operator": { "type": "string", "operation": "equals" }, "id": "d056529b-35e5-4b59-81ea-6390368dcc4a" } ], "combinator": "and" }, "renameOutput": true, "outputKey": "INICIAR" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "8113bb38-04a1-4272-8db8-29cb469d4404", "leftValue": "={{ $json.accion_flujo }}", "rightValue": "MEDIA_AUDIO", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "MEDIA_AUDIO" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "2f985186-3795-4cbc-ac92-e154ade8d7d2", "leftValue": "={{ $json.accion_flujo }}", "rightValue": "MEDIA_IMAGEN", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "MEDIA_IMAGEN" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "2e257327-485f-46c7-b718-b0b2aba3a912", "leftValue": "={{ $json.accion_flujo }}", "rightValue": "FOTOS_LISTAS", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "FOTOS_LISTAS" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "bdfd1a30-d1b1-4072-bfda-168c912317fe", "leftValue": "={{ String($json.message_type || '').trim().toLowerCase() }}", "rightValue": "video", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "MEDIA_VIDEO " }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "72090cce-6818-43ab-913e-d191b61f50a3", "leftValue": "={{ String($json.texto || '').trim().toUpperCase() }}", "rightValue": "SIN_VIDEO ", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "SIN_VIDEO" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "6f8333ec-fc61-4c12-bf9e-24d9f21ac1be", "leftValue": "={{ String($json.texto || '').trim().toUpperCase() }}", "rightValue": "LISTO ", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "LISTO " } ] }, "options": {} }, "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ -4720, -2448 ], "id": "d844de66-b9de-489e-bce6-71b5dab3d384", "name": "Switch - Acción flujo guiado" }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\n\nconst sesionesValidas = rows.filter(row => clean(row.session_id));\n\nconst sesionActiva = sesionesValidas.find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n});\n\nif (sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n crear_nueva_sesion: false,\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n estado: clean(sesionActiva.estado),\n audio_count: Number(sesionActiva.audio_count || 0),\n imagenes_count: Number(sesionActiva.imagenes_count || 0),\n videos_count: Number(sesionActiva.videos_count || 0),\n },\n },\n ];\n}\n\nconst now = new Date().toISOString();\nconst stamp = now.replace(/\\D/g, '').slice(0, 14);\nconst telefono4 = telefono.slice(-4) || '0000';\nconst random = Math.random().toString(36).slice(2, 6).toUpperCase();\n\nconst sessionId = `SES_${stamp}_${telefono4}_${random}`;\n\nreturn [\n {\n json: {\n ...entrada,\n\n crear_nueva_sesion: true,\n\n session_id: sessionId,\n manager_telefono: telefono,\n manager_nombre: clean(entrada.manager_nombre),\n canal_origen: clean(entrada.canal_origen || 'WHATSAPP'),\n\n fecha_inicio: now,\n ultima_actividad: now,\n\n etapa: 'ESPERANDO_AUDIO',\n\n audio_count: 0,\n imagenes_count: 0,\n videos_count: 0,\n\n estado: 'ACTIVA',\n ejecucion_id: '',\n motivo_revision: '',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1024, -5040 ], "id": "61a18e64-32ac-47ff-8427-7f5cbc223de3", "name": "Code - Resolver inicio sesión" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1232, -5040 ], "id": "08eaa473-383e-4230-ade6-3602a6a7c3d1", "name": "Sheets - Leer sesiones existentes", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "b9b21561-7dbb-470f-9bae-22189a2982ad", "leftValue": "={{ $json.crear_nueva_sesion }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -816, -5040 ], "id": "82a8ed63-2e86-4c27-91f8-1e424d15b09b", "name": "IF - Crear nueva sesión" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $json.session_id }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "canal_origen": "={{ $json.canal_origen }}", "fecha_inicio": "={{ $json.fecha_inicio }}", "ultima_actividad": "={{ $json.ultima_actividad }}", "etapa": "={{ $json.etapa }}", "audio_count": "={{ $json.audio_count }}", "imagenes_count": "={{ $json.imagenes_count }}", "videos_count": "={{ $json.videos_count }}", "estado": "={{ $json.estado }}", "ejecucion_id": "={{ $json.ejecucion_id }}", "motivo_revision": "={{ $json.motivo_revision }}" }, "matchingColumns": [], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -432, -5088 ], "id": "da72ce9f-f96f-4b3d-a8a4-bc3e80bfece5", "name": "Sheets - Crear sesión reporte", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Resolver inicio sesión').first().json;\n\nconst nombre = String(sesion.manager_nombre || '').trim();\nconst saludo = nombre ? `¡Hola, ${nombre}!` : '¡Hola!';\n\nconst texto = `📸 *Propuestas Ejecutadas - GLM*\n\n${saludo} Bienvenido al registro de propuestas ejecutadas.\n\nEste proceso permite documentar ejecuciones reales para alimentar el banco de evidencias de Fulgencio Fumado.\n\n*Paso 1 de 3 — Nota de voz obligatoria*\n\nEnvía una nota de voz indicando:\n\n• Nombre o referencia de la propuesta ejecutada\n• Marca o cliente\n• País\n• Ubicación de la ejecución\n• Fecha de ejecución\n• Qué se implementó\n• Comentarios o resultados observados\n\nImportante: registra una sola propuesta ejecutada a la vez.`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -256, -5088 ], "id": "0c5a1f3b-c7a8-48cc-a9c6-f43e06f76186", "name": "Code - Preparar bienvenida y solicitud audio" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const etapa = String($json.etapa || '').trim();\n\nconst instrucciones = {\n ESPERANDO_AUDIO: 'Actualmente estoy esperando la nota de voz obligatoria.',\n ESPERANDO_IMAGENES: 'Actualmente estoy esperando las imágenes obligatorias de la ejecución.',\n ESPERANDO_VIDEO: 'Actualmente estoy esperando videos opcionales o la confirmación SIN VIDEO.',\n PROCESANDO: 'Tu reporte se encuentra en procesamiento.',\n};\n\nconst detalle =\n instrucciones[etapa] ||\n 'Tienes un reporte activo pendiente de completar.';\n\nconst texto = `⚠️ Ya tienes un reporte de propuesta ejecutada en proceso.\n\n${detalle}\n\nCompleta ese reporte antes de iniciar uno nuevo. Si deseas cancelarlo, escribe: CANCELAR`;\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -448, -4832 ], "id": "0a2e63f8-ef9d-4aa6-9e4a-7e7a92dec42d", "name": "Code - Preparar aviso sesión activa" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1280, -4064 ], "id": "412c6cef-b6c2-4cf0-97c6-c87ed900c668", "name": "Sheets - Leer sesiones para audio", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst now = new Date().toISOString();\n\nconst sesionesValidas = rows.filter(row => clean(row.session_id));\n\nconst sesionActiva = sesionesValidas.find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n});\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n audio_aceptado: false,\n motivo_audio: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\n\nif (etapaActual !== 'ESPERANDO_AUDIO') {\n return [\n {\n json: {\n ...entrada,\n audio_aceptado: false,\n motivo_audio: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst audioCountActual = Number(sesionActiva.audio_count || 0);\n\nreturn [\n {\n json: {\n ...entrada,\n\n audio_aceptado: true,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n etapa_recibida: 'ESPERANDO_AUDIO',\n etapa: 'ESPERANDO_AUDIO',\n nueva_etapa: 'ESPERANDO_IMAGENES',\n\n audio_count: audioCountActual + 1,\n imagenes_count: Number(sesionActiva.imagenes_count || 0),\n videos_count: Number(sesionActiva.videos_count || 0),\n\n ultima_actividad: now,\n\n estado_evento: 'PENDIENTE',\n estado_sesion: 'ACTIVA',\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1072, -4064 ], "id": "3baee4b0-e7c3-41b9-beb6-d7521797ca26", "name": "Code - Resolver sesión para audio" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "dd1bbbb7-a9de-4214-b4a7-7d105803dc3f", "leftValue": "={{ $json.audio_aceptado }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -864, -4064 ], "id": "7321ac9c-141b-4205-833c-6234463b5f0c", "name": "IF - Audio válido para sesión" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_audio === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.etapa === 'ESPERANDO_IMAGENES') {\n texto = `✅ Ya recibí la nota de voz de tu reporte.\n\nAhora debes enviar al menos una imagen de la ejecución.\n\nCuando termines de enviar las imágenes, escribe: FOTOS LISTAS`;\n} else if ($json.etapa === 'ESPERANDO_VIDEO') {\n texto = `✅ La nota de voz y las imágenes ya fueron recibidas.\n\nAhora puedes enviar videos opcionales y luego escribir LISTO, o escribir SIN VIDEO si no tienes videos.`;\n} else {\n texto = `⚠️ Recibí tu nota de voz, pero actualmente no corresponde al paso activo de tu reporte.\n\nCompleta el paso pendiente antes de continuar.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -656, -3968 ], "id": "03c759bb-1def-48ea-9a35-42346716d57c", "name": "Code - Preparar rechazo audio" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 306129743, "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=306129743" }, "columns": { "mappingMode": "defineBelow", "value": { "event_id": "={{ $json.event_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "message_type": "={{ $json.message_type }}", "texto": "NOTA DE VOZ RECIBIDA", "comando": "={{ $json.accion_flujo }}", "tiene_media": "={{ $json.tiene_media }}", "media_count": "={{ $json.media_count }}", "raw_preview": "={{ $json.raw_preview }}", "fecha_procesado": "=", "estado": "={{ $json.estado_evento }}", "session_id": "={{ $json.session_id }}", "canal_origen": "={{ $json.canal_origen }}", "etapa_recibida": "={{ $json.etapa_recibida }}", "media_source_id": "={{ $json.media_source_id }}", "media_mime_type": "={{ $json.media_mime_type }}", "media_file_name": "={{ $json.media_file_name }}" }, "matchingColumns": [], "schema": [ { "id": "event_id", "displayName": "event_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "message_type", "displayName": "message_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "texto", "displayName": "texto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comando", "displayName": "comando", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tiene_media", "displayName": "tiene_media", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_count", "displayName": "media_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "raw_preview", "displayName": "raw_preview", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_procesado", "displayName": "fecha_procesado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa_recibida", "displayName": "etapa_recibida", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_source_id", "displayName": "media_source_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_mime_type", "displayName": "media_mime_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_file_name", "displayName": "media_file_name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_drive_url", "displayName": "media_drive_url", "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": [ -656, -4160 ], "id": "7538b493-9f75-4870-92ca-83ff68ced808", "name": "Sheets - Guardar evento audio", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Resolver sesión para audio').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Resolver sesión para audio').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Resolver sesión para audio').first().json.nueva_etapa }}", "audio_count": "={{ $('Code - Resolver sesión para audio').first().json.audio_count }}", "estado": "={{ $('Code - Resolver sesión para audio').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "videos_count", "displayName": "videos_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "estado", "displayName": "estado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -448, -4160 ], "id": "adb59f9f-f1db-415d-8aef-ffcfec1f6fac", "name": "Sheets - Actualizar sesión tras audio", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Resolver sesión para audio').first().json;\n\nconst texto = `✅ Nota de voz recibida.\n\n*Paso 2 de 3 — Imágenes obligatorias*\n\nAhora envía al menos una foto de la propuesta ejecutada.\n\nPuedes enviar varias imágenes seguidas.\n\nCuando termines de enviar las imágenes, escribe: FOTOS LISTAS`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -240, -4160 ], "id": "714069de-e125-472d-8287-309212587180", "name": "Code - Preparar solicitud imágenes" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1312, -3072 ], "id": "2823d97e-f5f0-4547-bae2-7cf2e270107b", "name": "Sheets - Leer sesiones para imagen", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst now = new Date().toISOString();\n\nconst sesionesValidas = rows.filter(row => clean(row.session_id));\n\nconst sesionActiva = sesionesValidas.find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n});\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n imagen_aceptada: false,\n motivo_imagen: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\n\nif (etapaActual !== 'ESPERANDO_IMAGENES') {\n return [\n {\n json: {\n ...entrada,\n imagen_aceptada: false,\n motivo_imagen: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst imagenesCountActual = Number(sesionActiva.imagenes_count || 0);\n\nreturn [\n {\n json: {\n ...entrada,\n\n imagen_aceptada: true,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n etapa_recibida: 'ESPERANDO_IMAGENES',\n etapa: 'ESPERANDO_IMAGENES',\n\n audio_count: Number(sesionActiva.audio_count || 0),\n imagenes_count: imagenesCountActual + 1,\n videos_count: Number(sesionActiva.videos_count || 0),\n\n ultima_actividad: now,\n\n estado_evento: 'PENDIENTE',\n estado_sesion: 'ACTIVA',\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1104, -3072 ], "id": "b9efdab7-0dc2-46aa-9f58-f11cf3419ef0", "name": "Code - Resolver sesión para imagen" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "daee475e-88ff-4d39-9823-17bd9d13435f", "leftValue": "={{ $json.imagen_aceptada }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -896, -3072 ], "id": "b29bbb17-19e4-4aac-9758-b97831e1263d", "name": "IF - Imagen válida para sesión" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_imagen === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.etapa === 'ESPERANDO_AUDIO') {\n texto = `⚠️ Antes de enviar imágenes, debes enviar la nota de voz obligatoria del reporte.\n\nIndica en la nota de voz la propuesta ejecutada, marca o cliente, país, ubicación, fecha y detalles de la ejecución.`;\n} else if ($json.etapa === 'ESPERANDO_VIDEO') {\n texto = `✅ Las imágenes de tu reporte ya fueron registradas.\n\nAhora puedes enviar videos opcionales y luego escribir LISTO, o escribir SIN VIDEO si no tienes videos.`;\n} else {\n texto = `⚠️ Recibí tu imagen, pero actualmente no corresponde al paso activo de tu reporte.\n\nCompleta el paso pendiente antes de continuar.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -688, -2976 ], "id": "b3dfdde1-9f91-489d-b426-3174c810db49", "name": "Code - Preparar rechazo imagen" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 306129743, "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=306129743" }, "columns": { "mappingMode": "defineBelow", "value": { "event_id": "={{ $json.event_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "message_type": "={{ $json.message_type }}", "texto": "IMAGEN RECIBIDA", "comando": "={{ $json.accion_flujo }}", "tiene_media": "={{ $json.tiene_media }}", "media_count": "={{ $json.media_count }}", "raw_preview": "={{ $json.raw_preview }}", "estado": "={{ $json.estado_evento }}", "session_id": "={{ $json.session_id }}", "canal_origen": "={{ $json.canal_origen }}", "etapa_recibida": "={{ $json.etapa_recibida }}", "media_source_id": "={{ $json.media_source_id }}", "media_mime_type": "={{ $json.media_mime_type }}", "media_file_name": "={{ $json.media_file_name }}" }, "matchingColumns": [], "schema": [ { "id": "event_id", "displayName": "event_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "message_type", "displayName": "message_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "texto", "displayName": "texto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comando", "displayName": "comando", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tiene_media", "displayName": "tiene_media", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_count", "displayName": "media_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "raw_preview", "displayName": "raw_preview", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_procesado", "displayName": "fecha_procesado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa_recibida", "displayName": "etapa_recibida", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_source_id", "displayName": "media_source_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_mime_type", "displayName": "media_mime_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_file_name", "displayName": "media_file_name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_drive_url", "displayName": "media_drive_url", "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": [ -560, -3216 ], "id": "3048e00d-c0f6-4935-967d-1d7785f098d3", "name": "Sheets - Guardar evento imagen", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Resolver sesión para imagen').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Resolver sesión para imagen').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Resolver sesión para imagen').first().json.etapa }}", "imagenes_count": "={{ $('Code - Resolver sesión para imagen').first().json.imagenes_count }}", "estado": "={{ $('Code - Resolver sesión para imagen').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -352, -3216 ], "id": "712f692d-04e2-40ec-8c0d-ee468910c802", "name": "Sheets - Actualizar sesión tras imagen", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Resolver sesión para imagen').first().json;\nconst cantidad = Number(sesion.imagenes_count || 1);\n\nconst texto = `✅ Imagen recibida. Total de imágenes registradas: ${cantidad}.\n\nPuedes enviar más imágenes de esta ejecución.\n\nCuando termines de enviar las imágenes, escribe: FOTOS LISTAS`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -176, -3216 ], "id": "311e44a1-af81-4998-9daa-3e9dd2fbd829", "name": "Code - Preparar confirmación imagen recibida" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1360, -2048 ], "id": "9bc8b9d0-6d43-4059-8901-331136476ad4", "name": "Sheets - Leer sesiones para fotos listas", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst now = new Date().toISOString();\n\nconst sesionActiva = rows\n .filter(row => clean(row.session_id))\n .find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n });\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n fotos_listas_valido: false,\n motivo_fotos_listas: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\nconst imagenesCount = Number(sesionActiva.imagenes_count || 0);\n\nif (etapaActual !== 'ESPERANDO_IMAGENES') {\n return [\n {\n json: {\n ...entrada,\n fotos_listas_valido: false,\n motivo_fotos_listas: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n imagenes_count: imagenesCount,\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nif (imagenesCount < 1) {\n return [\n {\n json: {\n ...entrada,\n fotos_listas_valido: false,\n motivo_fotos_listas: 'SIN_IMAGENES',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n imagenes_count: imagenesCount,\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nreturn [\n {\n json: {\n ...entrada,\n\n fotos_listas_valido: true,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n etapa: clean(sesionActiva.etapa),\n nueva_etapa: 'ESPERANDO_VIDEO',\n\n audio_count: Number(sesionActiva.audio_count || 0),\n imagenes_count: imagenesCount,\n videos_count: Number(sesionActiva.videos_count || 0),\n\n ultima_actividad: now,\n estado_sesion: 'ACTIVA',\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1152, -2048 ], "id": "43a983b9-c467-4523-bc0e-8309fbd9a61b", "name": "Code - Validar fotos listas" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "67809c33-d2b5-4c2a-b551-fac585b1d0a3", "leftValue": "={{ $json.fotos_listas_valido }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -944, -2048 ], "id": "14d30604-b0a9-42b5-bbc1-9660fda01ee3", "name": "IF - Puede pasar a videos" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_fotos_listas === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.motivo_fotos_listas === 'SIN_IMAGENES') {\n texto = `⚠️ Aún no he recibido imágenes para este reporte.\n\nDebes enviar al menos una foto de la propuesta ejecutada antes de escribir FOTOS LISTAS.`;\n} else if ($json.etapa === 'ESPERANDO_AUDIO') {\n texto = `⚠️ Aún falta la nota de voz obligatoria.\n\nEnvía primero la nota de voz con los datos de la propuesta ejecutada.`;\n} else if ($json.etapa === 'ESPERANDO_VIDEO') {\n texto = `✅ Las imágenes ya fueron confirmadas.\n\nAhora puedes enviar videos opcionales y luego escribir LISTO, o escribir SIN VIDEO si no tienes videos.`;\n} else {\n texto = `⚠️ No puedo cerrar la etapa de imágenes en este momento.\n\nCompleta el paso pendiente del reporte antes de continuar.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -592, -1920 ], "id": "2ad98280-d0ed-49c5-8e55-dadf3458bbab", "name": "Code - Preparar rechazo fotos listas" }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Validar fotos listas').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Validar fotos listas').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Validar fotos listas').first().json.nueva_etapa }}", "estado": "={{ $('Code - Validar fotos listas').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -384, -2480 ], "id": "6ebaa40e-769d-4ea3-8fe4-c67c745d17ac", "name": "Sheets - Actualizar sesión a video", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Validar fotos listas').first().json;\nconst cantidad = Number(sesion.imagenes_count || 0);\n\nconst texto = `✅ Imágenes recibidas: ${cantidad}.\n\n*Paso 3 de 3 — Videos opcionales*\n\nSi tienes videos de la ejecución, envíalos ahora.\n\nCuando termines de enviar los videos, escribe: LISTO\n\nSi no tienes videos, escribe: SIN VIDEO`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -176, -2480 ], "id": "3e901ae3-8c0c-428a-b11c-6b6130af29a3", "name": "Code - Preparar solicitud videos" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -368, -1216 ], "id": "17766352-4a1d-46c0-8cad-1c81e13b21ee", "name": "Sheets - Leer sesiones para video", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst now = new Date().toISOString();\n\nconst sesionActiva = rows\n .filter(row => clean(row.session_id))\n .find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n });\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n video_aceptado: false,\n motivo_video: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\n\nif (etapaActual !== 'ESPERANDO_VIDEO') {\n return [\n {\n json: {\n ...entrada,\n video_aceptado: false,\n motivo_video: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst videosCountActual = Number(sesionActiva.videos_count || 0);\n\nreturn [\n {\n json: {\n ...entrada,\n\n video_aceptado: true,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n etapa_recibida: 'ESPERANDO_VIDEO',\n etapa: 'ESPERANDO_VIDEO',\n\n audio_count: Number(sesionActiva.audio_count || 0),\n imagenes_count: Number(sesionActiva.imagenes_count || 0),\n videos_count: videosCountActual + 1,\n\n ultima_actividad: now,\n\n estado_evento: 'PENDIENTE',\n estado_sesion: 'ACTIVA',\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -160, -1216 ], "id": "cfca2f37-e675-424f-840b-bbdee7f8b6d1", "name": "Code - Resolver sesión para video" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "cae99e4c-7474-4480-a5b3-ab8f1092acfd", "leftValue": "={{ String($json.video_aceptado || '').trim().toLowerCase() === 'true' }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 48, -1216 ], "id": "66c45324-3088-4c91-834d-388a890e0d9e", "name": "IF - Video válido para sesión" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_video === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.etapa === 'ESPERANDO_AUDIO') {\n texto = `⚠️ Antes de enviar videos, debes completar la nota de voz obligatoria del reporte.`;\n} else if ($json.etapa === 'ESPERANDO_IMAGENES') {\n texto = `⚠️ Antes de enviar videos, debes enviar al menos una imagen y luego escribir: FOTOS LISTAS`;\n} else {\n texto = `⚠️ Recibí tu video, pero actualmente no corresponde al paso activo de tu reporte.\n\nCompleta el paso pendiente antes de continuar.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 256, -1120 ], "id": "d2dac207-1498-4d3c-8ca3-560f460c1975", "name": "Code - Preparar rechazo video" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 306129743, "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=306129743" }, "columns": { "mappingMode": "defineBelow", "value": { "event_id": "={{ $json.event_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "message_type": "={{ $json.message_type }}", "texto": "VIDEO RECIBIDO ", "comando": "={{ $json.accion_flujo }}", "tiene_media": "={{ $json.tiene_media }}", "media_count": "={{ $json.media_count }}", "raw_preview": "={{ $json.raw_preview }}", "estado": "={{ $json.estado_evento }}", "session_id": "={{ $json.session_id }}", "canal_origen": "={{ $json.canal_origen }}", "etapa_recibida": "={{ $json.etapa_recibida }}", "media_source_id": "={{ $json.media_source_id }}", "media_mime_type": "={{ $json.media_mime_type }}", "media_file_name": "={{ $json.media_file_name }}" }, "matchingColumns": [], "schema": [ { "id": "event_id", "displayName": "event_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "message_type", "displayName": "message_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "texto", "displayName": "texto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comando", "displayName": "comando", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tiene_media", "displayName": "tiene_media", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_count", "displayName": "media_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "raw_preview", "displayName": "raw_preview", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_procesado", "displayName": "fecha_procesado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa_recibida", "displayName": "etapa_recibida", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_source_id", "displayName": "media_source_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_mime_type", "displayName": "media_mime_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_file_name", "displayName": "media_file_name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_drive_url", "displayName": "media_drive_url", "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": [ 256, -1312 ], "id": "d3366552-d51e-41ef-96d5-cef48e3afde1", "name": "Sheets - Guardar evento video", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Resolver sesión para video').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Resolver sesión para video').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Resolver sesión para video').first().json.etapa }}", "videos_count": "={{ $('Code - Resolver sesión para video').first().json.videos_count }}", "estado": "={{ $('Code - Resolver sesión para video').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 464, -1312 ], "id": "cc528852-d9b1-4b0a-b492-52bd0d4d07ba", "name": "Sheets - Actualizar sesión tras video", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Resolver sesión para video').first().json;\nconst cantidad = Number(sesion.videos_count || 1);\n\nconst texto = `✅ Video recibido. Total de videos registrados: ${cantidad}.\n\nPuedes enviar más videos de esta ejecución.\n\nCuando termines, escribe: LISTO`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 672, -1312 ], "id": "53f352a4-906f-4018-80a8-cb687e1a44fb", "name": "Code - Preparar confirmación video recibido" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -4544, 768 ], "id": "16814259-a246-49bb-bc6d-10cb047dd843", "name": "Sheets - Leer sesiones para cierre", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst accion = norm(entrada.accion_flujo);\nconst now = new Date().toISOString();\n\nconst sesionActiva = rows\n .filter(row => clean(row.session_id))\n .find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n });\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\nconst audioCount = Number(sesionActiva.audio_count || 0);\nconst imagenesCount = Number(sesionActiva.imagenes_count || 0);\nconst videosCount = Number(sesionActiva.videos_count || 0);\n\nif (etapaActual !== 'ESPERANDO_VIDEO') {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n audio_count: audioCount,\n imagenes_count: imagenesCount,\n videos_count: videosCount,\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nif (audioCount < 1) {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'SIN_AUDIO',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nif (imagenesCount < 1) {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'SIN_IMAGENES',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nif (accion === 'LISTO' && videosCount < 1) {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'LISTO_SIN_VIDEO',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n imagenes_count: imagenesCount,\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nreturn [\n {\n json: {\n ...entrada,\n\n cierre_valido: true,\n cierre_tipo: accion,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n audio_count: audioCount,\n imagenes_count: imagenesCount,\n videos_count: videosCount,\n\n etapa_anterior: clean(sesionActiva.etapa),\n nueva_etapa: 'PROCESANDO',\n estado_sesion: 'ACTIVA',\n ultima_actividad: now,\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -4208, 784 ], "id": "876821e8-0b0d-45bd-bf03-f7978ef10e53", "name": "Code - Validar cierre reporte" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_cierre === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.motivo_cierre === 'SIN_AUDIO') {\n texto = `⚠️ No puedo procesar el reporte porque falta la nota de voz obligatoria.`;\n} else if ($json.motivo_cierre === 'SIN_IMAGENES') {\n texto = `⚠️ No puedo procesar el reporte porque falta al menos una imagen de la ejecución.`;\n} else if ($json.motivo_cierre === 'LISTO_SIN_VIDEO') {\n texto = `Aún no he recibido videos para este reporte.\n\nSi no tienes videos, escribe: SIN VIDEO\n\nSi tienes videos, envíalos y luego escribe: LISTO`;\n} else if ($json.etapa === 'ESPERANDO_AUDIO') {\n texto = `⚠️ Primero debes enviar la nota de voz obligatoria.`;\n} else if ($json.etapa === 'ESPERANDO_IMAGENES') {\n texto = `⚠️ Primero debes enviar al menos una imagen y luego escribir: FOTOS LISTAS`;\n} else {\n texto = `⚠️ Tu reporte no puede procesarse todavía porque falta completar el paso activo.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -544, 1872 ], "id": "05cc7eb1-0540-449a-84b1-ff590ba40030", "name": "Code - Preparar rechazo cierre" }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Validar cierre reporte').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Validar cierre reporte').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Validar cierre reporte').first().json.nueva_etapa }}", "estado": "={{ $('Code - Validar cierre reporte').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -3680, 592 ], "id": "4983bbb3-34d4-4d80-883f-68618fb30f68", "name": "Sheets - Actualizar sesión a procesando", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const cierre = $('Code - Validar cierre reporte').first().json;\n\nreturn {\n json: {\n session_id: cierre.session_id,\n manager_telefono: cierre.manager_telefono,\n manager_nombre: cierre.manager_nombre,\n canal_origen: cierre.canal_origen,\n\n audio_count: cierre.audio_count,\n imagenes_count: cierre.imagenes_count,\n videos_count: cierre.videos_count,\n\n cierre_tipo: cierre.cierre_tipo,\n estado_procesamiento: 'LISTO_PARA_ANALISIS_IA',\n\n whatsapp_to: cierre.manager_telefono,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1840, 416 ], "id": "28a29330-39b0-42aa-836b-d51cf02eed58", "name": "Code - Preparar procesamiento final guiado" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 306129743, "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=306129743" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1648, 416 ], "id": "7eb9dcb2-ba23-4eee-b0e6-1827940af037", "name": "Sheets - Leer eventos de sesión final", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const cierre = $('Code - Preparar procesamiento final guiado').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst sessionId = clean(cierre.session_id);\n\nconst eventosSesion = rows.filter(row => {\n return clean(row.session_id) === sessionId;\n});\n\nconst eventosPendientes = eventosSesion.filter(row => {\n const estado = norm(row.estado);\n return estado === 'PENDIENTE' || estado === '';\n});\n\nconst audios = eventosPendientes.filter(row => {\n return norm(row.message_type) === 'AUDIO';\n});\n\nconst imagenes = eventosPendientes.filter(row => {\n return norm(row.message_type) === 'IMAGE';\n});\n\nconst videos = eventosPendientes.filter(row => {\n return norm(row.message_type) === 'VIDEO';\n});\n\nconst tieneAudio = audios.length >= 1;\nconst tieneImagenes = imagenes.length >= 1;\n\nlet motivoError = '';\n\nif (!tieneAudio) {\n motivoError = 'SIN_AUDIO_REGISTRADO';\n} else if (!tieneImagenes) {\n motivoError = 'SIN_IMAGENES_REGISTRADAS';\n}\n\nconst sesionCompleta = tieneAudio && tieneImagenes;\n\nreturn [\n {\n json: {\n ...cierre,\n\n session_id: sessionId,\n\n sesion_completa_para_analizar: sesionCompleta,\n motivo_error_sesion: motivoError,\n\n total_eventos_sesion: eventosSesion.length,\n total_eventos_pendientes: eventosPendientes.length,\n\n audio_count: audios.length,\n imagenes_count: imagenes.length,\n videos_count: videos.length,\n\n audio_event_id: audios.length ? clean(audios[0].event_id) : '',\n audio_event_ids: audios.map(row => clean(row.event_id)).filter(Boolean),\n imagen_event_ids: imagenes.map(row => clean(row.event_id)).filter(Boolean),\n video_event_ids: videos.map(row => clean(row.event_id)).filter(Boolean),\n\n eventos_audio: audios,\n eventos_imagenes: imagenes,\n eventos_video: videos,\n\n estado_procesamiento: sesionCompleta\n ? 'LISTO_PARA_RECUPERAR_MEDIA'\n : 'CAPTURA_INCOMPLETA',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1440, 416 ], "id": "3c68c613-e09e-45fa-a463-f6744619acfa", "name": "Code - Consolidar sesión guiada para IA" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "11d390e1-dc34-4160-83f0-5449c93d89a3", "leftValue": "={{ String($json.sesion_completa_para_analizar || '').trim().toLowerCase() === 'true' }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -1232, 416 ], "id": "952f8fff-2af3-4676-94b4-e84426d3bb7a", "name": "IF - Sesión completa para analizar" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_error_sesion === 'SIN_AUDIO_REGISTRADO') {\n texto = `⚠️ No pude procesar tu reporte porque no encontré la nota de voz obligatoria registrada.\n\nPor favor inicia nuevamente el reporte desde el menú principal.`;\n} else if ($json.motivo_error_sesion === 'SIN_IMAGENES_REGISTRADAS') {\n texto = `⚠️ No pude procesar tu reporte porque no encontré imágenes registradas de la ejecución.\n\nPor favor inicia nuevamente el reporte desde el menú principal.`;\n} else {\n texto = `⚠️ Ocurrió un inconveniente al consolidar las evidencias de tu reporte.\n\nPor favor inicia nuevamente el reporte desde el menú principal.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -336, 1552 ], "id": "06bad8cf-95ee-473f-b468-ff0a29fb52d6", "name": "Code - Preparar error sesión incompleta" }, { "parameters": { "updates": [ "message" ], "additionalFields": {} }, "type": "n8n-nodes-base.telegramTrigger", "typeVersion": 1.3, "position": [ -7936, -832 ], "id": "46831d28-473a-43b6-8900-0b536c79beae", "name": "Telegram Trigger", "webhookId": "f90d5f34-ecba-49c2-8b5a-1eb65881723a", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "chatId": "={{ $json.telegram_chat_id || $json.whatsapp_to }}", "text": "={{ $json.telegram_text || $json.whatsapp_text }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 16848, -1168 ], "id": "ad6d1294-1d38-44e0-9404-078b9682b183", "name": "Telegram - Enviar mensaje", "webhookId": "7c767a53-6a2c-4607-bf7d-77425d598c3f", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "77d8e579-5dbc-429d-9f5f-5ad6bba8d32e", "leftValue": "={{ String($json.cierre_valido || '').trim().toLowerCase() === 'true' }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -4000, 784 ], "id": "6cdf23ad-afbf-44c2-b55e-9597ad49b579", "name": "IF - Cierre válido" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const cierre = $('Code - Validar cierre reporte').first().json;\n\nconst texto = `✅ Recibí todas las evidencias de tu reporte.\n\nEstoy procesando la nota de voz, imágenes y videos para generar el registro de la propuesta ejecutada.\n\nTe enviaré un resumen cuando termine.`;\n\nreturn {\n json: {\n ...cierre,\n whatsapp_to: cierre.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -3200, 400 ], "id": "5d37f326-8061-45d4-a142-8a40d3c8a7a3", "name": "Code - Preparar aviso procesamiento" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "3a78e681-aa9d-4849-98fe-5ba92c72c0d3", "leftValue": "={{ ['LISTO', 'SIN VIDEO'].includes(String($json.texto || '').trim().toUpperCase()) }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -6016, -1632 ], "id": "f1632e97-2543-4f6e-af30-603dac207f3e", "name": "IF - Es comando de cierre" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $json || {};\n\nconst audios = Array.isArray(sesion.eventos_audio)\n ? sesion.eventos_audio\n : [];\n\nconst audio = audios[0] || {};\n\nconst mediaSourceId = String(audio.media_source_id || '').trim();\nconst mediaMimeType = String(audio.media_mime_type || 'audio/ogg').trim();\nconst mediaFileName = String(\n audio.media_file_name || `audio_${sesion.session_id || Date.now()}.ogg`\n).trim();\n\nif (!mediaSourceId) {\n throw new Error(\n 'No se encontró media_source_id para descargar la nota de voz desde Telegram.'\n );\n}\n\nreturn {\n json: {\n ...sesion,\n\n media_tipo_descarga: 'audio',\n media_event_id: String(audio.event_id || '').trim(),\n media_source_id: mediaSourceId,\n media_mime_type: mediaMimeType,\n media_file_name: mediaFileName,\n\n audio_listo_para_descarga: true,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -608, -272 ], "id": "c8b10cb7-9bc6-4057-9123-1552a9d1e83e", "name": "Code - Preparar descarga audio Telegram" }, { "parameters": { "resource": "file", "fileId": "={{ $json.media_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ -400, -272 ], "id": "5f5c86cf-728a-476e-a8d8-07f7a3bcf663", "name": "Telegram - Descargar audio", "webhookId": "2f9a2a25-4673-493e-bd8a-4ef2db061f3a", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "jsCode": "const sesion = $input.first().json || {};\n\nconst imagenes =\n Array.isArray(sesion.eventos_imagen) ? sesion.eventos_imagen :\n Array.isArray(sesion.eventos_imagenes) ? sesion.eventos_imagenes :\n Array.isArray(sesion.eventos)\n ? sesion.eventos.filter(e =>\n String(e.message_type || '').trim().toLowerCase() === 'image'\n )\n : [];\n\nif (!imagenes.length) {\n throw new Error(\n 'La sesión indica imágenes obligatorias, pero no se encontraron eventos de imagen para descargar.'\n );\n}\n\nreturn imagenes.map((imagen, index) => {\n const mediaSourceId = String(imagen.media_source_id || '').trim();\n\n if (!mediaSourceId) {\n throw new Error(\n `La imagen ${index + 1} no tiene media_source_id para descargarla desde Telegram.`\n );\n }\n\n return {\n json: {\n session_id: sesion.session_id,\n manager_telefono: sesion.manager_telefono,\n manager_nombre: sesion.manager_nombre,\n canal_origen: sesion.canal_origen,\n\n media_tipo_descarga: 'image',\n media_index: index + 1,\n media_event_id: String(imagen.event_id || '').trim(),\n media_source_id: mediaSourceId,\n media_mime_type: String(imagen.media_mime_type || 'image/jpeg').trim(),\n media_file_name: String(\n imagen.media_file_name ||\n `imagen_${sesion.session_id || Date.now()}_${index + 1}.jpg`\n ).trim(),\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 176, 512 ], "id": "ff766af6-fe90-47e2-a7b6-06fb541d095e", "name": "Code - Preparar descargas imágenes Telegram" }, { "parameters": { "resource": "file", "fileId": "={{ $json.media_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 384, 512 ], "id": "5454de7c-b78b-4fcb-bda2-5b53da1888dd", "name": "Telegram - Descargar imágenes", "webhookId": "1260fa2e-00fa-49f4-a16b-f77ffcc84e31", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "bf6c63f0-7998-4de8-9d81-1c64ece51d8d", "leftValue": "={{ Number($json.videos_count || 0) > 0 }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 208, 848 ], "id": "c86099bc-770e-4f22-8c5a-213ade209dcb", "name": "IF - Tiene videos para descargar" }, { "parameters": { "jsCode": "const sesion = $input.first().json || {};\n\nconst videos =\n Array.isArray(sesion.eventos_video) ? sesion.eventos_video :\n Array.isArray(sesion.eventos_videos) ? sesion.eventos_videos :\n Array.isArray(sesion.eventos)\n ? sesion.eventos.filter(e =>\n String(e.message_type || '').trim().toLowerCase() === 'video'\n )\n : [];\n\nif (!videos.length) {\n throw new Error(\n 'La sesión indica que tiene videos, pero no se encontraron eventos de video para descargar.'\n );\n}\n\nreturn videos.map((video, index) => {\n const mediaSourceId = String(video.media_source_id || '').trim();\n\n if (!mediaSourceId) {\n throw new Error(\n `El video ${index + 1} no tiene media_source_id para descargarlo desde Telegram.`\n );\n }\n\n return {\n json: {\n session_id: sesion.session_id,\n manager_telefono: sesion.manager_telefono,\n manager_nombre: sesion.manager_nombre,\n canal_origen: sesion.canal_origen,\n\n media_tipo_descarga: 'video',\n media_index: index + 1,\n media_event_id: String(video.event_id || '').trim(),\n media_source_id: mediaSourceId,\n media_mime_type: String(video.media_mime_type || 'video/mp4').trim(),\n media_file_name: String(\n video.media_file_name ||\n `video_${sesion.session_id || Date.now()}_${index + 1}.mp4`\n ).trim(),\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 416, 752 ], "id": "d377f9d3-c1e1-4c29-bbc6-c45598c46d46", "name": "Code - Preparar descargas videos Telegram" }, { "parameters": { "resource": "file", "fileId": "={{ $json.media_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 624, 752 ], "id": "db9c58f0-95b4-4c14-9f0d-aba551224605", "name": "Telegram - Descargar videos", "webhookId": "6d3e1c5d-eda3-40e8-a61e-9ad2a771247c", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "resource": "audio", "operation": "analyze", "modelId": { "__rl": true, "value": "models/gemini-2.5-flash", "mode": "list", "cachedResultName": "models/gemini-2.5-flash" }, "text": "Analiza esta nota de voz enviada por un manager para reportar una propuesta ejecutada de GomezLee Marketing.\n\nTu tarea es transcribir el audio y extraer solamente la información que realmente se mencione. No inventes datos. Si un campo no se escucha o no fue informado, devuelve una cadena vacía.\n\nDevuelve únicamente un JSON válido, sin markdown, sin comillas triples y sin texto adicional, con esta estructura exacta:\n\n{\n\"transcripcion\": \"\",\n\"propuesta_referencia\": \"\",\n\"marca_cliente\": \"\",\n\"pais\": \"\",\n\"ubicacion_ejecucion\": \"\",\n\"fecha_ejecucion\": \"\",\n\"implementacion\": \"\",\n\"comentarios_resultados\": \"\",\n\"datos_faltantes\": []\n}\n\nReglas:\n\n* \"transcripcion\" debe contener la transcripción completa de lo escuchado.\n* \"propuesta_referencia\" debe contener el nombre o referencia de la propuesta ejecutada.\n* \"marca_cliente\" debe contener la marca o cliente mencionado.\n* \"pais\" debe contener el país mencionado.\n* \"ubicacion_ejecucion\" debe contener el lugar o establecimiento mencionado.\n* \"fecha_ejecucion\" debe conservar la fecha expresada en el audio.\n* \"implementacion\" debe resumir qué se implementó.\n* \"comentarios_resultados\" debe resumir observaciones o resultados mencionados.\n* \"datos_faltantes\" debe listar los nombres de los campos no informados o no identificables.\n", "inputType": "binary", "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ -160, -272 ], "id": "c1490197-9c44-4e05-8814-c698f9e4526a", "name": "Gemini - Analizar nota de voz", "credentials": { "googlePalmApi": { "id": "6Jdcd9FONxG35YuJ", "name": "Isaac - Gemini Api Gratis" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const respuestaGemini = $json || {};\n\n// La respuesta real del nodo Gemini llega dentro de content.parts[].text\nconst partes = Array.isArray(respuestaGemini.content?.parts)\n ? respuestaGemini.content.parts\n : [];\n\nconst textoRespuesta = partes\n .map((parte) => String(parte?.text || ''))\n .join('\\n')\n .trim();\n\nif (!textoRespuesta) {\n throw new Error(\n 'Gemini no devolvió texto dentro de content.parts para analizar la nota de voz.'\n );\n}\n\n// Limpiar posibles bloques markdown si Gemini los agrega en otra ejecución.\nconst limpio = textoRespuesta\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\nlet datosAudio;\n\ntry {\n datosAudio = JSON.parse(limpio);\n} catch (error) {\n throw new Error(\n 'La respuesta de Gemini no llegó como JSON válido. Respuesta recibida: ' + limpio\n );\n}\n\n// Recuperar la sesión completa consolidada anteriormente.\nconst sesion = $('Code - Consolidar sesión guiada para IA').first().json;\n\nconst texto = (valor) => String(valor || '').trim();\n\nreturn {\n json: {\n ...sesion,\n\n analisis_audio: datosAudio,\n\n transcripcion_audio: texto(datosAudio.transcripcion),\n propuesta_referencia: texto(datosAudio.propuesta_referencia),\n marca_cliente: texto(datosAudio.marca_cliente),\n pais: texto(datosAudio.pais),\n ubicacion_ejecucion: texto(datosAudio.ubicacion_ejecucion),\n fecha_ejecucion: texto(datosAudio.fecha_ejecucion),\n implementacion: texto(datosAudio.implementacion),\n comentarios_resultados: texto(datosAudio.comentarios_resultados),\n\n datos_faltantes_audio: Array.isArray(datosAudio.datos_faltantes)\n ? datosAudio.datos_faltantes.map((valor) => texto(valor)).filter(Boolean)\n : [],\n\n audio_analizado: true,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 48, -272 ], "id": "345796e5-7ecf-4910-95d9-ad454d16da61", "name": "Code - Limpiar resultado audio Gemini" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst texto = (valor) => String(valor || '').trim();\n\nconst limpiarSegmento = (valor, fallback) => {\n const limpio = texto(valor)\n .replace(/[\\/\\\\:*?\"<>|#%{}\\[\\]]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n return limpio || fallback;\n};\n\nconst convertirFechaEjecucion = () => {\n // Preparado por si luego Gemini devuelve una fecha ISO directamente.\n const fechaISO = texto(datos.fecha_ejecucion_iso);\n\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(fechaISO)) {\n return fechaISO;\n }\n\n const original = texto(datos.fecha_ejecucion);\n\n const normalizada = original\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n\n const meses = {\n enero: '01',\n febrero: '02',\n marzo: '03',\n abril: '04',\n mayo: '05',\n junio: '06',\n julio: '07',\n agosto: '08',\n septiembre: '09',\n setiembre: '09',\n octubre: '10',\n noviembre: '11',\n diciembre: '12',\n };\n\n const coincidencia = normalizada.match(\n /(primero|uno|\\d{1,2})\\s+de\\s+(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|setiembre|octubre|noviembre|diciembre)\\s+de\\s+(\\d{4})/\n );\n\n if (!coincidencia) {\n return 'SIN-FECHA';\n }\n\n const diaTexto = coincidencia[1];\n const dia =\n diaTexto === 'primero' || diaTexto === 'uno'\n ? '01'\n : String(Number(diaTexto)).padStart(2, '0');\n\n const mes = meses[coincidencia[2]];\n const anio = coincidencia[3];\n\n return `${anio}-${mes}-${dia}`;\n};\n\nconst fechaCarpeta = convertirFechaEjecucion();\n\nconst marca = limpiarSegmento(datos.marca_cliente, 'SIN-MARCA');\n\nconst propuesta = limpiarSegmento(\n datos.propuesta_referencia,\n 'SIN-REFERENCIA'\n).slice(0, 75);\n\nconst sessionId = texto(datos.session_id);\nconst sessionShort = sessionId.split('_').pop() || 'SIN-ID';\n\nconst carpetaNombre = [\n 'TEST - PE',\n fechaCarpeta,\n marca,\n propuesta,\n sessionShort,\n].join(' - ');\n\nreturn {\n json: {\n ...datos,\n fecha_carpeta: fechaCarpeta,\n carpeta_nombre_drive: carpetaNombre,\n carpeta_estado: 'PENDIENTE_DE_CREACION',\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 256, -272 ], "id": "dff11e59-38e5-48c3-96e8-eba4910fc912", "name": "Code - Preparar carpeta ejecución Drive" }, { "parameters": { "resource": "folder", "name": "={{ $json.carpeta_nombre_drive }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "1q92b3lMw_fcjD1YUtp5YcJKne49zOfpc", "mode": "list", "cachedResultName": "TEST PROPUESTAS EJECUTADAS", "cachedResultUrl": "https://drive.google.com/drive/folders/1q92b3lMw_fcjD1YUtp5YcJKne49zOfpc" }, "options": { "simplifyOutput": true } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 464, -272 ], "id": "3d6d25d5-edb3-4226-b0bc-4efc8a825696", "name": "Drive - Crear Carpeta Ejecución", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const carpetaCreada = $json || {};\n\n// Importante: usa exactamente el nombre del nodo que ya tienes funcionando.\nconst datosReporte = $('Code - Preparar carpeta ejecución Drive').first().json;\n\nconst carpetaId = String(carpetaCreada.id || '').trim();\nconst carpetaNombre = String(carpetaCreada.name || '').trim();\n\nif (!carpetaId) {\n throw new Error('Drive no devolvió el ID de la carpeta de ejecución creada.');\n}\n\nreturn {\n json: {\n ...datosReporte,\n\n carpeta_drive_id: carpetaId,\n carpeta_drive_nombre: carpetaNombre,\n carpeta_drive_url: `https://drive.google.com/drive/folders/${carpetaId}`,\n carpeta_estado: 'CREADA',\n\n presentacion_drive_id: '',\n presentacion_drive_url: '',\n audio_drive_url: '',\n imagenes_drive_urls: [],\n videos_drive_urls: [],\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 672, -272 ], "id": "1bdb3db3-1d41-4d16-a7d5-96760fa452bf", "name": "Code - Guardar Datos Carpeta Ejecución TEST" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst audios = Array.isArray(datos.eventos_audio)\n ? datos.eventos_audio\n : [];\n\nconst audio = audios[0] || {};\n\nconst audioSourceId = String(audio.media_source_id || '').trim();\nconst carpetaId = String(datos.carpeta_drive_id || '').trim();\n\nif (!carpetaId) {\n throw new Error('No se encontró carpeta_drive_id para subir el audio.');\n}\n\nif (!audioSourceId) {\n throw new Error('No se encontró media_source_id del audio para descargarlo desde Telegram.');\n}\n\nconst sessionShort = String(datos.session_id || '').split('_').pop() || 'SIN-ID';\n\nreturn {\n json: {\n ...datos,\n\n archivo_tipo: 'audio',\n archivo_event_id: String(audio.event_id || '').trim(),\n archivo_source_id: audioSourceId,\n archivo_nombre_drive: `01_Audio_Nota_de_Voz_${sessionShort}.oga`,\n archivo_mime_type: String(audio.media_mime_type || 'audio/ogg').trim(),\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 880, -272 ], "id": "429a0e1a-b3a2-4153-894a-84b8672eb7db", "name": "Code - Preparar archivo audio para Drive TEST" }, { "parameters": { "resource": "file", "fileId": "={{ $json.archivo_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 1088, -272 ], "id": "8505a6d1-52cc-48b1-abee-f3462b8e459d", "name": "Telegram - Descargar audio para Drive TEST", "webhookId": "4bbf4b44-e36e-4130-989c-6e1cbb43a2c8", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "name": "={{ $('Code - Preparar archivo audio para Drive TEST').first().json.archivo_nombre_drive }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $('Code - Guardar Datos Carpeta Ejecución TEST').first().json.carpeta_drive_id }}", "mode": "id" }, "options": { "simplifyOutput": true } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 1296, -272 ], "id": "9959df5c-3096-4690-a35e-d6c18e1a3ac1", "name": "Drive - Subir audio ejecución TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const audioSubido = $json || {};\nconst datosReporte = $('Code - Guardar Datos Carpeta Ejecución TEST').first().json;\n\nconst audioId = String(audioSubido.id || '').trim();\nconst audioNombre = String(audioSubido.name || '').trim();\nconst audioMimeType = String(audioSubido.mimeType || 'audio/ogg').trim();\n\nif (!audioId) {\n throw new Error('Drive no devolvió el ID del audio subido.');\n}\n\nreturn {\n json: {\n ...datosReporte,\n\n audio_drive_id: audioId,\n audio_drive_nombre: audioNombre,\n audio_drive_mime_type: audioMimeType,\n audio_drive_url: `https://drive.google.com/file/d/${audioId}/view`,\n\n audio_estado_drive: 'SUBIDO',\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1504, -272 ], "id": "5c2c368e-5cfa-47fa-a558-521cb5fa71f9", "name": "Code - Guardar enlace audio Drive TEST" }, { "parameters": { "jsCode": "const datos = $input.first().json || {};\n\nconst imagenes =\n Array.isArray(datos.eventos_imagen) ? datos.eventos_imagen :\n Array.isArray(datos.eventos_imagenes) ? datos.eventos_imagenes :\n Array.isArray(datos.eventos)\n ? datos.eventos.filter((evento) =>\n String(evento.message_type || '').trim().toLowerCase() === 'image'\n )\n : [];\n\nif (!imagenes.length) {\n throw new Error('No se encontraron imágenes de la ejecución para subir a Drive.');\n}\n\nconst carpetaId = String(datos.carpeta_drive_id || '').trim();\n\nif (!carpetaId) {\n throw new Error('No se encontró carpeta_drive_id para subir las imágenes.');\n}\n\nconst sessionShort = String(datos.session_id || '').split('_').pop() || 'SIN-ID';\n\nreturn imagenes.map((imagen, index) => {\n const sourceId = String(imagen.media_source_id || '').trim();\n\n if (!sourceId) {\n throw new Error(`La imagen ${index + 1} no tiene media_source_id.`);\n }\n\n const extension =\n String(imagen.media_mime_type || '').toLowerCase().includes('png')\n ? 'png'\n : 'jpg';\n\n return {\n json: {\n ...datos,\n\n archivo_tipo: 'imagen',\n archivo_indice: index + 1,\n archivo_event_id: String(imagen.event_id || '').trim(),\n archivo_source_id: sourceId,\n archivo_mime_type: String(imagen.media_mime_type || 'image/jpeg').trim(),\n archivo_nombre_drive: `02_Imagen_${String(index + 1).padStart(2, '0')}_${sessionShort}.${extension}`,\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1712, -272 ], "id": "71b5c186-d1ed-4861-8882-6d7c8dfbb5c8", "name": "Code - Preparar archivos imágenes para Drive TEST" }, { "parameters": { "resource": "file", "fileId": "={{ $json.archivo_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 1920, -272 ], "id": "991476da-845c-425d-aa0f-6e84bbffbe76", "name": "Telegram - Descargar imágenes para Drive TEST", "webhookId": "8076a6ce-3649-4e1f-b56d-3af96d9294a8", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "name": "={{ $('Code - Preparar archivos imágenes para Drive TEST').item.json.archivo_nombre_drive }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $('Code - Guardar enlace audio Drive TEST').first().json.carpeta_drive_id }}", "mode": "id" }, "options": { "simplifyOutput": true } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 2128, -272 ], "id": "2fe2033b-072e-4dea-bb86-05c600165913", "name": "Drive - Subir imágenes ejecución TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "jsCode": "const datosReporte = $('Code - Guardar enlace audio Drive TEST').first().json;\n\nconst imagenesSubidas = $input.all().map((item, index) => {\n const imagen = item.json || {};\n\n const id = String(imagen.id || '').trim();\n const nombre = String(imagen.name || '').trim();\n const mimeType = String(imagen.mimeType || 'image/jpeg').trim();\n\n if (!id) {\n throw new Error(`Drive no devolvió el ID de la imagen subida número ${index + 1}.`);\n }\n\n return {\n indice: index + 1,\n id,\n nombre,\n mime_type: mimeType,\n url: `https://drive.google.com/file/d/${id}/view`,\n };\n});\n\nif (!imagenesSubidas.length) {\n throw new Error('No se recibieron imágenes subidas desde Drive.');\n}\n\nreturn [\n {\n json: {\n ...datosReporte,\n\n imagenes_drive_ids: imagenesSubidas.map((imagen) => imagen.id),\n imagenes_drive_urls: imagenesSubidas.map((imagen) => imagen.url),\n imagenes_drive_detalle: imagenesSubidas,\n\n imagenes_estado_drive: 'SUBIDAS',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2336, -272 ], "id": "f6bb8823-74a2-4c95-a1b3-4cfe5ac4e932", "name": "Code - Guardar enlaces imágenes Drive TEST" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "4b3671bb-c95b-42bb-880e-8a7b9a9beb04", "leftValue": "={{ Number($json.videos_count || 0) > 0 }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 2544, -272 ], "id": "29829153-fe0b-44a4-91b9-0d77b8a1b8f2", "name": "IF - Tiene videos para subir Drive TEST" }, { "parameters": { "jsCode": "const datos = $input.first().json || {};\n\nconst videos =\n Array.isArray(datos.eventos_video) ? datos.eventos_video :\n Array.isArray(datos.eventos_videos) ? datos.eventos_videos :\n Array.isArray(datos.eventos)\n ? datos.eventos.filter((evento) =>\n String(evento.message_type || '').trim().toLowerCase() === 'video'\n )\n : [];\n\nif (!videos.length) {\n throw new Error('La sesión indica videos registrados, pero no se encontraron sus eventos.');\n}\n\nconst carpetaId = String(datos.carpeta_drive_id || '').trim();\n\nif (!carpetaId) {\n throw new Error('No se encontró carpeta_drive_id para subir los videos.');\n}\n\nconst sessionShort = String(datos.session_id || '').split('_').pop() || 'SIN-ID';\n\nreturn videos.map((video, index) => {\n const sourceId = String(video.media_source_id || '').trim();\n\n if (!sourceId) {\n throw new Error(`El video ${index + 1} no tiene media_source_id.`);\n }\n\n return {\n json: {\n ...datos,\n\n archivo_tipo: 'video',\n archivo_indice: index + 1,\n archivo_event_id: String(video.event_id || '').trim(),\n archivo_source_id: sourceId,\n archivo_mime_type: String(video.media_mime_type || 'video/mp4').trim(),\n archivo_nombre_drive: `03_Video_${String(index + 1).padStart(2, '0')}_${sessionShort}.mp4`,\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2864, -336 ], "id": "5be8ee1a-324f-43d9-be08-b691dfd31ba5", "name": "Code - Preparar archivos videos para Drive TEST" }, { "parameters": { "resource": "file", "fileId": "={{ $json.archivo_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 3104, -336 ], "id": "dc1d44ca-ade9-4ba7-a5be-0a6a6a380542", "name": "Telegram - Descargar videos para Drive TEST", "webhookId": "fccffec5-25ed-434b-933d-5861836ca06c", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "name": "={{ $('Code - Preparar archivos videos para Drive TEST').item.json.archivo_nombre_drive }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $('Code - Guardar enlaces imágenes Drive TEST').first().json.carpeta_drive_id }}", "mode": "id" }, "options": { "simplifyOutput": true } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 3312, -336 ], "id": "966906a3-fd55-4f32-a4b5-8a7a79221f7f", "name": "Drive - Subir videos ejecución TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "jsCode": "const datosReporte = $('Code - Guardar enlaces imágenes Drive TEST').first().json;\n\nconst videosSubidos = $input.all().map((item, index) => {\n const video = item.json || {};\n\n const id = String(video.id || '').trim();\n const nombre = String(video.name || '').trim();\n const mimeType = String(video.mimeType || 'video/mp4').trim();\n\n if (!id) {\n throw new Error(`Drive no devolvió el ID del video subido número ${index + 1}.`);\n }\n\n return {\n indice: index + 1,\n id,\n nombre,\n mime_type: mimeType,\n url: `https://drive.google.com/file/d/${id}/view`,\n };\n});\n\nif (!videosSubidos.length) {\n throw new Error('No se recibieron videos subidos desde Drive.');\n}\n\nreturn [\n {\n json: {\n ...datosReporte,\n\n videos_drive_ids: videosSubidos.map((video) => video.id),\n videos_drive_urls: videosSubidos.map((video) => video.url),\n videos_drive_detalle: videosSubidos,\n\n videos_estado_drive: 'SUBIDOS',\n evidencias_drive_completas: true,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3520, -336 ], "id": "3f4ee6df-64d4-4f82-9194-7e71ca1b4505", "name": "Code - Guardar enlaces videos Drive TEST" }, { "parameters": { "jsCode": "const datos = $input.first().json || {};\n\nconst imagenes = Array.isArray(datos.imagenes_drive_detalle)\n ? datos.imagenes_drive_detalle\n : [];\n\nif (!imagenes.length) {\n throw new Error('No se encontraron imágenes subidas a Drive para analizar con Gemini.');\n}\n\nreturn imagenes.map((imagen, index) => {\n const imagenId = String(imagen.id || '').trim();\n\n if (!imagenId) {\n throw new Error(`La imagen ${index + 1} no tiene ID de Drive.`);\n }\n\n return {\n json: {\n ...datos,\n\n evidencia_analisis_tipo: 'imagen',\n evidencia_analisis_indice: index + 1,\n\n imagen_drive_id: imagenId,\n imagen_drive_nombre: String(imagen.nombre || '').trim(),\n imagen_drive_url: String(imagen.url || '').trim(),\n imagen_drive_mime_type: String(imagen.mime_type || 'image/jpeg').trim(),\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3760, -336 ], "id": "8048a1c9-a559-46a0-ac7d-906b1e8bc636", "name": "Code - Preparar imágenes para análisis Gemini TEST" }, { "parameters": { "operation": "download", "fileId": { "__rl": true, "value": "={{ $json.imagen_drive_id }}", "mode": "id" }, "options": { "fileName": "={{ $json.imagen_drive_nombre }}" } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 3968, -336 ], "id": "ec8ee599-5ac6-41b9-9891-4057ae6451f6", "name": "Drive - Descargar imágenes para Gemini TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "resource": "image", "operation": "analyze", "modelId": { "__rl": true, "value": "models/gemini-2.5-flash-lite", "mode": "list", "cachedResultName": "models/gemini-2.5-flash-lite" }, "text": "Analiza esta imagen como evidencia visual de una propuesta ejecutada de GomezLee Marketing.\n\nExtrae únicamente lo que pueda observarse en la imagen. No inventes marca, cliente, ubicación, fecha ni resultados si no son visibles.\n\nDevuelve únicamente un JSON válido, sin markdown y sin texto adicional, con esta estructura exacta:\n\n{\n \"descripcion_visual\": \"\",\n \"elementos_ejecutados_visibles\": [],\n \"marcas_o_logos_visibles\": [],\n \"textos_visibles_relevantes\": [],\n \"personas_o_interaccion_visible\": \"\",\n \"calidad_evidencia\": \"\",\n \"es_evidencia_util\": true,\n \"alertas\": []\n}\n\nReglas:\n- \"descripcion_visual\" debe describir brevemente lo observado.\n- \"elementos_ejecutados_visibles\" debe listar elementos como stand, exhibidor, material POP, sampling, promotores, pantallas o mobiliario, solo si son visibles.\n- \"marcas_o_logos_visibles\" debe listar únicamente marcas identificables visualmente.\n- \"textos_visibles_relevantes\" debe incluir textos legibles importantes.\n- \"personas_o_interaccion_visible\" debe indicar si se observa público, promotores o interacción.\n- \"calidad_evidencia\" debe indicar si la imagen es clara, parcial, borrosa o insuficiente.\n- \"es_evidencia_util\" debe ser true si la imagen ayuda a documentar la ejecución; de lo contrario false.\n- \"alertas\" debe listar problemas como imagen irrelevante, captura de pantalla, baja calidad o información contradictoria.\n\nReglas adicionales obligatorias:\n- Una captura de pantalla de un chat, aplicación, correo, documento, configuración técnica o interfaz digital NO constituye evidencia visual de una propuesta ejecutada.\n- Si la imagen no muestra físicamente la ejecución, instalación, activación, mobiliario, material promocional, producto exhibido o interacción en el lugar, \"es_evidencia_util\" debe ser false.\n- Si la imagen es una captura de pantalla o no muestra la ejecución física, agrega en \"alertas\": \"No muestra ejecución física de la propuesta\".", "inputType": "binary", "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ 4176, -336 ], "id": "0f02c239-e05d-499f-83a7-00acc6230593", "name": "Gemini - Analizar imágenes ejecución TEST", "credentials": { "googlePalmApi": { "id": "6Jdcd9FONxG35YuJ", "name": "Isaac - Gemini Api Gratis" } } }, { "parameters": { "jsCode": "const resultadosGemini = $input.all();\n\n// Datos completos del reporte, incluyendo enlaces de Drive.\nconst datosReporte = $('Code - Guardar enlaces videos Drive TEST').first().json;\n\n// Metadatos originales de cada imagen antes de pasar por Gemini.\nconst imagenesPreparadas = $('Code - Preparar imágenes para análisis Gemini TEST')\n .all()\n .map((item) => item.json || {});\n\nconst texto = (valor) => String(valor || '').trim();\n\nconst limpiarJsonGemini = (respuesta, index) => {\n const partes = Array.isArray(respuesta.content?.parts)\n ? respuesta.content.parts\n : [];\n\n const contenido = partes\n .map((parte) => texto(parte?.text))\n .join('\\n')\n .trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\n if (!contenido) {\n throw new Error(`Gemini no devolvió análisis para la imagen ${index + 1}.`);\n }\n\n try {\n return JSON.parse(contenido);\n } catch (error) {\n throw new Error(\n `El análisis de la imagen ${index + 1} no llegó como JSON válido. Respuesta: ${contenido}`\n );\n }\n};\n\nconst analisisImagenes = resultadosGemini.map((item, index) => {\n const analisis = limpiarJsonGemini(item.json || {}, index);\n const origen = imagenesPreparadas[index] || {};\n\n return {\n indice: index + 1,\n drive_id: texto(origen.imagen_drive_id),\n drive_nombre: texto(origen.imagen_drive_nombre),\n drive_url: texto(origen.imagen_drive_url),\n descripcion_visual: texto(analisis.descripcion_visual),\n elementos_ejecutados_visibles: Array.isArray(analisis.elementos_ejecutados_visibles)\n ? analisis.elementos_ejecutados_visibles\n : [],\n marcas_o_logos_visibles: Array.isArray(analisis.marcas_o_logos_visibles)\n ? analisis.marcas_o_logos_visibles\n : [],\n textos_visibles_relevantes: Array.isArray(analisis.textos_visibles_relevantes)\n ? analisis.textos_visibles_relevantes\n : [],\n personas_o_interaccion_visible: texto(analisis.personas_o_interaccion_visible),\n calidad_evidencia: texto(analisis.calidad_evidencia),\n es_evidencia_util: analisis.es_evidencia_util === true,\n alertas: Array.isArray(analisis.alertas) ? analisis.alertas : [],\n };\n});\n\nconst imagenesUtiles = analisisImagenes.filter(\n (imagen) => imagen.es_evidencia_util === true\n);\n\nconst imagenesNoUtiles = analisisImagenes.filter(\n (imagen) => imagen.es_evidencia_util !== true\n);\n\nreturn [\n {\n json: {\n ...datosReporte,\n\n analisis_imagenes: analisisImagenes,\n imagenes_analizadas_count: analisisImagenes.length,\n imagenes_utiles_count: imagenesUtiles.length,\n imagenes_no_utiles_count: imagenesNoUtiles.length,\n imagenes_tienen_alertas: analisisImagenes.some(\n (imagen) => imagen.alertas.length > 0\n ),\n analisis_imagenes_estado: 'COMPLETADO',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 4384, -336 ], "id": "327e1e9e-1d6c-42b6-b9ef-a5a416dd7728", "name": "Code - Limpiar análisis imágenes Gemini TEST" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "de236f77-f3e9-4ffb-9a0f-7aab24d7b0ca", "leftValue": "={{ (($json.videos_drive_ids || []).length > 0) }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 4592, -336 ], "id": "f861492b-d613-4ea5-ab40-0dfbf43a586f", "name": "IF - Tiene videos para análisis Gemini TEST" }, { "parameters": { "jsCode": "const datos = $input.first().json || {};\n\nconst videos = Array.isArray(datos.videos_drive_detalle)\n ? datos.videos_drive_detalle\n : [];\n\nif (!videos.length) {\n throw new Error('No se encontraron videos subidos a Drive para analizar con Gemini.');\n}\n\nreturn videos.map((video, index) => {\n const videoId = String(video.id || '').trim();\n\n if (!videoId) {\n throw new Error(`El video ${index + 1} no tiene ID de Drive.`);\n }\n\n return {\n json: {\n ...datos,\n\n evidencia_analisis_tipo: 'video',\n evidencia_analisis_indice: index + 1,\n\n video_drive_id: videoId,\n video_drive_nombre: String(video.nombre || '').trim(),\n video_drive_url: String(video.url || '').trim(),\n video_drive_mime_type: String(video.mime_type || 'video/mp4').trim(),\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 4896, -432 ], "id": "e503dc14-ee51-4a67-8971-5d699535897d", "name": "Code - Preparar videos para análisis Gemini TEST" }, { "parameters": { "operation": "download", "fileId": { "__rl": true, "value": "={{ $json.video_drive_id }}", "mode": "id" }, "options": { "fileName": "={{ $json.video_drive_nombre }}" } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 5104, -432 ], "id": "2486867b-2c86-4001-ab53-038d86a488ae", "name": "Drive - Descargar videos para Gemini TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "resource": "video", "operation": "analyze", "modelId": { "__rl": true, "value": "models/gemini-2.5-flash-lite", "mode": "list", "cachedResultName": "models/gemini-2.5-flash-lite" }, "text": "Analiza este video como evidencia audiovisual de una propuesta ejecutada de GomezLee Marketing.\n\nExtrae únicamente lo que realmente pueda observarse en el video. No inventes marca, cliente, ubicación, fecha, resultados ni elementos de ejecución si no son visibles.\n\nDevuelve únicamente un JSON válido, sin markdown y sin texto adicional, con esta estructura exacta:\n\n{\n \"descripcion_video\": \"\",\n \"elementos_ejecutados_visibles\": [],\n \"marcas_o_logos_visibles\": [],\n \"textos_visibles_relevantes\": [],\n \"interaccion_o_actividad_visible\": \"\",\n \"calidad_evidencia\": \"\",\n \"es_evidencia_util\": true,\n \"alertas\": []\n}\n\nReglas:\n- \"descripcion_video\" debe resumir brevemente lo observado en el video.\n- \"elementos_ejecutados_visibles\" debe listar elementos físicos de ejecución como stand, exhibidor, material POP, sampling, promotores, pantallas, mobiliario o producto exhibido, solo si son visibles.\n- \"marcas_o_logos_visibles\" debe listar únicamente marcas identificables visualmente.\n- \"textos_visibles_relevantes\" debe incluir textos legibles importantes.\n- \"interaccion_o_actividad_visible\" debe indicar si se observa público, promotores, montaje, activación o interacción real.\n- \"calidad_evidencia\" debe indicar si el video es claro, parcial, borroso, muy corto o insuficiente.\n- \"es_evidencia_util\" debe ser true solo si el video ayuda a documentar físicamente la ejecución realizada.\n- Una pantalla de prueba, barras de color, captura de pantalla, grabación de una interfaz digital, archivo irrelevante o video que no muestre ejecución física NO constituye evidencia útil.\n- Si el video no muestra la ejecución física, \"es_evidencia_util\" debe ser false.\n- Si el video no es útil, agrega en \"alertas\": \"No muestra ejecución física de la propuesta\".", "inputType": "binary", "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ 5312, -432 ], "id": "42886183-7341-4e4c-bc86-4a1da3e018fa", "name": "Gemini - Analizar videos ejecución TEST", "credentials": { "googlePalmApi": { "id": "6Jdcd9FONxG35YuJ", "name": "Isaac - Gemini Api Gratis" } } }, { "parameters": { "jsCode": "const resultadosGemini = $input.all();\n\n// Recupera los datos que ya incluyen audio, enlaces Drive y análisis de imágenes.\nconst datosReporte = $('Code - Limpiar análisis imágenes Gemini TEST').first().json;\n\n// Recupera los metadatos de los videos preparados antes de enviarlos a Gemini.\nconst videosPreparados = $('Code - Preparar videos para análisis Gemini TEST')\n .all()\n .map((item) => item.json || {});\n\nconst texto = (valor) => String(valor || '').trim();\n\nconst limpiarJsonGemini = (respuesta, index) => {\n const partes = Array.isArray(respuesta.content?.parts)\n ? respuesta.content.parts\n : [];\n\n const contenido = partes\n .map((parte) => texto(parte?.text))\n .join('\\n')\n .trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\n if (!contenido) {\n throw new Error(`Gemini no devolvió análisis para el video ${index + 1}.`);\n }\n\n try {\n return JSON.parse(contenido);\n } catch (error) {\n throw new Error(\n `El análisis del video ${index + 1} no llegó como JSON válido. Respuesta: ${contenido}`\n );\n }\n};\n\nconst analisisVideos = resultadosGemini.map((item, index) => {\n const analisis = limpiarJsonGemini(item.json || {}, index);\n const origen = videosPreparados[index] || {};\n\n return {\n indice: index + 1,\n drive_id: texto(origen.video_drive_id),\n drive_nombre: texto(origen.video_drive_nombre),\n drive_url: texto(origen.video_drive_url),\n\n descripcion_video: texto(analisis.descripcion_video),\n\n elementos_ejecutados_visibles: Array.isArray(analisis.elementos_ejecutados_visibles)\n ? analisis.elementos_ejecutados_visibles\n : [],\n\n marcas_o_logos_visibles: Array.isArray(analisis.marcas_o_logos_visibles)\n ? analisis.marcas_o_logos_visibles\n : [],\n\n textos_visibles_relevantes: Array.isArray(analisis.textos_visibles_relevantes)\n ? analisis.textos_visibles_relevantes\n : [],\n\n interaccion_o_actividad_visible: texto(analisis.interaccion_o_actividad_visible),\n calidad_evidencia: texto(analisis.calidad_evidencia),\n es_evidencia_util: analisis.es_evidencia_util === true,\n alertas: Array.isArray(analisis.alertas) ? analisis.alertas : [],\n };\n});\n\nconst videosUtiles = analisisVideos.filter(\n (video) => video.es_evidencia_util === true\n);\n\nconst videosNoUtiles = analisisVideos.filter(\n (video) => video.es_evidencia_util !== true\n);\n\nreturn [\n {\n json: {\n ...datosReporte,\n\n analisis_videos: analisisVideos,\n videos_analizados_count: analisisVideos.length,\n videos_utiles_count: videosUtiles.length,\n videos_no_utiles_count: videosNoUtiles.length,\n videos_tienen_alertas: analisisVideos.some(\n (video) => video.alertas.length > 0\n ),\n analisis_videos_estado: 'COMPLETADO',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 5520, -432 ], "id": "ac45a0ee-9ea9-4852-95a9-17b420636f9d", "name": "Code - Limpiar análisis videos Gemini TEST" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst texto = (valor) => String(valor ?? '').trim();\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\nconst analisisImagenes = lista(datos.analisis_imagenes);\nconst analisisVideos = lista(datos.analisis_videos);\n\nconst imagenesUtiles = analisisImagenes.filter(\n (imagen) => imagen.es_evidencia_util === true\n);\n\nconst videosUtiles = analisisVideos.filter(\n (video) => video.es_evidencia_util === true\n);\n\nconst datosFaltantesAudio = lista(datos.datos_faltantes_audio)\n .map((valor) => texto(valor))\n .filter(Boolean);\n\nconst audioValido =\n datos.audio_analizado === true &&\n Number(datos.audio_count || 0) >= 1 &&\n datosFaltantesAudio.length === 0;\n\n// Regla de negocio:\n// La imagen es obligatoria y debe mostrar evidencia física útil.\n// El video es opcional: si existe pero no es útil, genera alerta, pero no invalida\n// una ejecución que sí tenga imagen útil.\nconst imagenObligatoriaValida = imagenesUtiles.length >= 1;\n\nconst registroAprobadoProduccion =\n audioValido && imagenObligatoriaValida;\n\nconst alertasEvidencias = [\n ...analisisImagenes.flatMap((imagen) => lista(imagen.alertas)),\n ...analisisVideos.flatMap((video) => lista(video.alertas)),\n]\n .map((alerta) => texto(alerta))\n .filter(Boolean)\n .filter((alerta, index, arreglo) => arreglo.indexOf(alerta) === index);\n\nconst estadoRegistroTest = registroAprobadoProduccion\n ? 'TEST_APROBABLE'\n : 'TEST_EVIDENCIA_NO_VALIDA';\n\nconst notaControlTest = registroAprobadoProduccion\n ? 'La evidencia cumple las validaciones mínimas para una prueba.'\n : 'Esta ejecución utiliza evidencia visual no válida para producción. Se permite continuar únicamente para validar el flujo TEST de presentación y mensajería.';\n\nconst contextoReporteFinal = {\n ambiente: 'TEST_TELEGRAM',\n control_produccion: {\n registro_aprobado_produccion: registroAprobadoProduccion,\n audio_valido: audioValido,\n imagen_obligatoria_valida: imagenObligatoriaValida,\n imagenes_utiles_count: imagenesUtiles.length,\n videos_utiles_count: videosUtiles.length,\n alertas_evidencias: alertasEvidencias,\n nota: notaControlTest,\n },\n\n datos_declarados_en_audio: {\n propuesta_referencia: texto(datos.propuesta_referencia),\n marca_cliente: texto(datos.marca_cliente),\n pais: texto(datos.pais),\n ubicacion_ejecucion: texto(datos.ubicacion_ejecucion),\n fecha_ejecucion: texto(datos.fecha_ejecucion),\n implementacion: texto(datos.implementacion),\n comentarios_resultados: texto(datos.comentarios_resultados),\n transcripcion_audio: texto(datos.transcripcion_audio),\n datos_faltantes_audio: datosFaltantesAudio,\n },\n\n evidencias_drive: {\n carpeta_url: texto(datos.carpeta_drive_url),\n audio_url: texto(datos.audio_drive_url),\n imagenes_urls: lista(datos.imagenes_drive_urls),\n videos_urls: lista(datos.videos_drive_urls),\n },\n\n analisis_imagenes: analisisImagenes,\n analisis_videos: analisisVideos,\n};\n\nreturn {\n json: {\n ...datos,\n\n modo_test: true,\n canal_prueba: 'TELEGRAM',\n\n audio_valido_para_registro: audioValido,\n imagen_obligatoria_valida: imagenObligatoriaValida,\n imagenes_utiles_para_registro_count: imagenesUtiles.length,\n videos_utiles_para_registro_count: videosUtiles.length,\n alertas_evidencias: alertasEvidencias,\n\n registro_aprobado_produccion: registroAprobadoProduccion,\n permitir_generar_presentacion_test: true,\n estado_registro_test: estadoRegistroTest,\n nota_control_test: notaControlTest,\n\n contexto_reporte_final_json: JSON.stringify(contextoReporteFinal, null, 2),\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 5728, -432 ], "id": "5236c2e6-a0c1-493c-b952-f31680bdeca1", "name": "Code - Preparar consolidado final TEST" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst contexto = String(datos.contexto_reporte_final_json || '').trim();\n\nif (!contexto) {\n throw new Error('No se encontró contexto_reporte_final_json para generar el resumen final.');\n}\n\nconst prompt = `\nEres un asistente de GomezLee Marketing encargado de documentar propuestas ejecutadas.\n\nDebes generar el resumen final estructurado de una ejecución reportada por un manager.\n\nIMPORTANTE:\n- Este registro pertenece al ambiente TEST_TELEGRAM.\n- La presentación se generará únicamente para probar el flujo.\n- Si \"registro_aprobado_produccion\" es false, debes dejar claro que la evidencia NO es válida para producción.\n- No inventes información.\n- Los datos de propuesta, marca, ubicación, fecha, implementación y resultados provienen de la nota de voz del manager; trátalos como información declarada, no como validación visual.\n- Los hallazgos visuales solo pueden basarse en imágenes o videos marcados como evidencia útil.\n- Una captura de pantalla de Telegram o un video de barras de color no valida una ejecución física.\n- No corrijas automáticamente el nombre de la propuesta aunque parezca tener error de transcripción.\n- No relaciones todavía esta ejecución con una propuesta original del Banco de Propuestas.\n\nDevuelve únicamente un JSON válido, sin markdown y sin texto adicional, con esta estructura exacta:\n\n{\n \"ambiente\": \"TEST_TELEGRAM\",\n \"estatus_validacion\": \"\",\n \"titulo_presentacion\": \"\",\n \"subtitulo_presentacion\": \"\",\n \"resumen_ejecutivo\": \"\",\n \"propuesta_referencia_reportada\": \"\",\n \"marca_cliente\": \"\",\n \"pais\": \"\",\n \"ubicacion_ejecucion\": \"\",\n \"fecha_ejecucion\": \"\",\n \"implementacion_declarada\": \"\",\n \"resultados_declarados\": \"\",\n \"evidencias\": {\n \"audio\": {\n \"recibido\": true,\n \"analizado\": true,\n \"observacion\": \"\"\n },\n \"imagenes\": {\n \"cantidad_recibida\": 0,\n \"cantidad_util\": 0,\n \"observacion\": \"\"\n },\n \"videos\": {\n \"cantidad_recibida\": 0,\n \"cantidad_util\": 0,\n \"observacion\": \"\"\n }\n },\n \"hallazgos_visuales_confirmados\": [],\n \"alertas\": [],\n \"conclusion_control\": \"\",\n \"mensaje_feedback_manager\": \"\"\n}\n\nReglas de redacción:\n- \"estatus_validacion\" debe ser exactamente \"PRUEBA - EVIDENCIA NO VÁLIDA PARA PRODUCCIÓN\" cuando registro_aprobado_produccion sea false.\n- \"titulo_presentacion\" debe comenzar con \"[TEST] Propuesta Ejecutada - \".\n- \"subtitulo_presentacion\" debe incluir marca, país y fecha declarada.\n- \"resumen_ejecutivo\" debe tener máximo 3 oraciones.\n- \"hallazgos_visuales_confirmados\" debe quedar vacío si no existe evidencia visual útil.\n- \"alertas\" debe incluir los problemas detectados en imágenes o videos.\n- \"conclusion_control\" debe explicar claramente si el reporte puede o no registrarse en producción.\n- \"mensaje_feedback_manager\" debe ser un mensaje breve para Telegram. En esta prueba debe indicar que el reporte fue procesado en modo prueba, pero que la evidencia visual no sería válida en producción.\n\nCONTEXTO CONSOLIDADO DEL REPORTE:\n${contexto}\n`;\n\nreturn {\n json: {\n ...datos,\n prompt_resumen_final: prompt.trim(),\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 5936, -432 ], "id": "6a6aa296-583c-43f1-aa64-d2fcb296d3e6", "name": "Code - Preparar prompt resumen final TEST" }, { "parameters": { "modelId": { "__rl": true, "value": "models/gemini-2.5-flash-lite", "mode": "list", "cachedResultName": "models/gemini-2.5-flash-lite" }, "messages": { "values": [ { "content": "={{ $json.prompt_resumen_final }}" } ] }, "builtInTools": {}, "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ 6144, -432 ], "id": "2e97b216-bc70-4ebe-9c50-04f03748fb5a", "name": "Gemini - Generar resumen final TEST", "credentials": { "googlePalmApi": { "id": "6Jdcd9FONxG35YuJ", "name": "Isaac - Gemini Api Gratis" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const respuestaGemini = $json || {};\n\n// Recupera los datos consolidados previos, sin arrastrar únicamente la respuesta aislada de Gemini.\nconst datosReporte = $('Code - Preparar consolidado final TEST').first().json;\n\nconst partes = Array.isArray(respuestaGemini.content?.parts)\n ? respuestaGemini.content.parts\n : [];\n\nconst textoRespuesta = partes\n .map((parte) => String(parte?.text || '').trim())\n .join('\\n')\n .trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\nif (!textoRespuesta) {\n throw new Error('Gemini no devolvió contenido para el resumen final.');\n}\n\nlet resumenFinal;\n\ntry {\n resumenFinal = JSON.parse(textoRespuesta);\n} catch (error) {\n throw new Error(\n `El resumen final de Gemini no llegó como JSON válido. Respuesta: ${textoRespuesta}`\n );\n}\n\nconst texto = (valor) => String(valor ?? '').trim();\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\nconst estatusValidacion = texto(resumenFinal.estatus_validacion);\n\nif (!estatusValidacion) {\n throw new Error('El resumen final no contiene estatus_validacion.');\n}\n\nreturn {\n json: {\n ...datosReporte,\n\n resumen_final_ia: resumenFinal,\n\n ambiente_resumen: texto(resumenFinal.ambiente),\n estatus_validacion: estatusValidacion,\n titulo_presentacion: texto(resumenFinal.titulo_presentacion),\n subtitulo_presentacion: texto(resumenFinal.subtitulo_presentacion),\n resumen_ejecutivo_final: texto(resumenFinal.resumen_ejecutivo),\n\n propuesta_referencia_final: texto(resumenFinal.propuesta_referencia_reportada),\n marca_cliente_final: texto(resumenFinal.marca_cliente),\n pais_final: texto(resumenFinal.pais),\n ubicacion_ejecucion_final: texto(resumenFinal.ubicacion_ejecucion),\n fecha_ejecucion_final: texto(resumenFinal.fecha_ejecucion),\n implementacion_declarada_final: texto(resumenFinal.implementacion_declarada),\n resultados_declarados_final: texto(resumenFinal.resultados_declarados),\n\n evidencias_resumen_final: resumenFinal.evidencias || {},\n hallazgos_visuales_confirmados_final: lista(resumenFinal.hallazgos_visuales_confirmados),\n alertas_finales: lista(resumenFinal.alertas),\n conclusion_control_final: texto(resumenFinal.conclusion_control),\n mensaje_feedback_manager_final: texto(resumenFinal.mensaje_feedback_manager),\n\n resumen_final_estado: 'GENERADO',\n permitir_generar_slides_test: true,\n bloquear_registro_banco_real: datosReporte.registro_aprobado_produccion !== true,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 6496, -432 ], "id": "b6847a73-d0cd-4e61-8b18-fa787f74cb5f", "name": "Code - Limpiar resumen final Gemini TEST" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst texto = (valor) => String(valor ?? '').trim();\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\nconst limpiarNombreArchivo = (valor) =>\n texto(valor)\n .replace(/[\\\\/:*?\"<>|]/g, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 130);\n\nconst ejecucionId =\n texto(datos.ejecucion_id) ||\n texto(datos.session_id) ||\n `TEST_${Date.now()}`;\n\nconst marca =\n texto(datos.marca_cliente_final) ||\n texto(datos.marca_cliente) ||\n 'Marca no especificada';\n\nconst cliente =\n texto(datos.cliente_final) ||\n texto(datos.cliente) ||\n 'No especificado en audio';\n\nconst propuestaReferencia =\n texto(datos.propuesta_referencia_final) ||\n texto(datos.propuesta_referencia) ||\n 'Propuesta no especificada';\n\nconst pais =\n texto(datos.pais_final) ||\n texto(datos.pais) ||\n 'País no especificado';\n\nconst ubicacion =\n texto(datos.ubicacion_ejecucion_final) ||\n texto(datos.ubicacion_ejecucion) ||\n 'Ubicación no especificada';\n\nconst fechaEjecucion =\n texto(datos.fecha_ejecucion_final) ||\n texto(datos.fecha_ejecucion) ||\n 'Fecha no especificada';\n\nconst managerNombre =\n texto(datos.manager_nombre) ||\n 'Manager no especificado';\n\nconst fotosCount = Number(datos.imagenes_count || 0);\nconst videosCount = Number(datos.videos_count || 0);\nconst audiosCount = Number(datos.audio_count || 0);\n\nconst resumenIA =\n texto(datos.resumen_ejecutivo_final) ||\n 'No se generó resumen ejecutivo.';\n\nconst descripcionEjecucion = [\n texto(datos.implementacion_declarada_final)\n ? `Implementación declarada: ${texto(datos.implementacion_declarada_final)}`\n : '',\n texto(datos.resultados_declarados_final)\n ? `Resultados declarados: ${texto(datos.resultados_declarados_final)}`\n : '',\n texto(datos.conclusion_control_final)\n ? `Control de evidencia: ${texto(datos.conclusion_control_final)}`\n : '',\n]\n .filter(Boolean)\n .join('\\n\\n');\n\nconst hallazgosVisuales = lista(datos.hallazgos_visuales_confirmados_final)\n .map((hallazgo) => texto(hallazgo))\n .filter(Boolean);\n\nconst elementosDetectados = hallazgosVisuales.length\n ? hallazgosVisuales.map((hallazgo) => `• ${hallazgo}`).join('\\n')\n : 'No se confirmaron elementos físicos a partir de la evidencia visual enviada.';\n\nconst estadoCorto = datos.registro_aprobado_produccion === true\n ? 'VALIDADO PARA REGISTRO'\n : 'TEST | EVIDENCIA NO VÁLIDA PARA PRODUCCIÓN';\n\nconst tags = [\n datos.modo_test === true ? 'TEST' : '',\n marca,\n pais,\n datos.registro_aprobado_produccion === true\n ? 'Evidencia validada'\n : 'Evidencia no válida',\n]\n .filter(Boolean)\n .map((tag) => `• ${tag}`)\n .join('\\n');\n\nconst mediaFolderUrl =\n texto(datos.carpeta_drive_url) ||\n 'Carpeta no disponible';\n\nconst nombrePresentacion = limpiarNombreArchivo(\n `[TEST] Propuesta Ejecutada - ${marca} - ${fechaEjecucion} - ${ejecucionId}`\n);\n\nconst reemplazosSlides = {\n '{{ESTATUS_VALIDACION}}': estadoCorto,\n '{{MARCA}}': marca,\n '{{CLIENTE}}': cliente,\n '{{PROPUESTA_REFERENCIA}}': propuestaReferencia,\n '{{PAIS}}': pais,\n '{{UBICACION}}': ubicacion,\n '{{FECHA_EJECUCION}}': fechaEjecucion,\n '{{EJECUCION_ID}}': ejecucionId,\n '{{MANAGER_NOMBRE}}': managerNombre,\n '{{FOTOS_COUNT}}': String(fotosCount),\n '{{VIDEOS_COUNT}}': String(videosCount),\n '{{AUDIOS_COUNT}}': String(audiosCount),\n '{{RESUMEN_IA}}': resumenIA,\n '{{DESCRIPCION_EJECUCION}}': descripcionEjecucion,\n '{{ELEMENTOS_DETECTADOS}}': elementosDetectados,\n '{{TAGS}}': tags,\n '{{MEDIA_FOLDER_URL}}': mediaFolderUrl,\n};\n\nconst solicitudesReemplazoSlides = Object.entries(reemplazosSlides).map(\n ([placeholder, valor]) => ({\n replaceAllText: {\n containsText: {\n text: placeholder,\n matchCase: true,\n },\n replaceText: valor,\n },\n })\n);\n\nreturn {\n json: {\n ...datos,\n\n ejecucion_id_slides: ejecucionId,\n presentacion_nombre_drive: nombrePresentacion,\n\n estatus_validacion_slides: estadoCorto,\n cliente_slides: cliente,\n elementos_detectados_slides: elementosDetectados,\n tags_slides: tags,\n\n reemplazos_slides: reemplazosSlides,\n solicitudes_reemplazo_slides: solicitudesReemplazoSlides,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 6704, -432 ], "id": "9e4c81b5-bf81-438f-97b1-5fc2a55546f7", "name": "Code - Preparar presentación Slides TEST" }, { "parameters": { "operation": "copy", "fileId": { "__rl": true, "value": "10PZtoMFuiXczGM9Itbcjce4EKBUhiztFbXZiTWX2DKc", "mode": "list", "cachedResultName": "GLM_Propuesta_Ejecutada_Fulgencio_TEMPLATE_V2", "cachedResultUrl": "https://docs.google.com/presentation/d/10PZtoMFuiXczGM9Itbcjce4EKBUhiztFbXZiTWX2DKc/edit?usp=drivesdk" }, "name": "={{ $json.presentacion_nombre_drive }}", "sameFolder": false, "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $json.carpeta_drive_id }}", "mode": "id" }, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 6912, -432 ], "id": "58e0e4dd-acf7-4b03-aadd-c8099fa8899b", "name": "Drive - Copiar template presentación TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const copiaPresentacion = $json || {};\n\n// Recupera todos los datos preparados antes de copiar la plantilla.\nconst datosReporte = $('Code - Preparar presentación Slides TEST').first().json;\n\nconst texto = (valor) => String(valor ?? '').trim();\n\nconst presentacionId = texto(copiaPresentacion.id);\n\nif (!presentacionId) {\n throw new Error('No se recibió el ID de la presentación copiada desde Google Drive.');\n}\n\nconst presentacionNombre =\n texto(copiaPresentacion.name) ||\n texto(datosReporte.presentacion_nombre_drive);\n\nconst presentacionUrl =\n `https://docs.google.com/presentation/d/${presentacionId}/edit`;\n\nconst solicitudesReemplazo = Array.isArray(datosReporte.solicitudes_reemplazo_slides)\n ? datosReporte.solicitudes_reemplazo_slides\n : [];\n\nif (!solicitudesReemplazo.length) {\n throw new Error('No se encontraron solicitudes_reemplazo_slides para actualizar la presentación.');\n}\n\nreturn {\n json: {\n ...datosReporte,\n\n presentacion_drive_id: presentacionId,\n presentacion_drive_nombre: presentacionNombre,\n presentacion_drive_url: presentacionUrl,\n presentacion_mime_type: texto(copiaPresentacion.mimeType),\n\n slides_reemplazos_count: solicitudesReemplazo.length,\n slides_batch_update_body: {\n requests: solicitudesReemplazo,\n },\n\n presentacion_copia_estado: 'CREADA',\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 7120, -432 ], "id": "81201353-8cb8-4e4e-856b-9bdc37bce81f", "name": "Code - Guardar datos presentación copiada TEST" }, { "parameters": { "method": "POST", "url": "={{ 'https://slides.googleapis.com/v1/presentations/' + $json.presentacion_drive_id + ':batchUpdate' }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "googleOAuth2Api", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ $json.slides_batch_update_body }}", "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 7360, -432 ], "id": "ae880386-1743-46b9-b799-a2b7cab21ac2", "name": "HTTP Request - Reemplazar textos Slides TEST", "credentials": { "googleOAuth2Api": { "id": "eHseMeH39kRcXgOF", "name": "Google account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Respuesta del HTTP Request de Google Slides.\n// Si llegamos aquí, los placeholders de la presentación ya fueron reemplazados.\nconst respuestaSlides = $json || {};\n\n// Recuperamos todos los datos completos que existían antes del HTTP Request.\nconst datos = $('Code - Guardar datos presentación copiada TEST').first().json || {};\n\nconst texto = (valor) => String(valor ?? '').trim();\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\n// -----------------------------\n// Validación de presentación\n// -----------------------------\nconst presentacionId =\n texto(respuestaSlides.presentationId) ||\n texto(datos.presentacion_drive_id);\n\nif (!presentacionId) {\n throw new Error('No se encontró el ID de la presentación generada.');\n}\n\nconst presentacionUrl =\n texto(datos.presentacion_drive_url) ||\n `https://docs.google.com/presentation/d/${presentacionId}/edit`;\n\n// -----------------------------\n// Identificadores principales\n// -----------------------------\nconst sessionId = texto(datos.session_id);\n\nconst ejecucionId =\n texto(datos.ejecucion_id_slides) ||\n texto(datos.ejecucion_id) ||\n sessionId;\n\nif (!ejecucionId) {\n throw new Error('No se encontró ejecucion_id ni session_id para guardar el registro.');\n}\n\n// -----------------------------\n// Datos declarados en audio\n// -----------------------------\nconst propuestaReferencia =\n texto(datos.propuesta_referencia_final) ||\n texto(datos.propuesta_referencia) ||\n texto(datos.propuesta_referencia_reportada);\n\nconst marca =\n texto(datos.marca_cliente_final) ||\n texto(datos.marca_cliente);\n\nconst cliente =\n texto(datos.cliente_final) ||\n texto(datos.cliente) ||\n 'No especificado en audio';\n\nconst pais =\n texto(datos.pais_final) ||\n texto(datos.pais);\n\nconst ubicacion =\n texto(datos.ubicacion_ejecucion_final) ||\n texto(datos.ubicacion_ejecucion);\n\nconst fechaEjecucion =\n texto(datos.fecha_ejecucion_final) ||\n texto(datos.fecha_ejecucion);\n\nconst implementacion =\n texto(datos.implementacion_declarada_final) ||\n texto(datos.implementacion);\n\nconst comentarioOriginal =\n texto(datos.resultados_declarados_final) ||\n texto(datos.comentarios_resultados);\n\nconst transcripcionAudio =\n texto(datos.transcripcion_audio) ||\n texto(datos.transcripcion);\n\n// -----------------------------\n// Datos generados / validados\n// -----------------------------\nconst resumenIA =\n texto(datos.resumen_ejecutivo_final) ||\n texto(datos.resumen_ejecutivo) ||\n texto(datos.reemplazos_slides?.['{{RESUMEN_IA}}']);\n\nconst descripcionEjecucion =\n texto(datos.reemplazos_slides?.['{{DESCRIPCION_EJECUCION}}']) ||\n [\n implementacion ? `Implementación declarada: ${implementacion}` : '',\n comentarioOriginal ? `Resultados declarados: ${comentarioOriginal}` : '',\n texto(datos.conclusion_control_final)\n ? `Control de evidencia: ${texto(datos.conclusion_control_final)}`\n : '',\n ]\n .filter(Boolean)\n .join('\\n\\n');\n\nconst elementosDetectados =\n texto(datos.elementos_detectados_slides) ||\n lista(datos.hallazgos_visuales_confirmados_final)\n .map((item) => texto(item))\n .filter(Boolean)\n .map((item) => `• ${item}`)\n .join('\\n') ||\n 'No se confirmaron elementos físicos a partir de la evidencia visual enviada.';\n\nconst tags =\n texto(datos.tags_slides) ||\n ['TEST', marca, pais, 'Evidencia no válida']\n .filter(Boolean)\n .map((item) => `• ${item}`)\n .join('\\n');\n\n// -----------------------------\n// Enlaces y conteos\n// -----------------------------\nconst mediaFolderUrl = texto(datos.carpeta_drive_url);\n\nconst fotosCount = Number(datos.imagenes_count || 0);\nconst videosCount = Number(datos.videos_count || 0);\nconst audiosCount = Number(datos.audio_count || 0);\n\n// -----------------------------\n// Fecha de recepción\n// -----------------------------\nconst fechaRecepcion =\n texto(datos.fecha_recepcion) ||\n texto(datos.fecha_inicio) ||\n texto(datos.eventos_audio?.[0]?.fecha_recepcion) ||\n new Date().toISOString();\n\n// -----------------------------\n// Estados de esta prueba\n// -----------------------------\n// No vinculamos con el banco real porque es una ejecución TEST\n// y la evidencia no valida una ejecución física real.\nconst estadoRevision = 'TEST_NO_VALIDO_PRODUCCION';\n\nconst motivoRevision =\n texto(datos.conclusion_control_final) ||\n 'Evidencia enviada únicamente para probar el flujo. No valida una ejecución física real.';\n\nconst propuestaMatchRevision =\n 'No se realizó match contra el banco de propuestas porque esta ejecución corresponde a una prueba TEST con evidencia no válida para producción.';\n\n// -----------------------------\n// Fila final para Google Sheets\n// Los nombres coinciden exactamente con los encabezados A:AE.\n// -----------------------------\nreturn {\n json: {\n ejecucion_id: ejecucionId,\n fecha_recepcion: fechaRecepcion,\n fecha_ejecucion: fechaEjecucion,\n manager_nombre: texto(datos.manager_nombre),\n manager_telefono: texto(datos.manager_telefono),\n\n propuesta_referencia: propuestaReferencia,\n propuesta_match_estado: 'NO_EVALUADO_TEST',\n propuesta_match_confianza: 0,\n propuesta_nombre_banco: '',\n propuesta_link_banco: '',\n\n marca: marca,\n cliente: cliente,\n pais: pais,\n ubicacion: ubicacion,\n comentario_original: comentarioOriginal,\n\n resumen_ia: resumenIA,\n descripcion_ejecucion: descripcionEjecucion,\n elementos_detectados: elementosDetectados,\n tags: tags,\n\n media_folder_url: mediaFolderUrl,\n presentacion_ejecucion_url: presentacionUrl,\n\n fotos_count: fotosCount,\n videos_count: videosCount,\n audios_count: audiosCount,\n\n estado_revision: estadoRevision,\n motivo_revision: motivoRevision,\n ultima_actualizacion: new Date().toISOString(),\n\n session_id: sessionId,\n canal_origen: texto(datos.canal_origen) || 'TELEGRAM',\n transcripcion_audio: transcripcionAudio,\n propuesta_match_revision: propuestaMatchRevision,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 7824, -432 ], "id": "287a8282-ce4a-4560-bff3-8a6bf00bb68c", "name": "Code - Preparar fila propuestas ejecutadas TEST" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 737153956, "mode": "list", "cachedResultName": "propuestas_ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=737153956" }, "columns": { "mappingMode": "autoMapInputData", "value": { "ejecucion_id": "={{ $json.ejecucion_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "fecha_ejecucion": "={{ $json.fecha_ejecucion }}", "manager_nombre": "={{ $json.manager_nombre }}", "manager_telefono": "={{ $json.manager_telefono }}", "propuesta_referencia": "={{ $json.propuesta_referencia }}", "propuesta_match_estado": "={{ $json.propuesta_match_estado }}", "propuesta_match_confianza": "={{ $json.propuesta_match_confianza }}", "propuesta_nombre_banco": "={{ $json.propuesta_nombre_banco }}", "propuesta_link_banco": "={{ $json.propuesta_link_banco }}", "marca": "={{ $json.marca }}", "cliente": "={{ $json.cliente }}", "pais": "={{ $json.cliente }}", "ubicacion": "={{ $json.ubicacion }}", "comentario_original": "={{ $json.comentario_original }}", "resumen_ia": "={{ $json.resumen_ia }}", "descripcion_ejecucion": "={{ $json.descripcion_ejecucion }}", "elementos_detectados": "={{ $json.elementos_detectados }}", "tags": "={{ $json.tags }}", "media_folder_url": "={{ $json.media_folder_url }}", "presentacion_ejecucion_url": "={{ $json.presentacion_ejecucion_url }}", "fotos_count": "={{ $json.fotos_count }}", "videos_count": "={{ $json.videos_count }}", "audios_count": "={{ $json.audios_count }}", "estado_revision": "={{ $json.estado_revision }}", "motivo_revision": "={{ $json.motivo_revision }}", "ultima_actualizacion": "={{ $json.ultima_actualizacion }}", "session_id": "={{ $json.session_id }}", "canal_origen": "={{ $json.canal_origen }}", "transcripcion_audio": "={{ $json.transcripcion_audio }}", "propuesta_match_revision": "={{ $json.propuesta_match_revision }}" }, "matchingColumns": [], "schema": [ { "id": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_ejecucion", "displayName": "fecha_ejecucion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_referencia", "displayName": "propuesta_referencia", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_match_estado", "displayName": "propuesta_match_estado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_match_confianza", "displayName": "propuesta_match_confianza", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_nombre_banco", "displayName": "propuesta_nombre_banco", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_link_banco", "displayName": "propuesta_link_banco", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "marca", "displayName": "marca", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "cliente", "displayName": "cliente", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "pais", "displayName": "pais", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ubicacion", "displayName": "ubicacion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comentario_original", "displayName": "comentario_original", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "resumen_ia", "displayName": "resumen_ia", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "descripcion_ejecucion", "displayName": "descripcion_ejecucion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "elementos_detectados", "displayName": "elementos_detectados", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tags", "displayName": "tags", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_folder_url", "displayName": "media_folder_url", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "presentacion_ejecucion_url", "displayName": "presentacion_ejecucion_url", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fotos_count", "displayName": "fotos_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audios_count", "displayName": "audios_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "estado_revision", "displayName": "estado_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actualizacion", "displayName": "ultima_actualizacion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "transcripcion_audio", "displayName": "transcripcion_audio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_match_revision", "displayName": "propuesta_match_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 8048, -432 ], "id": "2e1fb406-81b3-45a0-b305-8900ed0ede71", "name": "Sheets - Guardar propuesta ejecutada TEST", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const registroFinal = $json || {};\n\nconst texto = (valor) => String(valor ?? '').trim();\n\nconst sessionId = texto(registroFinal.session_id);\nconst ejecucionId = texto(registroFinal.ejecucion_id);\n\nif (!sessionId) {\n throw new Error('No se recibió session_id para cerrar la sesión.');\n}\n\nif (!ejecucionId) {\n throw new Error('No se recibió ejecucion_id para relacionarlo con la sesión.');\n}\n\nreturn {\n json: {\n session_id: sessionId,\n\n ultima_actividad: new Date().toISOString(),\n etapa: 'FINALIZADA',\n estado: 'COMPLETADA',\n\n ejecucion_id: ejecucionId,\n motivo_revision: texto(registroFinal.estado_revision) || 'TEST_NO_VALIDO_PRODUCCION',\n\n // Conservamos datos para el mensaje final de Telegram.\n manager_nombre: texto(registroFinal.manager_nombre),\n manager_telefono: texto(registroFinal.manager_telefono),\n telegram_chat_id: texto(registroFinal.manager_telefono),\n canal_origen: texto(registroFinal.canal_origen) || 'TELEGRAM',\n\n marca: texto(registroFinal.marca),\n propuesta_referencia: texto(registroFinal.propuesta_referencia),\n estado_revision: texto(registroFinal.estado_revision),\n media_folder_url: texto(registroFinal.media_folder_url),\n presentacion_ejecucion_url: texto(registroFinal.presentacion_ejecucion_url),\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 8272, -432 ], "id": "d1f119b8-803d-4212-b414-c00f38f8d53a", "name": "Code - Preparar actualización sesión final TEST" }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $json.session_id }}", "ultima_actividad": "={{ $json.ultima_actividad }}", "etapa": "={{ $json.etapa }}", "estado": "={{ $json.estado }}", "ejecucion_id": "={{ $json.ejecucion_id }}", "motivo_revision": "={{ $json.motivo_revision }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 8560, -432 ], "id": "a894d4b8-a9ec-4d0a-8785-036dd3da80f8", "name": "Sheets - Actualizar sesión finalizada TEST", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Recuperamos los datos antes de actualizar la sesión,\n// porque el nodo de Google Sheets solo devuelve las columnas actualizadas.\nconst datosSesion =\n $('Code - Preparar actualización sesión final TEST').first().json || {};\n\nconst registroFinal =\n $('Code - Preparar fila propuestas ejecutadas TEST').first().json || {};\n\n// ------------------------------\n// Funciones auxiliares\n// ------------------------------\nconst texto = (valor) => String(valor ?? '').trim();\n\nconst escapeHtml = (valor) =>\n texto(valor)\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst escapeUrlHtml = (valor) =>\n texto(valor)\n .replace(/&/g, '&')\n .replace(/\"/g, '"');\n\n// ------------------------------\n// Datos principales del cierre\n// ------------------------------\nconst chatId =\n texto(datosSesion.telegram_chat_id) ||\n texto(datosSesion.manager_telefono) ||\n texto(registroFinal.manager_telefono);\n\nconst ejecucionId =\n texto(registroFinal.ejecucion_id) ||\n texto(datosSesion.ejecucion_id) ||\n texto(datosSesion.session_id);\n\nconst propuestaReferencia =\n texto(registroFinal.propuesta_referencia) ||\n 'No especificada';\n\nconst marca =\n texto(registroFinal.marca) ||\n 'No especificada';\n\nconst presentacionUrl =\n texto(registroFinal.presentacion_ejecucion_url);\n\nconst carpetaUrl =\n texto(registroFinal.media_folder_url);\n\nconst estadoRevision =\n texto(registroFinal.estado_revision) ||\n texto(datosSesion.estado_revision) ||\n texto(datosSesion.motivo_revision);\n\n// ------------------------------\n// Validaciones obligatorias\n// ------------------------------\nif (!chatId) {\n throw new Error(\n 'No se encontró telegram_chat_id o manager_telefono para enviar el mensaje final.'\n );\n}\n\nif (!presentacionUrl) {\n throw new Error(\n 'No se encontró presentacion_ejecucion_url para enviar el cierre.'\n );\n}\n\nif (!carpetaUrl) {\n throw new Error(\n 'No se encontró media_folder_url para enviar el cierre.'\n );\n}\n\n// ------------------------------\n// Estado mostrado al usuario\n// ------------------------------\nlet bloqueResultado = '';\n\nif (\n estadoRevision === 'TEST_NO_VALIDO_PRODUCCION' ||\n estadoRevision === 'NO_VALIDO_PRODUCCION'\n) {\n bloqueResultado = [\n '',\n '⚠️ Resultado de prueba: la evidencia enviada no es válida para registro en producción.',\n ].join('\\n');\n} else if (\n estadoRevision === 'VALIDADO' ||\n estadoRevision === 'APROBADO' ||\n estadoRevision === 'VALIDO_PRODUCCION'\n) {\n bloqueResultado = [\n '',\n '✅ Resultado: la evidencia fue procesada correctamente para registro.',\n ].join('\\n');\n} else if (estadoRevision) {\n bloqueResultado = [\n '',\n `ℹ️ Estado de revisión: ${escapeHtml(estadoRevision)}`,\n ].join('\\n');\n}\n\n// ------------------------------\n// Mensaje final en formato HTML\n// Importante: el nodo Telegram debe usar Parse Mode = HTML.\n// ------------------------------\nconst mensaje = [\n '✅ Reporte procesado correctamente.',\n bloqueResultado,\n '',\n `Propuesta: ${escapeHtml(propuestaReferencia)}`,\n `Marca: ${escapeHtml(marca)}`,\n `ID de ejecución: ${escapeHtml(ejecucionId)}`,\n '',\n 'Presentación generada:',\n `Abrir presentación de ejecución`,\n '',\n 'Carpeta de evidencias:',\n `Abrir carpeta de evidencias`,\n '',\n 'Este reporte quedó cerrado. Ya puedes iniciar uno nuevo escribiendo: /start',\n].join('\\n');\n\n// ------------------------------\n// Salida para Telegram y compatibilidad\n// ------------------------------\nreturn {\n json: {\n ...registroFinal,\n\n telegram_chat_id: chatId,\n telegram_text: mensaje,\n\n // Compatibilidad con configuraciones previas del nodo final.\n whatsapp_to: chatId,\n whatsapp_text: mensaje,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 8768, -432 ], "id": "4748701d-2799-48e6-9a64-0c339ca7f156", "name": "Code - Preparar mensaje final Telegram TEST" }, { "parameters": { "chatId": "={{ $json.telegram_chat_id || $json.whatsapp_to }}", "text": "={{ $json.telegram_text }}", "additionalFields": { "parse_mode": "HTML" } }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 11040, -864 ], "id": "0788a62e-a657-4700-aafa-0854b5ce548f", "name": "Telegram - Enviar cierre final TEST", "webhookId": "7c767a53-6a2c-4607-bf7d-77425d598c3f", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "chatId": "={{ $json.telegram_chat_id || $json.whatsapp_to }}", "text": "={{ $json.telegram_text || $json.whatsapp_text }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ -2624, 448 ], "id": "e5ec9510-06a7-49e0-8af5-08de4b33aea8", "name": "Telegram - Enviar aviso procesamiento TEST", "webhookId": "7c767a53-6a2c-4607-bf7d-77425d598c3f", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datosSesion = $('Sheets - Actualizar sesión a procesando').first().json;\n\nreturn {\n json: datosSesion,\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -2208, 416 ], "id": "c09e5d72-1a11-4e44-895e-ab9904a6e798", "name": "Code - Recuperar datos después aviso procesamiento TEST" }, { "parameters": { "jsCode": "const datos = $json || {};\n\nreturn [\n {\n json: {\n ...datos,\n\n videos_drive_ids: [],\n videos_drive_urls: [],\n videos_drive_detalle: [],\n\n videos_estado_drive: 'SIN_VIDEO',\n videos_subidos_drive: false,\n videos_drive_completos: true,\n\n evidencias_drive_completas: true,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2768, -80 ], "id": "611cfc04-64dc-4bb0-9dde-f1db296725f3", "name": "Code - Continuar sin videos Drive TEST" }, { "parameters": { "jsCode": "const datos = $json || {};\n\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\nreturn [\n {\n json: {\n ...datos,\n\n // Aseguramos que no haya videos en esta ruta\n videos_drive_ids: lista(datos.videos_drive_ids),\n videos_drive_urls: lista(datos.videos_drive_urls),\n videos_drive_detalle: lista(datos.videos_drive_detalle),\n\n // Salida compatible para los nodos siguientes\n analisis_videos: [],\n videos_analizados: false,\n videos_estado_analisis: 'SIN_VIDEO',\n videos_alertas: [],\n videos_utiles_count: 0,\n videos_con_evidencia_util: 0,\n videos_sin_evidencia_util: 0,\n\n // Confirmación interna de continuidad\n ruta_video: 'SIN_VIDEO',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 4880, -160 ], "id": "ee345096-4deb-46d1-9e3a-ef32e6886c4d", "name": "Code - Continuar sin análisis videos TEST" } ], "connections": { "Code - Normalizar evento WhatsApp": { "main": [ [ { "node": "IF - Mensaje entrante válido", "type": "main", "index": 0 } ] ] }, "IF - Mensaje entrante válido": { "main": [ [ { "node": "IF - Es comando de cierre", "type": "main", "index": 0 } ] ] }, "Code - Resolver inicio sesión": { "main": [ [ { "node": "IF - Crear nueva sesión", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones existentes": { "main": [ [ { "node": "Code - Resolver inicio sesión", "type": "main", "index": 0 } ] ] }, "IF - Crear nueva sesión": { "main": [ [ { "node": "Sheets - Crear sesión reporte", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar aviso sesión activa", "type": "main", "index": 0 } ] ] }, "Sheets - Crear sesión reporte": { "main": [ [ { "node": "Code - Preparar bienvenida y solicitud audio", "type": "main", "index": 0 } ] ] }, "Code - Preparar bienvenida y solicitud audio": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Code - Preparar aviso sesión activa": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Switch - Acción flujo guiado": { "main": [ [ { "node": "Sheets - Leer sesiones existentes", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para audio", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para imagen", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para fotos listas", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para video", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para cierre", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para cierre", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para audio": { "main": [ [ { "node": "Code - Resolver sesión para audio", "type": "main", "index": 0 } ] ] }, "Code - Resolver sesión para audio": { "main": [ [ { "node": "IF - Audio válido para sesión", "type": "main", "index": 0 } ] ] }, "IF - Audio válido para sesión": { "main": [ [ { "node": "Sheets - Guardar evento audio", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo audio", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo audio": { "main": [ [] ] }, "Sheets - Guardar evento audio": { "main": [ [ { "node": "Sheets - Actualizar sesión tras audio", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión tras audio": { "main": [ [ { "node": "Code - Preparar solicitud imágenes", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para imagen": { "main": [ [ { "node": "Code - Resolver sesión para imagen", "type": "main", "index": 0 } ] ] }, "Code - Resolver sesión para imagen": { "main": [ [ { "node": "IF - Imagen válida para sesión", "type": "main", "index": 0 } ] ] }, "IF - Imagen válida para sesión": { "main": [ [ { "node": "Sheets - Guardar evento imagen", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo imagen", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo imagen": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Guardar evento imagen": { "main": [ [ { "node": "Sheets - Actualizar sesión tras imagen", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión tras imagen": { "main": [ [ { "node": "Code - Preparar confirmación imagen recibida", "type": "main", "index": 0 } ] ] }, "Code - Preparar confirmación imagen recibida": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para fotos listas": { "main": [ [ { "node": "Code - Validar fotos listas", "type": "main", "index": 0 } ] ] }, "Code - Validar fotos listas": { "main": [ [ { "node": "IF - Puede pasar a videos", "type": "main", "index": 0 } ] ] }, "IF - Puede pasar a videos": { "main": [ [ { "node": "Sheets - Actualizar sesión a video", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo fotos listas", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo fotos listas": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión a video": { "main": [ [ { "node": "Code - Preparar solicitud videos", "type": "main", "index": 0 } ] ] }, "Code - Preparar solicitud videos": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para video": { "main": [ [ { "node": "Code - Resolver sesión para video", "type": "main", "index": 0 } ] ] }, "Code - Resolver sesión para video": { "main": [ [ { "node": "IF - Video válido para sesión", "type": "main", "index": 0 } ] ] }, "IF - Video válido para sesión": { "main": [ [ { "node": "Sheets - Guardar evento video", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo video", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo video": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Guardar evento video": { "main": [ [ { "node": "Sheets - Actualizar sesión tras video", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión tras video": { "main": [ [ { "node": "Code - Preparar confirmación video recibido", "type": "main", "index": 0 } ] ] }, "Code - Preparar confirmación video recibido": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Code - Preparar solicitud imágenes": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para cierre": { "main": [ [ { "node": "Code - Validar cierre reporte", "type": "main", "index": 0 } ] ] }, "Code - Validar cierre reporte": { "main": [ [ { "node": "IF - Cierre válido", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo cierre": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión a procesando": { "main": [ [ { "node": "Code - Preparar aviso procesamiento", "type": "main", "index": 0 } ] ] }, "Code - Preparar procesamiento final guiado": { "main": [ [ { "node": "Sheets - Leer eventos de sesión final", "type": "main", "index": 0 } ] ] }, "Sheets - Leer eventos de sesión final": { "main": [ [ { "node": "Code - Consolidar sesión guiada para IA", "type": "main", "index": 0 } ] ] }, "Code - Consolidar sesión guiada para IA": { "main": [ [ { "node": "IF - Sesión completa para analizar", "type": "main", "index": 0 } ] ] }, "IF - Sesión completa para analizar": { "main": [ [ { "node": "Code - Preparar descarga audio Telegram", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar error sesión incompleta", "type": "main", "index": 0 } ] ] }, "Code - Preparar error sesión incompleta": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Telegram Trigger": { "main": [ [ { "node": "Code - Normalizar evento WhatsApp", "type": "main", "index": 0 } ] ] }, "IF - Cierre válido": { "main": [ [ { "node": "Sheets - Actualizar sesión a procesando", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo cierre", "type": "main", "index": 0 } ] ] }, "Code - Preparar aviso procesamiento": { "main": [ [ { "node": "Telegram - Enviar aviso procesamiento TEST", "type": "main", "index": 0 } ] ] }, "IF - Es comando de cierre": { "main": [ [ { "node": "Sheets - Leer sesiones para cierre", "type": "main", "index": 0 } ], [ { "node": "Switch - Acción flujo guiado", "type": "main", "index": 0 } ] ] }, "Code - Preparar descarga audio Telegram": { "main": [ [ { "node": "Telegram - Descargar audio", "type": "main", "index": 0 } ] ] }, "Telegram - Descargar audio": { "main": [ [ { "node": "Gemini - Analizar nota de voz", "type": "main", "index": 0 } ] ] }, "Code - Preparar descargas imágenes Telegram": { "main": [ [ { "node": "Telegram - Descargar imágenes", "type": "main", "index": 0 } ] ] }, "IF - Tiene videos para descargar": { "main": [ [ { "node": "Code - Preparar descargas videos Telegram", "type": "main", "index": 0 } ] ] }, "Code - Preparar descargas videos Telegram": { "main": [ [ { "node": "Telegram - Descargar videos", "type": "main", "index": 0 } ] ] }, "Gemini - Analizar nota de voz": { "main": [ [ { "node": "Code - Limpiar resultado audio Gemini", "type": "main", "index": 0 } ] ] }, "Code - Limpiar resultado audio Gemini": { "main": [ [ { "node": "Code - Preparar carpeta ejecución Drive", "type": "main", "index": 0 } ] ] }, "Code - Preparar carpeta ejecución Drive": { "main": [ [ { "node": "Drive - Crear Carpeta Ejecución", "type": "main", "index": 0 } ] ] }, "Drive - Crear Carpeta Ejecución": { "main": [ [ { "node": "Code - Guardar Datos Carpeta Ejecución TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar Datos Carpeta Ejecución TEST": { "main": [ [ { "node": "Code - Preparar archivo audio para Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar archivo audio para Drive TEST": { "main": [ [ { "node": "Telegram - Descargar audio para Drive TEST", "type": "main", "index": 0 } ] ] }, "Telegram - Descargar audio para Drive TEST": { "main": [ [ { "node": "Drive - Subir audio ejecución TEST", "type": "main", "index": 0 } ] ] }, "Drive - Subir audio ejecución TEST": { "main": [ [ { "node": "Code - Guardar enlace audio Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar enlace audio Drive TEST": { "main": [ [ { "node": "Code - Preparar archivos imágenes para Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar archivos imágenes para Drive TEST": { "main": [ [ { "node": "Telegram - Descargar imágenes para Drive TEST", "type": "main", "index": 0 } ] ] }, "Telegram - Descargar imágenes para Drive TEST": { "main": [ [ { "node": "Drive - Subir imágenes ejecución TEST", "type": "main", "index": 0 } ] ] }, "Drive - Subir imágenes ejecución TEST": { "main": [ [ { "node": "Code - Guardar enlaces imágenes Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar enlaces imágenes Drive TEST": { "main": [ [ { "node": "IF - Tiene videos para subir Drive TEST", "type": "main", "index": 0 } ] ] }, "IF - Tiene videos para subir Drive TEST": { "main": [ [ { "node": "Code - Preparar archivos videos para Drive TEST", "type": "main", "index": 0 } ], [ { "node": "Code - Continuar sin videos Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar archivos videos para Drive TEST": { "main": [ [ { "node": "Telegram - Descargar videos para Drive TEST", "type": "main", "index": 0 } ] ] }, "Telegram - Descargar videos para Drive TEST": { "main": [ [ { "node": "Drive - Subir videos ejecución TEST", "type": "main", "index": 0 } ] ] }, "Drive - Subir videos ejecución TEST": { "main": [ [ { "node": "Code - Guardar enlaces videos Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar enlaces videos Drive TEST": { "main": [ [ { "node": "Code - Preparar imágenes para análisis Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar imágenes para análisis Gemini TEST": { "main": [ [ { "node": "Drive - Descargar imágenes para Gemini TEST", "type": "main", "index": 0 } ] ] }, "Drive - Descargar imágenes para Gemini TEST": { "main": [ [ { "node": "Gemini - Analizar imágenes ejecución TEST", "type": "main", "index": 0 } ] ] }, "Gemini - Analizar imágenes ejecución TEST": { "main": [ [ { "node": "Code - Limpiar análisis imágenes Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Limpiar análisis imágenes Gemini TEST": { "main": [ [ { "node": "IF - Tiene videos para análisis Gemini TEST", "type": "main", "index": 0 } ] ] }, "IF - Tiene videos para análisis Gemini TEST": { "main": [ [ { "node": "Code - Preparar videos para análisis Gemini TEST", "type": "main", "index": 0 } ], [ { "node": "Code - Continuar sin análisis videos TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar videos para análisis Gemini TEST": { "main": [ [ { "node": "Drive - Descargar videos para Gemini TEST", "type": "main", "index": 0 } ] ] }, "Drive - Descargar videos para Gemini TEST": { "main": [ [ { "node": "Gemini - Analizar videos ejecución TEST", "type": "main", "index": 0 } ] ] }, "Gemini - Analizar videos ejecución TEST": { "main": [ [ { "node": "Code - Limpiar análisis videos Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Limpiar análisis videos Gemini TEST": { "main": [ [ { "node": "Code - Preparar consolidado final TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar consolidado final TEST": { "main": [ [ { "node": "Code - Preparar prompt resumen final TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar prompt resumen final TEST": { "main": [ [ { "node": "Gemini - Generar resumen final TEST", "type": "main", "index": 0 } ] ] }, "Gemini - Generar resumen final TEST": { "main": [ [ { "node": "Code - Limpiar resumen final Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Limpiar resumen final Gemini TEST": { "main": [ [ { "node": "Code - Preparar presentación Slides TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar presentación Slides TEST": { "main": [ [ { "node": "Drive - Copiar template presentación TEST", "type": "main", "index": 0 } ] ] }, "Drive - Copiar template presentación TEST": { "main": [ [ { "node": "Code - Guardar datos presentación copiada TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar datos presentación copiada TEST": { "main": [ [ { "node": "HTTP Request - Reemplazar textos Slides TEST", "type": "main", "index": 0 } ] ] }, "HTTP Request - Reemplazar textos Slides TEST": { "main": [ [ { "node": "Code - Preparar fila propuestas ejecutadas TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar fila propuestas ejecutadas TEST": { "main": [ [ { "node": "Sheets - Guardar propuesta ejecutada TEST", "type": "main", "index": 0 } ] ] }, "Sheets - Guardar propuesta ejecutada TEST": { "main": [ [ { "node": "Code - Preparar actualización sesión final TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar actualización sesión final TEST": { "main": [ [ { "node": "Sheets - Actualizar sesión finalizada TEST", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión finalizada TEST": { "main": [ [ { "node": "Code - Preparar mensaje final Telegram TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar mensaje final Telegram TEST": { "main": [ [ { "node": "Telegram - Enviar cierre final TEST", "type": "main", "index": 0 } ] ] }, "Telegram - Enviar aviso procesamiento TEST": { "main": [ [ { "node": "Code - Recuperar datos después aviso procesamiento TEST", "type": "main", "index": 0 } ] ] }, "Code - Recuperar datos después aviso procesamiento TEST": { "main": [ [ { "node": "Code - Preparar procesamiento final guiado", "type": "main", "index": 0 } ] ] }, "Code - Continuar sin videos Drive TEST": { "main": [ [ { "node": "IF - Tiene videos para análisis Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Continuar sin análisis videos TEST": { "main": [ [ { "node": "Code - Preparar consolidado final TEST", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "binaryMode": "separate", "availableInMCP": false, "timeSavedMode": "fixed", "callerPolicy": "workflowsFromSameOwner" }, "staticData": null, "meta": { "templateCredsSetupCompleted": true }, "versionId": "8b8087ff-bfbc-455b-9666-731028169ebf", "activeVersionId": "0447b997-2ad2-444c-a43a-d537e1f2d704", "versionCounter": 631, "triggerCount": 1, "shared": [ { "updatedAt": "2026-06-02T14:16:46.204Z", "createdAt": "2026-06-02T14:16:46.204Z", "role": "workflow:owner", "workflowId": "4eCPQcE3FDIll8bW", "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-08T18:29:26.000Z", "createdAt": "2026-06-08T18:27:10.687Z", "versionId": "0447b997-2ad2-444c-a43a-d537e1f2d704", "workflowId": "4eCPQcE3FDIll8bW", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "4798df84-d324-4d15-9655-653896c710fc", "responseMode": "responseNode", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -12272, 6752 ], "id": "5191681d-e92c-4cd9-9682-59fb26a9086f", "name": "Webhook", "webhookId": "4798df84-d324-4d15-9655-653896c710fc", "disabled": true }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const update = $json || {};\nconst message =\n update.message ||\n update.edited_message ||\n update.channel_post ||\n {};\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst chatId = clean(message.chat?.id);\nconst userId = clean(message.from?.id);\nconst firstName = clean(message.from?.first_name);\nconst lastName = clean(message.from?.last_name);\nconst username = clean(message.from?.username);\n\nconst managerNombre =\n [firstName, lastName].filter(Boolean).join(' ') ||\n username ||\n 'Usuario Telegram';\n\nlet messageType = 'unknown';\nlet texto = clean(message.text || message.caption);\nlet mediaSourceId = '';\nlet mediaMimeType = '';\nlet mediaFileName = '';\n\nif (message.voice) {\n messageType = 'audio';\n mediaSourceId = clean(message.voice.file_id);\n mediaMimeType = clean(message.voice.mime_type || 'audio/ogg');\n mediaFileName = `nota_voz_${message.message_id || Date.now()}.ogg`;\n} else if (message.audio) {\n messageType = 'audio';\n mediaSourceId = clean(message.audio.file_id);\n mediaMimeType = clean(message.audio.mime_type || 'audio/mpeg');\n mediaFileName =\n clean(message.audio.file_name) ||\n `audio_${message.message_id || Date.now()}.mp3`;\n} else if (message.photo && Array.isArray(message.photo) && message.photo.length) {\n messageType = 'image';\n const bestPhoto = message.photo[message.photo.length - 1];\n mediaSourceId = clean(bestPhoto.file_id);\n mediaMimeType = 'image/jpeg';\n mediaFileName = `imagen_${message.message_id || Date.now()}.jpg`;\n} else if (message.video) {\n messageType = 'video';\n mediaSourceId = clean(message.video.file_id);\n mediaMimeType = clean(message.video.mime_type || 'video/mp4');\n mediaFileName =\n clean(message.video.file_name) ||\n `video_${message.message_id || Date.now()}.mp4`;\n} else if (texto) {\n messageType = 'text';\n}\n\nfunction detectarAccion(text, type) {\n const t = norm(text);\n\n if (\n t === '/START' ||\n t === 'INICIAR' ||\n t === 'INICIAR REPORTE' ||\n t === '2' ||\n t === 'PROPUESTAS EJECUTADAS'\n ) {\n return 'INICIAR';\n }\n\n if (t === 'CANCELAR' || t === 'CANCELAR REPORTE') {\n return 'CANCELAR';\n }\n\n if (\n t === 'FOTOS LISTAS' ||\n t === 'IMAGENES LISTAS' ||\n t === 'IMÁGENES LISTAS'\n ) {\n return 'FOTOS_LISTAS';\n }\n\n if (\n t === 'SIN VIDEO' ||\n t === 'NO TENGO VIDEO' ||\n t === 'NO HAY VIDEO'\n ) {\n return 'SIN_VIDEO';\n }\n\n if (t === 'LISTO' || t === 'FINALIZAR' || t === 'PROCESAR') {\n return 'LISTO';\n }\n\n if (type === 'audio') return 'MEDIA_AUDIO';\n if (type === 'image') return 'MEDIA_IMAGEN';\n if (type === 'video') return 'MEDIA_VIDEO';\n\n return 'MENSAJE';\n}\n\nconst accionFlujo = detectarAccion(texto, messageType);\nconst tieneMedia = ['audio', 'image', 'video'].includes(messageType);\n\nreturn {\n json: {\n event_id: clean(message.message_id) || `TG-${Date.now()}`,\n fecha_recepcion: new Date().toISOString(),\n\n canal_origen: 'TELEGRAM',\n\n // Usaremos el user ID de Telegram como identificador del manager\n // en esta versión de prueba para reutilizar la lógica existente.\n manager_telefono: userId,\n manager_nombre: managerNombre,\n usuario_id_origen: userId,\n\n telegram_chat_id: chatId,\n telegram_user_id: userId,\n\n from_me: false,\n\n message_type: messageType,\n texto,\n accion_flujo: accionFlujo,\n\n tiene_media: tieneMedia,\n media_count: tieneMedia ? 1 : 0,\n\n media_source_id: mediaSourceId,\n media_mime_type: mediaMimeType,\n media_file_name: mediaFileName,\n\n raw_preview: JSON.stringify(update).slice(0, 1500),\n raw_event: update,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -7712, -832 ], "id": "f2eb6fde-d967-407b-8eb2-b77390e098b9", "name": "Code - Normalizar evento WhatsApp" }, { "parameters": { "respondWith": "json", "responseBody": "={{\n {\n ok: true,\n received: true,\n event_id: $json.event_id\n }\n}}", "options": {} }, "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ -11840, 6752 ], "id": "1b6ca190-22ef-45dd-b704-873eab5f196c", "name": "Respond - OK Evolution", "disabled": true }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "f4d8feac-ea04-4689-806a-42d4e97d451b", "leftValue": "={{ $json.from_me }}", "rightValue": "", "operator": { "type": "boolean", "operation": "false", "singleValue": true } }, { "id": "758ddbe7-3ec0-4a00-8812-7fcad7c3767d", "leftValue": "={{ $json.manager_telefono }}", "rightValue": "", "operator": { "type": "string", "operation": "notEmpty", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -7520, -832 ], "id": "66ec0b50-31fa-4da6-aeeb-5757d2863428", "name": "IF - Mensaje entrante válido" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const managerTelefono = String($json.manager_telefono || '').replace(/\\D/g, '');\n\nconst textoFormato = `Para reportar una propuesta ejecutada, envía la información así:\n\nEJECUTADA\nPropuesta: nombre o enlace de la propuesta\nMarca/Cliente:\nPaís:\nUbicación:\nFecha de ejecución:\nComentario:\n\nLuego envía las fotos de la ejecución.\n\nCuando termines, escribe: LISTO\n\nImportante: envía una propuesta ejecutada a la vez. Cuando termines, escribe LISTO antes de iniciar otro reporte.`;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -2640, 8240 ], "id": "473f688d-d4e3-4a76-bd20-d7c220ae74df", "name": "Code - Preparar formato WhatsApp" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw", "mode": "list", "cachedResultName": "Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": "gid=0", "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw/edit#gid=0" }, "columns": { "mappingMode": "defineBelow", "value": { "event_id": "={{ $json.event_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "message_type": "={{ $json.message_type }}", "texto": "={{ $json.texto }}", "comando": "={{ $json.comando }}", "tiene_media": "={{ $json.tiene_media }}", "media_count": "={{ $json.media_count }}", "raw_preview": "={{ $json.raw_preview }}", "estado": "=PENDIENTE" }, "matchingColumns": [], "schema": [ { "id": "event_id", "displayName": "event_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "message_type", "displayName": "message_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "texto", "displayName": "texto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comando", "displayName": "comando", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tiene_media", "displayName": "tiene_media", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_count", "displayName": "media_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "raw_preview", "displayName": "raw_preview", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "estado", "displayName": "estado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -2992, 10576 ], "id": "0ce71ac2-210c-4f00-b04f-ad3ae880b233", "name": "Sheets - Guardar evento WhatsApp", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const managerTelefono = String($json.manager_telefono || '').replace(/\\D/g, '');\n\nconst texto = `Recibido. Puedes enviar más fotos o información de la propuesta ejecutada.\n\nCuando termines, escribe: LISTO`;\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: managerTelefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -2784, 10576 ], "id": "65224c67-d6e9-4052-ac3e-82c047f76b91", "name": "Code - Preparar confirmación recibido" }, { "parameters": { "jsCode": "const managerTelefono = String($json.manager_telefono || '').replace(/\\D/g, '');\n\nlet texto;\n\nif ($json.estado_revision === 'REQUIERE_REVISION') {\n texto = `Recibí tu solicitud para procesar la propuesta ejecutada, pero hay información que debemos revisar:\n\n${$json.motivo_revision}\n\nPuedes enviar la información faltante y luego escribir LISTO nuevamente.`;\n} else {\n texto = `Perfecto. Recibí la información de la propuesta ejecutada.\n\nEventos recibidos: ${$json.eventos_pendientes_count}\nFotos: ${$json.fotos_count}\nVideos: ${$json.videos_count}\nAudios: ${$json.audios_count}\n\nEn la próxima fase el sistema analizará la información, guardará los archivos y generará el registro correspondiente.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: managerTelefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -2544, 9376 ], "id": "fd81ad2c-0d3c-4aeb-bacb-e22f7820e64d", "name": "Code - Preparar respuesta LISTO" }, { "parameters": { "documentId": { "__rl": true, "value": "1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw", "mode": "list", "cachedResultName": "Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": "gid=0", "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw/edit#gid=0" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -4960, 8944 ], "id": "226b4cc3-fe5a-44b6-9af5-d68ecdad3e07", "name": "Sheets - Leer eventos pendientes", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const rows = $input.all();\n\nconst telefonoActual = String(\n $('Code - Normalizar evento WhatsApp').first().json.manager_telefono || ''\n).replace(/\\D/g, '');\n\nconst managerNombreActual =\n $('Code - Normalizar evento WhatsApp').first().json.manager_nombre || '';\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nfunction extractField(text, label) {\n const regex = new RegExp(`${label}\\\\s*:\\\\s*(.+)`, 'i');\n const match = clean(text).match(regex);\n return match ? clean(match[1]) : '';\n}\n\nconst eventos = rows\n .map(item => item.json || {})\n .filter(row => {\n const tel = String(row.manager_telefono || '').replace(/\\D/g, '');\n const estado = norm(row.estado || 'PENDIENTE');\n return tel === telefonoActual && estado === 'PENDIENTE';\n });\n\nconst textos = eventos\n .map(e => clean(e.texto))\n .filter(Boolean);\n\nconst textoCompleto = textos.join('\\n\\n');\n\nconst fotosCount = eventos.filter(e => norm(e.message_type) === 'IMAGE').length;\nconst videosCount = eventos.filter(e => norm(e.message_type) === 'VIDEO').length;\nconst audiosCount = eventos.filter(e => norm(e.message_type) === 'AUDIO').length;\n\nconst propuestaReferencia =\n extractField(textoCompleto, 'Propuesta') ||\n extractField(textoCompleto, 'Propuesta ejecutada');\n\nconst marcaCliente =\n extractField(textoCompleto, 'Marca/Cliente') ||\n extractField(textoCompleto, 'Marca') ||\n extractField(textoCompleto, 'Cliente');\n\nconst pais = extractField(textoCompleto, 'País') || extractField(textoCompleto, 'Pais');\nconst ubicacion = extractField(textoCompleto, 'Ubicación') || extractField(textoCompleto, 'Ubicacion');\nconst fechaEjecucion =\n extractField(textoCompleto, 'Fecha de ejecución') ||\n extractField(textoCompleto, 'Fecha ejecucion') ||\n extractField(textoCompleto, 'Fecha');\n\nconst comentario = extractField(textoCompleto, 'Comentario') || textoCompleto;\n\nconst now = new Date();\nconst fechaISO = now.toISOString();\n\nconst ejecucionId = `EJEC-${fechaISO.slice(0, 10).replace(/-/g, '')}-${telefonoActual.slice(-4)}-${Math.random().toString(36).slice(2, 6).toUpperCase()}`;\n\nconst motivosRevision = [];\n\nif (!eventos.length) motivosRevision.push('No se encontraron eventos pendientes para este número.');\nif (!propuestaReferencia) motivosRevision.push('Falta nombre o enlace de la propuesta.');\nif (!marcaCliente) motivosRevision.push('Falta marca o cliente.');\nif (!pais) motivosRevision.push('Falta país.');\nif (!ubicacion) motivosRevision.push('Falta ubicación.');\nif (fotosCount === 0 && videosCount === 0 && audiosCount === 0) {\n motivosRevision.push('No se detectó media en la sesión pendiente.');\n}\n\nconst estadoRevision = motivosRevision.length ? 'REQUIERE_REVISION' : 'PENDIENTE_PROCESAR';\n\nreturn [\n {\n json: {\n ejecucion_id: ejecucionId,\n fecha_recepcion: fechaISO,\n fecha_ejecucion: fechaEjecucion,\n manager_nombre: managerNombreActual,\n manager_telefono: telefonoActual,\n\n propuesta_referencia: propuestaReferencia,\n marca_cliente: marcaCliente,\n pais,\n ubicacion,\n comentario_original: comentario,\n\n eventos_pendientes_count: eventos.length,\n fotos_count: fotosCount,\n videos_count: videosCount,\n audios_count: audiosCount,\n\n estado_revision: estadoRevision,\n motivo_revision: motivosRevision.join(' | '),\n\n texto_sesion: textoCompleto,\n eventos_sesion: eventos,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -4720, 8944 ], "id": "ae5d5cd5-21c2-48ce-a146-ddeb0c0fda50", "name": "Code - Consolidar sesión LISTO" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "4425ec41-389a-45cf-8454-e54ab9168298", "leftValue": "={{ $json.estado_revision }}", "rightValue": "PENDIENTE_PROCESAR", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -4496, 8944 ], "id": "215b217d-6c79-427c-9d26-7d1cd486a20a", "name": "IF - Sesión lista para procesar" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "function clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction slug(value, max = 50) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .toUpperCase()\n .slice(0, max);\n}\n\nfunction paisCorto(value) {\n const p = slug(value, 40);\n\n const mapa = {\n 'REPUBLICA-DOMINICANA': 'RD',\n 'DOMINICANA': 'RD',\n 'PANAMA': 'PA',\n 'GUATEMALA': 'GT',\n 'COSTA-RICA': 'CR',\n 'EL-SALVADOR': 'SV',\n 'HONDURAS': 'HN',\n 'NICARAGUA': 'NI',\n 'COLOMBIA': 'CO',\n 'MEXICO': 'MX',\n 'PUERTO-RICO': 'PR',\n };\n\n return mapa[p] || p || 'SIN-PAIS';\n}\n\nconst fechaRaw = clean($json.fecha_ejecucion) || new Date().toISOString().slice(0, 10);\nconst fechaCompacta = fechaRaw.replace(/\\D/g, '').slice(0, 8) || new Date().toISOString().slice(0, 10).replace(/-/g, '');\n\nconst pais = paisCorto($json.pais);\n\nconst marcaCliente = slug($json.marca_cliente || $json.marca || $json.cliente || 'SIN-MARCA', 35);\n\nconst proyecto = slug(\n $json.propuesta_referencia ||\n $json.propuesta_nombre_banco ||\n $json.comentario_original ||\n 'SIN-PROYECTO',\n 45\n);\n\nconst telefono4 = clean($json.manager_telefono).replace(/\\D/g, '').slice(-4) || '0000';\n\nconst baseName = `EJEC_${fechaCompacta}_${pais}_${marcaCliente}_${proyecto}_${telefono4}`;\n\nreturn {\n json: {\n ...$json,\n drive_folder_name: baseName,\n presentation_file_name: `${baseName} - Reporte propuesta ejecutada`,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -3888, 8800 ], "id": "1200e236-05e2-4f1e-a7f6-b3193996a12a", "name": "Code - Preparar carpeta ejecución" }, { "parameters": { "resource": "folder", "name": "={{ $json.drive_folder_name }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "16b9efHlPm0GjXaY0dYomtTU1GPu23VrU", "mode": "list", "cachedResultName": "Presentaciones de propuestas ejecutadas", "cachedResultUrl": "https://drive.google.com/drive/folders/16b9efHlPm0GjXaY0dYomtTU1GPu23VrU" }, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ -3680, 8800 ], "id": "ade0f176-e786-4e68-8d47-59b228b722a9", "name": "Drive - Crear carpeta ejecución", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Consolidar sesión LISTO').first().json;\n\nconst prompt = `\nAnaliza esta información de una propuesta ejecutada.\n\nDebes responder únicamente en JSON válido, sin markdown y sin texto adicional.\n\nDatos disponibles:\n- Ejecución ID: ${sesion.ejecucion_id}\n- Manager: ${sesion.manager_nombre}\n- Teléfono: ${sesion.manager_telefono}\n- Propuesta referencia: ${sesion.propuesta_referencia}\n- Marca/Cliente: ${sesion.marca_cliente}\n- País: ${sesion.pais}\n- Ubicación: ${sesion.ubicacion}\n- Fecha ejecución: ${sesion.fecha_ejecucion}\n- Comentario original: ${sesion.comentario_original}\n- Texto completo de sesión:\n${sesion.texto_sesion}\n\nDevuelve este JSON:\n{\n \"resumen_ia\": \"\",\n \"descripcion_ejecucion\": \"\",\n \"elementos_detectados\": \"\",\n \"tags\": \"\",\n \"cliente\": \"\",\n \"marca\": \"\",\n \"estado_revision_sugerido\": \"\",\n \"motivo_revision_sugerido\": \"\"\n}\n\nReglas:\n- No inventes datos que no estén en el texto.\n- Si no puedes separar cliente y marca, coloca el mismo valor inferido en ambos o deja vacío el campo que no sepas.\n- Los tags deben ser palabras o frases cortas separadas por coma.\n- estado_revision_sugerido debe ser \"OK\" si la información parece suficiente o \"REQUIERE_REVISION\" si faltan datos importantes.\n`;\n\nreturn {\n json: {\n ...sesion,\n prompt_analisis_ia: prompt,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -3472, 8800 ], "id": "918c6060-78f4-41a1-9600-5f5d41dfd761", "name": "Code - Preparar análisis IA ejecución" }, { "parameters": { "modelId": { "__rl": true, "value": "models/gemini-2.5-flash-lite", "mode": "list", "cachedResultName": "models/gemini-2.5-flash-lite" }, "messages": { "values": [ { "content": "={{ $json.prompt_analisis_ia }}" } ] }, "builtInTools": {}, "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.1, "position": [ -3264, 8800 ], "id": "cc9f9e6b-919b-4711-82c2-235256980561", "name": "Gemini - Analizar texto ejecución", "credentials": { "googlePalmApi": { "id": "jvsXYwL6IOoY2DBU", "name": "Isaac - Gemini Api Pago" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Consolidar sesión LISTO').first().json;\nconst drive = $('Drive - Crear carpeta ejecución').first().json || {};\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction parseAI(value) {\n const raw = clean(value);\n\n if (!raw) return {};\n\n try {\n return JSON.parse(raw);\n } catch (e) {\n const match = raw.match(/\\{[\\s\\S]*\\}/);\n if (match) {\n try {\n return JSON.parse(match[0]);\n } catch (err) {}\n }\n }\n\n return {\n resumen_ia: raw,\n };\n}\n\nconst rawAI =\n $json.output ||\n $json.text ||\n $json.response ||\n $json.content ||\n $json.message ||\n '';\n\nconst ai = parseAI(rawAI);\n\nconst driveId = clean(drive.id);\nconst mediaFolderUrl =\n clean(drive.webViewLink) ||\n clean(drive.webUrl) ||\n (driveId ? `https://drive.google.com/drive/folders/${driveId}` : 'PENDIENTE');\n\nconst estadoRevisionIA = clean(ai.estado_revision_sugerido);\nconst motivoRevisionIA = clean(ai.motivo_revision_sugerido);\n\nconst estadoRevision =\n estadoRevisionIA ||\n sesion.estado_revision ||\n 'PENDIENTE';\n\nconst motivoRevision =\n motivoRevisionIA ||\n sesion.motivo_revision ||\n '';\n\nreturn {\n json: {\n ejecucion_id: sesion.ejecucion_id,\n fecha_recepcion: sesion.fecha_recepcion,\n fecha_ejecucion: sesion.fecha_ejecucion,\n\n manager_nombre: sesion.manager_nombre,\n manager_telefono: sesion.manager_telefono,\n\n propuesta_referencia: sesion.propuesta_referencia,\n\n propuesta_match_estado: 'PENDIENTE_MATCH',\n propuesta_match_confianza: '',\n propuesta_nombre_banco: '',\n propuesta_link_banco: '',\n\n marca: clean(ai.marca) || sesion.marca_cliente,\n cliente: clean(ai.cliente) || sesion.marca_cliente,\n pais: sesion.pais,\n ubicacion: sesion.ubicacion,\n\n comentario_original: sesion.comentario_original,\n\n resumen_ia: clean(ai.resumen_ia) || 'PENDIENTE',\n descripcion_ejecucion: clean(ai.descripcion_ejecucion) || 'PENDIENTE',\n elementos_detectados: clean(ai.elementos_detectados) || 'PENDIENTE',\n tags: clean(ai.tags) || 'PENDIENTE',\n\n media_folder_url: mediaFolderUrl,\n presentacion_ejecucion_url: 'PENDIENTE',\n\n fotos_count: sesion.fotos_count,\n videos_count: sesion.videos_count,\n audios_count: sesion.audios_count,\n\n estado_revision: estadoRevision,\n motivo_revision: motivoRevision,\n ultima_actualizacion: new Date().toISOString(),\n\n eventos_sesion: sesion.eventos_sesion,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -2912, 8800 ], "id": "ae2e9fbf-2bcb-46e4-b9e8-2db121b71f89", "name": "Code - Preparar fila propuesta ejecutada" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw", "mode": "list", "cachedResultName": "Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 1766200759, "mode": "list", "cachedResultName": "propuestas_ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw/edit#gid=1766200759" }, "columns": { "mappingMode": "defineBelow", "value": { "ultima_actualizacion": "={{ $json.ultima_actualizacion }}", "motivo_revision": "={{ $json.motivo_revision }}", "estado_revision": "={{ $json.estado_revision }}", "audios_count": "={{ $json.audios_count }}", "videos_count": "={{ $json.videos_count }}", "fotos_count": "={{ $json.fotos_count }}", "presentacion_ejecucion_url": "={{ $json.presentacion_ejecucion_url }}", "media_folder_url": "={{ $json.media_folder_url }}", "tags": "={{ $json.tags }}", "elementos_detectados": "={{ $json.elementos_detectados }}", "descripcion_ejecucion": "={{ $json.descripcion_ejecucion }}", "resumen_ia": "={{ $json.resumen_ia }}", "comentario_original": "={{ $json.comentario_original }}", "ubicacion": "={{ $json.ubicacion }}", "pais": "={{ $json.pais }}", "cliente": "={{ $json.cliente }}", "marca": "={{ $json.marca }}", "propuesta_link_banco": "={{ $json.propuesta_link_banco }}", "propuesta_nombre_banco": "={{ $json.propuesta_nombre_banco }}", "propuesta_match_confianza": "={{ $json.propuesta_match_confianza }}", "propuesta_match_estado": "={{ $json.propuesta_match_estado }}", "propuesta_referencia": "={{ $json.propuesta_referencia }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "fecha_ejecucion": "={{ $json.fecha_ejecucion }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "ejecucion_id": "={{ $json.ejecucion_id }}" }, "matchingColumns": [], "schema": [ { "id": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_ejecucion", "displayName": "fecha_ejecucion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_referencia", "displayName": "propuesta_referencia", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_match_estado", "displayName": "propuesta_match_estado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_match_confianza", "displayName": "propuesta_match_confianza", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_nombre_banco", "displayName": "propuesta_nombre_banco", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_link_banco", "displayName": "propuesta_link_banco", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "marca", "displayName": "marca", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "cliente", "displayName": "cliente", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "pais", "displayName": "pais", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ubicacion", "displayName": "ubicacion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comentario_original", "displayName": "comentario_original", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "resumen_ia", "displayName": "resumen_ia", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "descripcion_ejecucion", "displayName": "descripcion_ejecucion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "elementos_detectados", "displayName": "elementos_detectados", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tags", "displayName": "tags", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_folder_url", "displayName": "media_folder_url", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "presentacion_ejecucion_url", "displayName": "presentacion_ejecucion_url", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fotos_count", "displayName": "fotos_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audios_count", "displayName": "audios_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "estado_revision", "displayName": "estado_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actualizacion", "displayName": "ultima_actualizacion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1872, 8800 ], "id": "32a654ab-880e-4449-af1f-cae755a76cf1", "name": "Sheets - Guardar propuesta ejecutada", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const filaFinal = $('Code - Preparar fila propuesta ejecutada').first().json;\nconst eventos = filaFinal.eventos_sesion || [];\n\nfunction getRowNumber(row) {\n return (\n row.row_number ||\n row.__row_number ||\n row.__rowNumber ||\n row.rowNumber ||\n row._row_number ||\n ''\n );\n}\n\nreturn eventos\n .map(evento => {\n return {\n json: {\n ...evento,\n row_number: getRowNumber(evento),\n estado: 'PROCESADO',\n ejecucion_id: filaFinal.ejecucion_id,\n fecha_procesado: new Date().toISOString(),\n },\n };\n })\n .filter(item => item.json.row_number);" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1664, 8800 ], "id": "71217f03-1c8b-43d7-906b-6f0c133eb1f4", "name": "Code - Preparar eventos para marcar procesados" }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw", "mode": "list", "cachedResultName": "Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": "gid=0", "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pXW_Vy__UwFgUY6zjjHEHqjz01nr6Q2eXWpM2YPVfYw/edit#gid=0" }, "columns": { "mappingMode": "defineBelow", "value": { "event_id": "={{ $json.event_id }}", "estado": "={{ $json.estado }}", "ejecucion_id": "={{ $json.ejecucion_id }}", "fecha_procesado": "={{ $json.fecha_procesado }}" }, "matchingColumns": [ "event_id" ], "schema": [ { "id": "event_id", "displayName": "event_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "message_type", "displayName": "message_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "texto", "displayName": "texto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comando", "displayName": "comando", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tiene_media", "displayName": "tiene_media", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_count", "displayName": "media_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "raw_preview", "displayName": "raw_preview", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_procesado", "displayName": "fecha_procesado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1456, 8800 ], "id": "0ede07a5-f64d-4edb-9021-76f48e4bd844", "name": "Sheets - Marcar eventos procesados", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const fila = $('Code - Preparar fila propuesta ejecutada').first().json;\n\nconst texto = `Listo. Registré la propuesta ejecutada.\n\nID: ${fila.ejecucion_id}\nMarca/Cliente: ${fila.marca || fila.cliente || 'No disponible'}\nPaís: ${fila.pais || 'No disponible'}\nUbicación: ${fila.ubicacion || 'No disponible'}\n\nGuardé el registro para revisión y procesamiento final.`;\n\nreturn {\n json: {\n ...fila,\n whatsapp_to: String(fila.manager_telefono || '').replace(/\\D/g, ''),\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1248, 8800 ], "id": "1b04a14f-ef37-4214-a298-90e30a6e4278", "name": "Code - Preparar confirmación procesado" }, { "parameters": { "method": "POST", "url": "={{'https://wsp.gomezleemarketing.com'}}/message/sendText/{{'botsoporte'}}", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "={{'C267126ABB45-4C12-B626-6BAB1833F5D7'}}" } ] }, "sendBody": true, "bodyParameters": { "parameters": [ { "name": "number", "value": "={{ String($json.whatsapp_to || '').includes('@') ? String($json.whatsapp_to) : String($json.whatsapp_to || '').replace(/\\D/g, '') + '@s.whatsapp.net' }}" }, { "name": "text", "value": "={{ $json.whatsapp_text }}" }, { "name": "delay", "value": "={{2000}}" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 8128, 6880 ], "id": "4e2f9d5b-887f-40ed-95f9-a5b6721050d9", "name": "HTTP Request - Enviar mensaje WhatsApp" }, { "parameters": { "operation": "copy", "fileId": { "__rl": true, "value": "1oDY713ydM5ef9VkfmLPUB3st7qBYPKLU", "mode": "list", "cachedResultName": "TEMPLATE_Propuesta_Ejecutada_Fulgencio.pptx", "cachedResultUrl": "https://docs.google.com/presentation/d/1oDY713ydM5ef9VkfmLPUB3st7qBYPKLU/edit?usp=drivesdk&ouid=110013484451765346305&rtpof=true&sd=true" }, "name": "={{ $('Code - Preparar carpeta ejecución').first().json.presentation_file_name }}", "sameFolder": false, "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $('Drive - Crear carpeta ejecución').first().json.id }}", "mode": "id" }, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ -2704, 8800 ], "id": "456fb9bd-cb1c-416b-935f-9c9f6a9bc921", "name": "Drive - Copiar template presentación", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "jsCode": "const fila = $('Code - Preparar fila propuesta ejecutada').first().json;\nconst slide = $('Drive - Copiar template presentación').first().json || {};\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nconst presentationId = clean(slide.id);\n\nconst presentationUrl =\n clean(slide.webViewLink) ||\n clean(slide.webUrl) ||\n (presentationId ? `https://docs.google.com/presentation/d/${presentationId}/edit` : 'PENDIENTE');\n\nconst replacements = {\n '{{EJECUCION_ID}}': fila.ejecucion_id,\n '{{FECHA_EJECUCION}}': fila.fecha_ejecucion,\n '{{MARCA}}': fila.marca,\n '{{CLIENTE}}': fila.cliente,\n '{{PAIS}}': fila.pais,\n '{{UBICACION}}': fila.ubicacion,\n '{{PROPUESTA_REFERENCIA}}': fila.propuesta_referencia,\n '{{MANAGER_NOMBRE}}': fila.manager_nombre,\n '{{RESUMEN_IA}}': fila.resumen_ia,\n '{{DESCRIPCION_EJECUCION}}': fila.descripcion_ejecucion,\n '{{ELEMENTOS_DETECTADOS}}': fila.elementos_detectados,\n '{{TAGS}}': fila.tags,\n '{{MEDIA_FOLDER_URL}}': fila.media_folder_url,\n '{{FOTOS_COUNT}}': fila.fotos_count,\n '{{VIDEOS_COUNT}}': fila.videos_count,\n '{{AUDIOS_COUNT}}': fila.audios_count,\n};\n\nconst requests = Object.entries(replacements).map(([placeholder, value]) => ({\n replaceAllText: {\n containsText: {\n text: placeholder,\n matchCase: true,\n },\n replaceText: clean(value) || 'No disponible',\n },\n}));\n\nreturn {\n json: {\n ...fila,\n presentation_id: presentationId,\n presentacion_ejecucion_url: presentationUrl,\n slides_requests: requests,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -2496, 8800 ], "id": "b081a544-b13d-421d-8e52-8206be087dc1", "name": "Code - Preparar reemplazos Slides" }, { "parameters": { "method": "POST", "url": "={{ 'https://slides.googleapis.com/v1/presentations/' + $json.presentation_id + ':batchUpdate' }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "googleOAuth2Api", "sendBody": true, "specifyBody": "json", "jsonBody": "={{\n {\n requests: $json.slides_requests\n }\n}}", "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ -2288, 8800 ], "id": "c66aaace-b635-43cb-9977-669a98d8fcbf", "name": "HTTP Request - Reemplazar textos Slides", "credentials": { "googleOAuth2Api": { "id": "eHseMeH39kRcXgOF", "name": "Google account 2" } } }, { "parameters": { "jsCode": "const fila = $('Code - Preparar reemplazos Slides').first().json;\n\nreturn {\n json: {\n ...fila,\n presentacion_ejecucion_url: fila.presentacion_ejecucion_url || 'PENDIENTE',\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -2080, 8800 ], "id": "48666a6c-dea5-4195-a257-136d4ba70193", "name": "Code - Actualizar link presentación" }, { "parameters": { "workflowInputs": { "values": [ { "name": "phone" } ] } }, "type": "n8n-nodes-base.executeWorkflowTrigger", "typeVersion": 1.1, "position": [ -9744, 8112 ], "id": "9a0322b0-396a-4cd1-8829-a841fe2aa784", "name": "When Executed by Another Workflow", "disabled": true }, { "parameters": { "rules": { "values": [ { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "leftValue": "={{ $json.comando }}", "rightValue": "=FORMATO", "operator": { "type": "string", "operation": "equals" }, "id": "c749d60c-b479-42d2-ab4b-32f4ab5ddcb8" } ], "combinator": "and" }, "renameOutput": "={{ true }}" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "c46d57fa-81c0-42e4-a602-6983df1d33b1", "leftValue": "={{ $json.comando }}", "rightValue": "=LISTO", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": "={{ true }}" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "27e1a704-d8e0-442b-a9ed-28430b91429a", "leftValue": "={{ $json.comando }}", "rightValue": "=NORMAL", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": "={{ true }}" } ] }, "options": {} }, "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ -8816, 7232 ], "id": "574a721b-22fa-4fb0-b1a4-da15d17958ea", "name": "ANTERIOR - Switch Comando WhatsApp" }, { "parameters": { "rules": { "values": [ { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "leftValue": "={{ $json.accion_flujo }}", "rightValue": "INICIAR", "operator": { "type": "string", "operation": "equals" }, "id": "d056529b-35e5-4b59-81ea-6390368dcc4a" } ], "combinator": "and" }, "renameOutput": true, "outputKey": "INICIAR" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "8113bb38-04a1-4272-8db8-29cb469d4404", "leftValue": "={{ $json.accion_flujo }}", "rightValue": "MEDIA_AUDIO", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "MEDIA_AUDIO" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "2f985186-3795-4cbc-ac92-e154ade8d7d2", "leftValue": "={{ $json.accion_flujo }}", "rightValue": "MEDIA_IMAGEN", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "MEDIA_IMAGEN" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "2e257327-485f-46c7-b718-b0b2aba3a912", "leftValue": "={{ $json.accion_flujo }}", "rightValue": "FOTOS_LISTAS", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "FOTOS_LISTAS" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "bdfd1a30-d1b1-4072-bfda-168c912317fe", "leftValue": "={{ String($json.message_type || '').trim().toLowerCase() }}", "rightValue": "video", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "MEDIA_VIDEO " }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "72090cce-6818-43ab-913e-d191b61f50a3", "leftValue": "={{ String($json.texto || '').trim().toUpperCase() }}", "rightValue": "SIN_VIDEO ", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "SIN_VIDEO" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "6f8333ec-fc61-4c12-bf9e-24d9f21ac1be", "leftValue": "={{ String($json.texto || '').trim().toUpperCase() }}", "rightValue": "LISTO ", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "LISTO " } ] }, "options": {} }, "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ -4720, -2448 ], "id": "d844de66-b9de-489e-bce6-71b5dab3d384", "name": "Switch - Acción flujo guiado" }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\n\nconst sesionesValidas = rows.filter(row => clean(row.session_id));\n\nconst sesionActiva = sesionesValidas.find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n});\n\nif (sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n crear_nueva_sesion: false,\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n estado: clean(sesionActiva.estado),\n audio_count: Number(sesionActiva.audio_count || 0),\n imagenes_count: Number(sesionActiva.imagenes_count || 0),\n videos_count: Number(sesionActiva.videos_count || 0),\n },\n },\n ];\n}\n\nconst now = new Date().toISOString();\nconst stamp = now.replace(/\\D/g, '').slice(0, 14);\nconst telefono4 = telefono.slice(-4) || '0000';\nconst random = Math.random().toString(36).slice(2, 6).toUpperCase();\n\nconst sessionId = `SES_${stamp}_${telefono4}_${random}`;\n\nreturn [\n {\n json: {\n ...entrada,\n\n crear_nueva_sesion: true,\n\n session_id: sessionId,\n manager_telefono: telefono,\n manager_nombre: clean(entrada.manager_nombre),\n canal_origen: clean(entrada.canal_origen || 'WHATSAPP'),\n\n fecha_inicio: now,\n ultima_actividad: now,\n\n etapa: 'ESPERANDO_AUDIO',\n\n audio_count: 0,\n imagenes_count: 0,\n videos_count: 0,\n\n estado: 'ACTIVA',\n ejecucion_id: '',\n motivo_revision: '',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1024, -5040 ], "id": "61a18e64-32ac-47ff-8427-7f5cbc223de3", "name": "Code - Resolver inicio sesión" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1232, -5040 ], "id": "08eaa473-383e-4230-ade6-3602a6a7c3d1", "name": "Sheets - Leer sesiones existentes", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "b9b21561-7dbb-470f-9bae-22189a2982ad", "leftValue": "={{ $json.crear_nueva_sesion }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -816, -5040 ], "id": "82a8ed63-2e86-4c27-91f8-1e424d15b09b", "name": "IF - Crear nueva sesión" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $json.session_id }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "canal_origen": "={{ $json.canal_origen }}", "fecha_inicio": "={{ $json.fecha_inicio }}", "ultima_actividad": "={{ $json.ultima_actividad }}", "etapa": "={{ $json.etapa }}", "audio_count": "={{ $json.audio_count }}", "imagenes_count": "={{ $json.imagenes_count }}", "videos_count": "={{ $json.videos_count }}", "estado": "={{ $json.estado }}", "ejecucion_id": "={{ $json.ejecucion_id }}", "motivo_revision": "={{ $json.motivo_revision }}" }, "matchingColumns": [], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -432, -5088 ], "id": "da72ce9f-f96f-4b3d-a8a4-bc3e80bfece5", "name": "Sheets - Crear sesión reporte", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Resolver inicio sesión').first().json;\n\nconst nombre = String(sesion.manager_nombre || '').trim();\nconst saludo = nombre ? `¡Hola, ${nombre}!` : '¡Hola!';\n\nconst texto = `📸 *Propuestas Ejecutadas - GLM*\n\n${saludo} Bienvenido al registro de propuestas ejecutadas.\n\nEste proceso permite documentar ejecuciones reales para alimentar el banco de evidencias de Fulgencio Fumado.\n\n*Paso 1 de 3 — Nota de voz obligatoria*\n\nEnvía una nota de voz indicando:\n\n• Nombre o referencia de la propuesta ejecutada\n• Marca o cliente\n• País\n• Ubicación de la ejecución\n• Fecha de ejecución\n• Qué se implementó\n• Comentarios o resultados observados\n\nImportante: registra una sola propuesta ejecutada a la vez.`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -256, -5088 ], "id": "0c5a1f3b-c7a8-48cc-a9c6-f43e06f76186", "name": "Code - Preparar bienvenida y solicitud audio" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const etapa = String($json.etapa || '').trim();\n\nconst instrucciones = {\n ESPERANDO_AUDIO: 'Actualmente estoy esperando la nota de voz obligatoria.',\n ESPERANDO_IMAGENES: 'Actualmente estoy esperando las imágenes obligatorias de la ejecución.',\n ESPERANDO_VIDEO: 'Actualmente estoy esperando videos opcionales o la confirmación SIN VIDEO.',\n PROCESANDO: 'Tu reporte se encuentra en procesamiento.',\n};\n\nconst detalle =\n instrucciones[etapa] ||\n 'Tienes un reporte activo pendiente de completar.';\n\nconst texto = `⚠️ Ya tienes un reporte de propuesta ejecutada en proceso.\n\n${detalle}\n\nCompleta ese reporte antes de iniciar uno nuevo. Si deseas cancelarlo, escribe: CANCELAR`;\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -448, -4832 ], "id": "0a2e63f8-ef9d-4aa6-9e4a-7e7a92dec42d", "name": "Code - Preparar aviso sesión activa" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1280, -4064 ], "id": "412c6cef-b6c2-4cf0-97c6-c87ed900c668", "name": "Sheets - Leer sesiones para audio", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst now = new Date().toISOString();\n\nconst sesionesValidas = rows.filter(row => clean(row.session_id));\n\nconst sesionActiva = sesionesValidas.find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n});\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n audio_aceptado: false,\n motivo_audio: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\n\nif (etapaActual !== 'ESPERANDO_AUDIO') {\n return [\n {\n json: {\n ...entrada,\n audio_aceptado: false,\n motivo_audio: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst audioCountActual = Number(sesionActiva.audio_count || 0);\n\nreturn [\n {\n json: {\n ...entrada,\n\n audio_aceptado: true,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n etapa_recibida: 'ESPERANDO_AUDIO',\n etapa: 'ESPERANDO_AUDIO',\n nueva_etapa: 'ESPERANDO_IMAGENES',\n\n audio_count: audioCountActual + 1,\n imagenes_count: Number(sesionActiva.imagenes_count || 0),\n videos_count: Number(sesionActiva.videos_count || 0),\n\n ultima_actividad: now,\n\n estado_evento: 'PENDIENTE',\n estado_sesion: 'ACTIVA',\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1072, -4064 ], "id": "3baee4b0-e7c3-41b9-beb6-d7521797ca26", "name": "Code - Resolver sesión para audio" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "dd1bbbb7-a9de-4214-b4a7-7d105803dc3f", "leftValue": "={{ $json.audio_aceptado }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -864, -4064 ], "id": "7321ac9c-141b-4205-833c-6234463b5f0c", "name": "IF - Audio válido para sesión" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_audio === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.etapa === 'ESPERANDO_IMAGENES') {\n texto = `✅ Ya recibí la nota de voz de tu reporte.\n\nAhora debes enviar al menos una imagen de la ejecución.\n\nCuando termines de enviar las imágenes, escribe: FOTOS LISTAS`;\n} else if ($json.etapa === 'ESPERANDO_VIDEO') {\n texto = `✅ La nota de voz y las imágenes ya fueron recibidas.\n\nAhora puedes enviar videos opcionales y luego escribir LISTO, o escribir SIN VIDEO si no tienes videos.`;\n} else {\n texto = `⚠️ Recibí tu nota de voz, pero actualmente no corresponde al paso activo de tu reporte.\n\nCompleta el paso pendiente antes de continuar.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -656, -3968 ], "id": "03c759bb-1def-48ea-9a35-42346716d57c", "name": "Code - Preparar rechazo audio" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 306129743, "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=306129743" }, "columns": { "mappingMode": "defineBelow", "value": { "event_id": "={{ $json.event_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "message_type": "={{ $json.message_type }}", "texto": "NOTA DE VOZ RECIBIDA", "comando": "={{ $json.accion_flujo }}", "tiene_media": "={{ $json.tiene_media }}", "media_count": "={{ $json.media_count }}", "raw_preview": "={{ $json.raw_preview }}", "fecha_procesado": "=", "estado": "={{ $json.estado_evento }}", "session_id": "={{ $json.session_id }}", "canal_origen": "={{ $json.canal_origen }}", "etapa_recibida": "={{ $json.etapa_recibida }}", "media_source_id": "={{ $json.media_source_id }}", "media_mime_type": "={{ $json.media_mime_type }}", "media_file_name": "={{ $json.media_file_name }}" }, "matchingColumns": [], "schema": [ { "id": "event_id", "displayName": "event_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "message_type", "displayName": "message_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "texto", "displayName": "texto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comando", "displayName": "comando", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tiene_media", "displayName": "tiene_media", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_count", "displayName": "media_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "raw_preview", "displayName": "raw_preview", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_procesado", "displayName": "fecha_procesado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa_recibida", "displayName": "etapa_recibida", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_source_id", "displayName": "media_source_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_mime_type", "displayName": "media_mime_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_file_name", "displayName": "media_file_name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_drive_url", "displayName": "media_drive_url", "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": [ -656, -4160 ], "id": "7538b493-9f75-4870-92ca-83ff68ced808", "name": "Sheets - Guardar evento audio", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Resolver sesión para audio').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Resolver sesión para audio').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Resolver sesión para audio').first().json.nueva_etapa }}", "audio_count": "={{ $('Code - Resolver sesión para audio').first().json.audio_count }}", "estado": "={{ $('Code - Resolver sesión para audio').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "videos_count", "displayName": "videos_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "estado", "displayName": "estado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -448, -4160 ], "id": "adb59f9f-f1db-415d-8aef-ffcfec1f6fac", "name": "Sheets - Actualizar sesión tras audio", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Resolver sesión para audio').first().json;\n\nconst texto = `✅ Nota de voz recibida.\n\n*Paso 2 de 3 — Imágenes obligatorias*\n\nAhora envía al menos una foto de la propuesta ejecutada.\n\nPuedes enviar varias imágenes seguidas.\n\nCuando termines de enviar las imágenes, escribe: FOTOS LISTAS`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -240, -4160 ], "id": "714069de-e125-472d-8287-309212587180", "name": "Code - Preparar solicitud imágenes" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1312, -3072 ], "id": "2823d97e-f5f0-4547-bae2-7cf2e270107b", "name": "Sheets - Leer sesiones para imagen", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst now = new Date().toISOString();\n\nconst sesionesValidas = rows.filter(row => clean(row.session_id));\n\nconst sesionActiva = sesionesValidas.find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n});\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n imagen_aceptada: false,\n motivo_imagen: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\n\nif (etapaActual !== 'ESPERANDO_IMAGENES') {\n return [\n {\n json: {\n ...entrada,\n imagen_aceptada: false,\n motivo_imagen: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst imagenesCountActual = Number(sesionActiva.imagenes_count || 0);\n\nreturn [\n {\n json: {\n ...entrada,\n\n imagen_aceptada: true,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n etapa_recibida: 'ESPERANDO_IMAGENES',\n etapa: 'ESPERANDO_IMAGENES',\n\n audio_count: Number(sesionActiva.audio_count || 0),\n imagenes_count: imagenesCountActual + 1,\n videos_count: Number(sesionActiva.videos_count || 0),\n\n ultima_actividad: now,\n\n estado_evento: 'PENDIENTE',\n estado_sesion: 'ACTIVA',\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1104, -3072 ], "id": "b9efdab7-0dc2-46aa-9f58-f11cf3419ef0", "name": "Code - Resolver sesión para imagen" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "daee475e-88ff-4d39-9823-17bd9d13435f", "leftValue": "={{ $json.imagen_aceptada }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -896, -3072 ], "id": "b29bbb17-19e4-4aac-9758-b97831e1263d", "name": "IF - Imagen válida para sesión" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_imagen === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.etapa === 'ESPERANDO_AUDIO') {\n texto = `⚠️ Antes de enviar imágenes, debes enviar la nota de voz obligatoria del reporte.\n\nIndica en la nota de voz la propuesta ejecutada, marca o cliente, país, ubicación, fecha y detalles de la ejecución.`;\n} else if ($json.etapa === 'ESPERANDO_VIDEO') {\n texto = `✅ Las imágenes de tu reporte ya fueron registradas.\n\nAhora puedes enviar videos opcionales y luego escribir LISTO, o escribir SIN VIDEO si no tienes videos.`;\n} else {\n texto = `⚠️ Recibí tu imagen, pero actualmente no corresponde al paso activo de tu reporte.\n\nCompleta el paso pendiente antes de continuar.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -688, -2976 ], "id": "b3dfdde1-9f91-489d-b426-3174c810db49", "name": "Code - Preparar rechazo imagen" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 306129743, "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=306129743" }, "columns": { "mappingMode": "defineBelow", "value": { "event_id": "={{ $json.event_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "message_type": "={{ $json.message_type }}", "texto": "IMAGEN RECIBIDA", "comando": "={{ $json.accion_flujo }}", "tiene_media": "={{ $json.tiene_media }}", "media_count": "={{ $json.media_count }}", "raw_preview": "={{ $json.raw_preview }}", "estado": "={{ $json.estado_evento }}", "session_id": "={{ $json.session_id }}", "canal_origen": "={{ $json.canal_origen }}", "etapa_recibida": "={{ $json.etapa_recibida }}", "media_source_id": "={{ $json.media_source_id }}", "media_mime_type": "={{ $json.media_mime_type }}", "media_file_name": "={{ $json.media_file_name }}" }, "matchingColumns": [], "schema": [ { "id": "event_id", "displayName": "event_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "message_type", "displayName": "message_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "texto", "displayName": "texto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comando", "displayName": "comando", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tiene_media", "displayName": "tiene_media", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_count", "displayName": "media_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "raw_preview", "displayName": "raw_preview", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_procesado", "displayName": "fecha_procesado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa_recibida", "displayName": "etapa_recibida", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_source_id", "displayName": "media_source_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_mime_type", "displayName": "media_mime_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_file_name", "displayName": "media_file_name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_drive_url", "displayName": "media_drive_url", "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": [ -560, -3216 ], "id": "3048e00d-c0f6-4935-967d-1d7785f098d3", "name": "Sheets - Guardar evento imagen", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Resolver sesión para imagen').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Resolver sesión para imagen').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Resolver sesión para imagen').first().json.etapa }}", "imagenes_count": "={{ $('Code - Resolver sesión para imagen').first().json.imagenes_count }}", "estado": "={{ $('Code - Resolver sesión para imagen').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -352, -3216 ], "id": "712f692d-04e2-40ec-8c0d-ee468910c802", "name": "Sheets - Actualizar sesión tras imagen", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Resolver sesión para imagen').first().json;\nconst cantidad = Number(sesion.imagenes_count || 1);\n\nconst texto = `✅ Imagen recibida. Total de imágenes registradas: ${cantidad}.\n\nPuedes enviar más imágenes de esta ejecución.\n\nCuando termines de enviar las imágenes, escribe: FOTOS LISTAS`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -176, -3216 ], "id": "311e44a1-af81-4998-9daa-3e9dd2fbd829", "name": "Code - Preparar confirmación imagen recibida" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1360, -2048 ], "id": "9bc8b9d0-6d43-4059-8901-331136476ad4", "name": "Sheets - Leer sesiones para fotos listas", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst now = new Date().toISOString();\n\nconst sesionActiva = rows\n .filter(row => clean(row.session_id))\n .find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n });\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n fotos_listas_valido: false,\n motivo_fotos_listas: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\nconst imagenesCount = Number(sesionActiva.imagenes_count || 0);\n\nif (etapaActual !== 'ESPERANDO_IMAGENES') {\n return [\n {\n json: {\n ...entrada,\n fotos_listas_valido: false,\n motivo_fotos_listas: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n imagenes_count: imagenesCount,\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nif (imagenesCount < 1) {\n return [\n {\n json: {\n ...entrada,\n fotos_listas_valido: false,\n motivo_fotos_listas: 'SIN_IMAGENES',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n imagenes_count: imagenesCount,\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nreturn [\n {\n json: {\n ...entrada,\n\n fotos_listas_valido: true,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n etapa: clean(sesionActiva.etapa),\n nueva_etapa: 'ESPERANDO_VIDEO',\n\n audio_count: Number(sesionActiva.audio_count || 0),\n imagenes_count: imagenesCount,\n videos_count: Number(sesionActiva.videos_count || 0),\n\n ultima_actividad: now,\n estado_sesion: 'ACTIVA',\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1152, -2048 ], "id": "43a983b9-c467-4523-bc0e-8309fbd9a61b", "name": "Code - Validar fotos listas" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "67809c33-d2b5-4c2a-b551-fac585b1d0a3", "leftValue": "={{ $json.fotos_listas_valido }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -944, -2048 ], "id": "14d30604-b0a9-42b5-bbc1-9660fda01ee3", "name": "IF - Puede pasar a videos" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_fotos_listas === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.motivo_fotos_listas === 'SIN_IMAGENES') {\n texto = `⚠️ Aún no he recibido imágenes para este reporte.\n\nDebes enviar al menos una foto de la propuesta ejecutada antes de escribir FOTOS LISTAS.`;\n} else if ($json.etapa === 'ESPERANDO_AUDIO') {\n texto = `⚠️ Aún falta la nota de voz obligatoria.\n\nEnvía primero la nota de voz con los datos de la propuesta ejecutada.`;\n} else if ($json.etapa === 'ESPERANDO_VIDEO') {\n texto = `✅ Las imágenes ya fueron confirmadas.\n\nAhora puedes enviar videos opcionales y luego escribir LISTO, o escribir SIN VIDEO si no tienes videos.`;\n} else {\n texto = `⚠️ No puedo cerrar la etapa de imágenes en este momento.\n\nCompleta el paso pendiente del reporte antes de continuar.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -592, -1920 ], "id": "2ad98280-d0ed-49c5-8e55-dadf3458bbab", "name": "Code - Preparar rechazo fotos listas" }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Validar fotos listas').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Validar fotos listas').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Validar fotos listas').first().json.nueva_etapa }}", "estado": "={{ $('Code - Validar fotos listas').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -384, -2480 ], "id": "6ebaa40e-769d-4ea3-8fe4-c67c745d17ac", "name": "Sheets - Actualizar sesión a video", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Validar fotos listas').first().json;\nconst cantidad = Number(sesion.imagenes_count || 0);\n\nconst texto = `✅ Imágenes recibidas: ${cantidad}.\n\n*Paso 3 de 3 — Videos opcionales*\n\nSi tienes videos de la ejecución, envíalos ahora.\n\nCuando termines de enviar los videos, escribe: LISTO\n\nSi no tienes videos, escribe: SIN VIDEO`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -176, -2480 ], "id": "3e901ae3-8c0c-428a-b11c-6b6130af29a3", "name": "Code - Preparar solicitud videos" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -368, -1216 ], "id": "17766352-4a1d-46c0-8cad-1c81e13b21ee", "name": "Sheets - Leer sesiones para video", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst now = new Date().toISOString();\n\nconst sesionActiva = rows\n .filter(row => clean(row.session_id))\n .find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n });\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n video_aceptado: false,\n motivo_video: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\n\nif (etapaActual !== 'ESPERANDO_VIDEO') {\n return [\n {\n json: {\n ...entrada,\n video_aceptado: false,\n motivo_video: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst videosCountActual = Number(sesionActiva.videos_count || 0);\n\nreturn [\n {\n json: {\n ...entrada,\n\n video_aceptado: true,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n etapa_recibida: 'ESPERANDO_VIDEO',\n etapa: 'ESPERANDO_VIDEO',\n\n audio_count: Number(sesionActiva.audio_count || 0),\n imagenes_count: Number(sesionActiva.imagenes_count || 0),\n videos_count: videosCountActual + 1,\n\n ultima_actividad: now,\n\n estado_evento: 'PENDIENTE',\n estado_sesion: 'ACTIVA',\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -160, -1216 ], "id": "cfca2f37-e675-424f-840b-bbdee7f8b6d1", "name": "Code - Resolver sesión para video" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "cae99e4c-7474-4480-a5b3-ab8f1092acfd", "leftValue": "={{ String($json.video_aceptado || '').trim().toLowerCase() === 'true' }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 48, -1216 ], "id": "66c45324-3088-4c91-834d-388a890e0d9e", "name": "IF - Video válido para sesión" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_video === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.etapa === 'ESPERANDO_AUDIO') {\n texto = `⚠️ Antes de enviar videos, debes completar la nota de voz obligatoria del reporte.`;\n} else if ($json.etapa === 'ESPERANDO_IMAGENES') {\n texto = `⚠️ Antes de enviar videos, debes enviar al menos una imagen y luego escribir: FOTOS LISTAS`;\n} else {\n texto = `⚠️ Recibí tu video, pero actualmente no corresponde al paso activo de tu reporte.\n\nCompleta el paso pendiente antes de continuar.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 256, -1120 ], "id": "d2dac207-1498-4d3c-8ca3-560f460c1975", "name": "Code - Preparar rechazo video" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 306129743, "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=306129743" }, "columns": { "mappingMode": "defineBelow", "value": { "event_id": "={{ $json.event_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "manager_telefono": "={{ $json.manager_telefono }}", "manager_nombre": "={{ $json.manager_nombre }}", "message_type": "={{ $json.message_type }}", "texto": "VIDEO RECIBIDO ", "comando": "={{ $json.accion_flujo }}", "tiene_media": "={{ $json.tiene_media }}", "media_count": "={{ $json.media_count }}", "raw_preview": "={{ $json.raw_preview }}", "estado": "={{ $json.estado_evento }}", "session_id": "={{ $json.session_id }}", "canal_origen": "={{ $json.canal_origen }}", "etapa_recibida": "={{ $json.etapa_recibida }}", "media_source_id": "={{ $json.media_source_id }}", "media_mime_type": "={{ $json.media_mime_type }}", "media_file_name": "={{ $json.media_file_name }}" }, "matchingColumns": [], "schema": [ { "id": "event_id", "displayName": "event_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "message_type", "displayName": "message_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "texto", "displayName": "texto", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comando", "displayName": "comando", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tiene_media", "displayName": "tiene_media", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_count", "displayName": "media_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "raw_preview", "displayName": "raw_preview", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_procesado", "displayName": "fecha_procesado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa_recibida", "displayName": "etapa_recibida", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_source_id", "displayName": "media_source_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_mime_type", "displayName": "media_mime_type", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_file_name", "displayName": "media_file_name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "media_drive_url", "displayName": "media_drive_url", "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": [ 256, -1312 ], "id": "d3366552-d51e-41ef-96d5-cef48e3afde1", "name": "Sheets - Guardar evento video", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Resolver sesión para video').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Resolver sesión para video').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Resolver sesión para video').first().json.etapa }}", "videos_count": "={{ $('Code - Resolver sesión para video').first().json.videos_count }}", "estado": "={{ $('Code - Resolver sesión para video').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 464, -1312 ], "id": "cc528852-d9b1-4b0a-b492-52bd0d4d07ba", "name": "Sheets - Actualizar sesión tras video", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $('Code - Resolver sesión para video').first().json;\nconst cantidad = Number(sesion.videos_count || 1);\n\nconst texto = `✅ Video recibido. Total de videos registrados: ${cantidad}.\n\nPuedes enviar más videos de esta ejecución.\n\nCuando termines, escribe: LISTO`;\n\nreturn {\n json: {\n ...sesion,\n whatsapp_to: sesion.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 672, -1312 ], "id": "53f352a4-906f-4018-80a8-cb687e1a44fb", "name": "Code - Preparar confirmación video recibido" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=616166581" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -4544, 768 ], "id": "16814259-a246-49bb-bc6d-10cb047dd843", "name": "Sheets - Leer sesiones para cierre", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const entrada = $('Code - Normalizar evento WhatsApp').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction onlyDigits(value) {\n return clean(value).replace(/\\D/g, '');\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst telefono = onlyDigits(entrada.manager_telefono);\nconst accion = norm(entrada.accion_flujo);\nconst now = new Date().toISOString();\n\nconst sesionActiva = rows\n .filter(row => clean(row.session_id))\n .find(row => {\n return (\n onlyDigits(row.manager_telefono) === telefono &&\n norm(row.estado) === 'ACTIVA'\n );\n });\n\nif (!sesionActiva) {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'SIN_SESION',\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nconst etapaActual = norm(sesionActiva.etapa);\nconst audioCount = Number(sesionActiva.audio_count || 0);\nconst imagenesCount = Number(sesionActiva.imagenes_count || 0);\nconst videosCount = Number(sesionActiva.videos_count || 0);\n\nif (etapaActual !== 'ESPERANDO_VIDEO') {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'ETAPA_INCORRECTA',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n audio_count: audioCount,\n imagenes_count: imagenesCount,\n videos_count: videosCount,\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nif (audioCount < 1) {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'SIN_AUDIO',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nif (imagenesCount < 1) {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'SIN_IMAGENES',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nif (accion === 'LISTO' && videosCount < 1) {\n return [\n {\n json: {\n ...entrada,\n cierre_valido: false,\n motivo_cierre: 'LISTO_SIN_VIDEO',\n session_id: clean(sesionActiva.session_id),\n etapa: clean(sesionActiva.etapa),\n imagenes_count: imagenesCount,\n whatsapp_to: telefono,\n },\n },\n ];\n}\n\nreturn [\n {\n json: {\n ...entrada,\n\n cierre_valido: true,\n cierre_tipo: accion,\n\n session_id: clean(sesionActiva.session_id),\n manager_telefono: telefono,\n manager_nombre: clean(sesionActiva.manager_nombre || entrada.manager_nombre),\n canal_origen: clean(sesionActiva.canal_origen || entrada.canal_origen || 'WHATSAPP'),\n\n audio_count: audioCount,\n imagenes_count: imagenesCount,\n videos_count: videosCount,\n\n etapa_anterior: clean(sesionActiva.etapa),\n nueva_etapa: 'PROCESANDO',\n estado_sesion: 'ACTIVA',\n ultima_actividad: now,\n\n whatsapp_to: telefono,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -4208, 784 ], "id": "876821e8-0b0d-45bd-bf03-f7978ef10e53", "name": "Code - Validar cierre reporte" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_cierre === 'SIN_SESION') {\n texto = `⚠️ No tienes un reporte activo de propuestas ejecutadas.\n\nPrimero selecciona la opción de Propuestas ejecutadas en el menú principal para iniciar un reporte.`;\n} else if ($json.motivo_cierre === 'SIN_AUDIO') {\n texto = `⚠️ No puedo procesar el reporte porque falta la nota de voz obligatoria.`;\n} else if ($json.motivo_cierre === 'SIN_IMAGENES') {\n texto = `⚠️ No puedo procesar el reporte porque falta al menos una imagen de la ejecución.`;\n} else if ($json.motivo_cierre === 'LISTO_SIN_VIDEO') {\n texto = `Aún no he recibido videos para este reporte.\n\nSi no tienes videos, escribe: SIN VIDEO\n\nSi tienes videos, envíalos y luego escribe: LISTO`;\n} else if ($json.etapa === 'ESPERANDO_AUDIO') {\n texto = `⚠️ Primero debes enviar la nota de voz obligatoria.`;\n} else if ($json.etapa === 'ESPERANDO_IMAGENES') {\n texto = `⚠️ Primero debes enviar al menos una imagen y luego escribir: FOTOS LISTAS`;\n} else {\n texto = `⚠️ Tu reporte no puede procesarse todavía porque falta completar el paso activo.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -544, 1872 ], "id": "05cc7eb1-0540-449a-84b1-ff590ba40030", "name": "Code - Preparar rechazo cierre" }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $('Code - Validar cierre reporte').first().json.session_id }}", "ultima_actividad": "={{ $('Code - Validar cierre reporte').first().json.ultima_actividad }}", "etapa": "={{ $('Code - Validar cierre reporte').first().json.nueva_etapa }}", "estado": "={{ $('Code - Validar cierre reporte').first().json.estado_sesion }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -3680, 592 ], "id": "4983bbb3-34d4-4d80-883f-68618fb30f68", "name": "Sheets - Actualizar sesión a procesando", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const cierre = $('Code - Validar cierre reporte').first().json;\n\nreturn {\n json: {\n session_id: cierre.session_id,\n manager_telefono: cierre.manager_telefono,\n manager_nombre: cierre.manager_nombre,\n canal_origen: cierre.canal_origen,\n\n audio_count: cierre.audio_count,\n imagenes_count: cierre.imagenes_count,\n videos_count: cierre.videos_count,\n\n cierre_tipo: cierre.cierre_tipo,\n estado_procesamiento: 'LISTO_PARA_ANALISIS_IA',\n\n whatsapp_to: cierre.manager_telefono,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1840, 416 ], "id": "28a29330-39b0-42aa-836b-d51cf02eed58", "name": "Code - Preparar procesamiento final guiado" }, { "parameters": { "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 306129743, "mode": "list", "cachedResultName": "wa_ejecuciones_eventos", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=306129743" }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -1648, 416 ], "id": "7eb9dcb2-ba23-4eee-b0e6-1827940af037", "name": "Sheets - Leer eventos de sesión final", "alwaysOutputData": true, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const cierre = $('Code - Preparar procesamiento final guiado').first().json;\nconst rows = $input.all().map(item => item.json || {});\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction norm(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase();\n}\n\nconst sessionId = clean(cierre.session_id);\n\nconst eventosSesion = rows.filter(row => {\n return clean(row.session_id) === sessionId;\n});\n\nconst eventosPendientes = eventosSesion.filter(row => {\n const estado = norm(row.estado);\n return estado === 'PENDIENTE' || estado === '';\n});\n\nconst audios = eventosPendientes.filter(row => {\n return norm(row.message_type) === 'AUDIO';\n});\n\nconst imagenes = eventosPendientes.filter(row => {\n return norm(row.message_type) === 'IMAGE';\n});\n\nconst videos = eventosPendientes.filter(row => {\n return norm(row.message_type) === 'VIDEO';\n});\n\nconst tieneAudio = audios.length >= 1;\nconst tieneImagenes = imagenes.length >= 1;\n\nlet motivoError = '';\n\nif (!tieneAudio) {\n motivoError = 'SIN_AUDIO_REGISTRADO';\n} else if (!tieneImagenes) {\n motivoError = 'SIN_IMAGENES_REGISTRADAS';\n}\n\nconst sesionCompleta = tieneAudio && tieneImagenes;\n\nreturn [\n {\n json: {\n ...cierre,\n\n session_id: sessionId,\n\n sesion_completa_para_analizar: sesionCompleta,\n motivo_error_sesion: motivoError,\n\n total_eventos_sesion: eventosSesion.length,\n total_eventos_pendientes: eventosPendientes.length,\n\n audio_count: audios.length,\n imagenes_count: imagenes.length,\n videos_count: videos.length,\n\n audio_event_id: audios.length ? clean(audios[0].event_id) : '',\n audio_event_ids: audios.map(row => clean(row.event_id)).filter(Boolean),\n imagen_event_ids: imagenes.map(row => clean(row.event_id)).filter(Boolean),\n video_event_ids: videos.map(row => clean(row.event_id)).filter(Boolean),\n\n eventos_audio: audios,\n eventos_imagenes: imagenes,\n eventos_video: videos,\n\n estado_procesamiento: sesionCompleta\n ? 'LISTO_PARA_RECUPERAR_MEDIA'\n : 'CAPTURA_INCOMPLETA',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -1440, 416 ], "id": "3c68c613-e09e-45fa-a463-f6744619acfa", "name": "Code - Consolidar sesión guiada para IA" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "11d390e1-dc34-4160-83f0-5449c93d89a3", "leftValue": "={{ String($json.sesion_completa_para_analizar || '').trim().toLowerCase() === 'true' }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -1232, 416 ], "id": "952f8fff-2af3-4676-94b4-e84426d3bb7a", "name": "IF - Sesión completa para analizar" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "let texto = '';\n\nif ($json.motivo_error_sesion === 'SIN_AUDIO_REGISTRADO') {\n texto = `⚠️ No pude procesar tu reporte porque no encontré la nota de voz obligatoria registrada.\n\nPor favor inicia nuevamente el reporte desde el menú principal.`;\n} else if ($json.motivo_error_sesion === 'SIN_IMAGENES_REGISTRADAS') {\n texto = `⚠️ No pude procesar tu reporte porque no encontré imágenes registradas de la ejecución.\n\nPor favor inicia nuevamente el reporte desde el menú principal.`;\n} else {\n texto = `⚠️ Ocurrió un inconveniente al consolidar las evidencias de tu reporte.\n\nPor favor inicia nuevamente el reporte desde el menú principal.`;\n}\n\nreturn {\n json: {\n ...$json,\n whatsapp_to: $json.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -336, 1552 ], "id": "06bad8cf-95ee-473f-b468-ff0a29fb52d6", "name": "Code - Preparar error sesión incompleta" }, { "parameters": { "method": "POST", "url": "https://wsp.gomezleemarketing.com/chat/getBase64FromMediaMessage/botsoporte", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "={{'C267126ABB45-4C12-B626-6BAB1833F5D7'}}" } ] }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{\n {\n message: {\n key: {\n id: $json.audio_event_id\n }\n },\n convertToMp4: false\n }\n}}", "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 1120, -1168 ], "id": "6e76e8d2-4384-4698-ae2f-0ff4e4680d69", "name": "HTTP Request - Obtener audio Evolution" }, { "parameters": { "updates": [ "message" ], "additionalFields": {} }, "type": "n8n-nodes-base.telegramTrigger", "typeVersion": 1.3, "position": [ -7936, -832 ], "id": "46831d28-473a-43b6-8900-0b536c79beae", "name": "Telegram Trigger", "webhookId": "f90d5f34-ecba-49c2-8b5a-1eb65881723a", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "chatId": "={{ $json.telegram_chat_id || $json.whatsapp_to }}", "text": "={{ $json.telegram_text || $json.whatsapp_text }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 16848, -1168 ], "id": "ad6d1294-1d38-44e0-9404-078b9682b183", "name": "Telegram - Enviar mensaje", "webhookId": "7c767a53-6a2c-4607-bf7d-77425d598c3f", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "77d8e579-5dbc-429d-9f5f-5ad6bba8d32e", "leftValue": "={{ String($json.cierre_valido || '').trim().toLowerCase() === 'true' }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -4000, 784 ], "id": "6cdf23ad-afbf-44c2-b55e-9597ad49b579", "name": "IF - Cierre válido" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const cierre = $('Code - Validar cierre reporte').first().json;\n\nconst texto = `✅ Recibí todas las evidencias de tu reporte.\n\nEstoy procesando la nota de voz, imágenes y videos para generar el registro de la propuesta ejecutada.\n\nTe enviaré un resumen cuando termine.`;\n\nreturn {\n json: {\n ...cierre,\n whatsapp_to: cierre.manager_telefono,\n whatsapp_text: texto,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -3200, 400 ], "id": "5d37f326-8061-45d4-a142-8a40d3c8a7a3", "name": "Code - Preparar aviso procesamiento" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "3a78e681-aa9d-4849-98fe-5ba92c72c0d3", "leftValue": "={{ ['LISTO', 'SIN VIDEO'].includes(String($json.texto || '').trim().toUpperCase()) }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -6016, -1632 ], "id": "f1632e97-2543-4f6e-af30-603dac207f3e", "name": "IF - Es comando de cierre" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const sesion = $json || {};\n\nconst audios = Array.isArray(sesion.eventos_audio)\n ? sesion.eventos_audio\n : [];\n\nconst audio = audios[0] || {};\n\nconst mediaSourceId = String(audio.media_source_id || '').trim();\nconst mediaMimeType = String(audio.media_mime_type || 'audio/ogg').trim();\nconst mediaFileName = String(\n audio.media_file_name || `audio_${sesion.session_id || Date.now()}.ogg`\n).trim();\n\nif (!mediaSourceId) {\n throw new Error(\n 'No se encontró media_source_id para descargar la nota de voz desde Telegram.'\n );\n}\n\nreturn {\n json: {\n ...sesion,\n\n media_tipo_descarga: 'audio',\n media_event_id: String(audio.event_id || '').trim(),\n media_source_id: mediaSourceId,\n media_mime_type: mediaMimeType,\n media_file_name: mediaFileName,\n\n audio_listo_para_descarga: true,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -608, -272 ], "id": "c8b10cb7-9bc6-4057-9123-1552a9d1e83e", "name": "Code - Preparar descarga audio Telegram" }, { "parameters": { "resource": "file", "fileId": "={{ $json.media_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ -400, -272 ], "id": "5f5c86cf-728a-476e-a8d8-07f7a3bcf663", "name": "Telegram - Descargar audio", "webhookId": "2f9a2a25-4673-493e-bd8a-4ef2db061f3a", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "jsCode": "const sesion = $input.first().json || {};\n\nconst imagenes =\n Array.isArray(sesion.eventos_imagen) ? sesion.eventos_imagen :\n Array.isArray(sesion.eventos_imagenes) ? sesion.eventos_imagenes :\n Array.isArray(sesion.eventos)\n ? sesion.eventos.filter(e =>\n String(e.message_type || '').trim().toLowerCase() === 'image'\n )\n : [];\n\nif (!imagenes.length) {\n throw new Error(\n 'La sesión indica imágenes obligatorias, pero no se encontraron eventos de imagen para descargar.'\n );\n}\n\nreturn imagenes.map((imagen, index) => {\n const mediaSourceId = String(imagen.media_source_id || '').trim();\n\n if (!mediaSourceId) {\n throw new Error(\n `La imagen ${index + 1} no tiene media_source_id para descargarla desde Telegram.`\n );\n }\n\n return {\n json: {\n session_id: sesion.session_id,\n manager_telefono: sesion.manager_telefono,\n manager_nombre: sesion.manager_nombre,\n canal_origen: sesion.canal_origen,\n\n media_tipo_descarga: 'image',\n media_index: index + 1,\n media_event_id: String(imagen.event_id || '').trim(),\n media_source_id: mediaSourceId,\n media_mime_type: String(imagen.media_mime_type || 'image/jpeg').trim(),\n media_file_name: String(\n imagen.media_file_name ||\n `imagen_${sesion.session_id || Date.now()}_${index + 1}.jpg`\n ).trim(),\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 176, 512 ], "id": "ff766af6-fe90-47e2-a7b6-06fb541d095e", "name": "Code - Preparar descargas imágenes Telegram" }, { "parameters": { "resource": "file", "fileId": "={{ $json.media_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 384, 512 ], "id": "5454de7c-b78b-4fcb-bda2-5b53da1888dd", "name": "Telegram - Descargar imágenes", "webhookId": "1260fa2e-00fa-49f4-a16b-f77ffcc84e31", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "bf6c63f0-7998-4de8-9d81-1c64ece51d8d", "leftValue": "={{ Number($json.videos_count || 0) > 0 }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 208, 848 ], "id": "c86099bc-770e-4f22-8c5a-213ade209dcb", "name": "IF - Tiene videos para descargar" }, { "parameters": { "jsCode": "const sesion = $input.first().json || {};\n\nconst videos =\n Array.isArray(sesion.eventos_video) ? sesion.eventos_video :\n Array.isArray(sesion.eventos_videos) ? sesion.eventos_videos :\n Array.isArray(sesion.eventos)\n ? sesion.eventos.filter(e =>\n String(e.message_type || '').trim().toLowerCase() === 'video'\n )\n : [];\n\nif (!videos.length) {\n throw new Error(\n 'La sesión indica que tiene videos, pero no se encontraron eventos de video para descargar.'\n );\n}\n\nreturn videos.map((video, index) => {\n const mediaSourceId = String(video.media_source_id || '').trim();\n\n if (!mediaSourceId) {\n throw new Error(\n `El video ${index + 1} no tiene media_source_id para descargarlo desde Telegram.`\n );\n }\n\n return {\n json: {\n session_id: sesion.session_id,\n manager_telefono: sesion.manager_telefono,\n manager_nombre: sesion.manager_nombre,\n canal_origen: sesion.canal_origen,\n\n media_tipo_descarga: 'video',\n media_index: index + 1,\n media_event_id: String(video.event_id || '').trim(),\n media_source_id: mediaSourceId,\n media_mime_type: String(video.media_mime_type || 'video/mp4').trim(),\n media_file_name: String(\n video.media_file_name ||\n `video_${sesion.session_id || Date.now()}_${index + 1}.mp4`\n ).trim(),\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 416, 752 ], "id": "d377f9d3-c1e1-4c29-bbc6-c45598c46d46", "name": "Code - Preparar descargas videos Telegram" }, { "parameters": { "resource": "file", "fileId": "={{ $json.media_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 624, 752 ], "id": "db9c58f0-95b4-4c14-9f0d-aba551224605", "name": "Telegram - Descargar videos", "webhookId": "6d3e1c5d-eda3-40e8-a61e-9ad2a771247c", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "resource": "audio", "operation": "analyze", "modelId": { "__rl": true, "value": "models/gemini-2.5-flash", "mode": "list", "cachedResultName": "models/gemini-2.5-flash" }, "text": "Analiza esta nota de voz enviada por un manager para reportar una propuesta ejecutada de GomezLee Marketing.\n\nTu tarea es transcribir el audio y extraer solamente la información que realmente se mencione. No inventes datos. Si un campo no se escucha o no fue informado, devuelve una cadena vacía.\n\nDevuelve únicamente un JSON válido, sin markdown, sin comillas triples y sin texto adicional, con esta estructura exacta:\n\n{\n\"transcripcion\": \"\",\n\"propuesta_referencia\": \"\",\n\"marca_cliente\": \"\",\n\"pais\": \"\",\n\"ubicacion_ejecucion\": \"\",\n\"fecha_ejecucion\": \"\",\n\"implementacion\": \"\",\n\"comentarios_resultados\": \"\",\n\"datos_faltantes\": []\n}\n\nReglas:\n\n* \"transcripcion\" debe contener la transcripción completa de lo escuchado.\n* \"propuesta_referencia\" debe contener el nombre o referencia de la propuesta ejecutada.\n* \"marca_cliente\" debe contener la marca o cliente mencionado.\n* \"pais\" debe contener el país mencionado.\n* \"ubicacion_ejecucion\" debe contener el lugar o establecimiento mencionado.\n* \"fecha_ejecucion\" debe conservar la fecha expresada en el audio.\n* \"implementacion\" debe resumir qué se implementó.\n* \"comentarios_resultados\" debe resumir observaciones o resultados mencionados.\n* \"datos_faltantes\" debe listar los nombres de los campos no informados o no identificables.\n", "inputType": "binary", "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ -160, -272 ], "id": "c1490197-9c44-4e05-8814-c698f9e4526a", "name": "Gemini - Analizar nota de voz", "credentials": { "googlePalmApi": { "id": "6Jdcd9FONxG35YuJ", "name": "Isaac - Gemini Api Gratis" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const respuestaGemini = $json || {};\n\n// La respuesta real del nodo Gemini llega dentro de content.parts[].text\nconst partes = Array.isArray(respuestaGemini.content?.parts)\n ? respuestaGemini.content.parts\n : [];\n\nconst textoRespuesta = partes\n .map((parte) => String(parte?.text || ''))\n .join('\\n')\n .trim();\n\nif (!textoRespuesta) {\n throw new Error(\n 'Gemini no devolvió texto dentro de content.parts para analizar la nota de voz.'\n );\n}\n\n// Limpiar posibles bloques markdown si Gemini los agrega en otra ejecución.\nconst limpio = textoRespuesta\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\nlet datosAudio;\n\ntry {\n datosAudio = JSON.parse(limpio);\n} catch (error) {\n throw new Error(\n 'La respuesta de Gemini no llegó como JSON válido. Respuesta recibida: ' + limpio\n );\n}\n\n// Recuperar la sesión completa consolidada anteriormente.\nconst sesion = $('Code - Consolidar sesión guiada para IA').first().json;\n\nconst texto = (valor) => String(valor || '').trim();\n\nreturn {\n json: {\n ...sesion,\n\n analisis_audio: datosAudio,\n\n transcripcion_audio: texto(datosAudio.transcripcion),\n propuesta_referencia: texto(datosAudio.propuesta_referencia),\n marca_cliente: texto(datosAudio.marca_cliente),\n pais: texto(datosAudio.pais),\n ubicacion_ejecucion: texto(datosAudio.ubicacion_ejecucion),\n fecha_ejecucion: texto(datosAudio.fecha_ejecucion),\n implementacion: texto(datosAudio.implementacion),\n comentarios_resultados: texto(datosAudio.comentarios_resultados),\n\n datos_faltantes_audio: Array.isArray(datosAudio.datos_faltantes)\n ? datosAudio.datos_faltantes.map((valor) => texto(valor)).filter(Boolean)\n : [],\n\n audio_analizado: true,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 48, -272 ], "id": "345796e5-7ecf-4910-95d9-ad454d16da61", "name": "Code - Limpiar resultado audio Gemini" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst texto = (valor) => String(valor || '').trim();\n\nconst limpiarSegmento = (valor, fallback) => {\n const limpio = texto(valor)\n .replace(/[\\/\\\\:*?\"<>|#%{}\\[\\]]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n return limpio || fallback;\n};\n\nconst convertirFechaEjecucion = () => {\n // Preparado por si luego Gemini devuelve una fecha ISO directamente.\n const fechaISO = texto(datos.fecha_ejecucion_iso);\n\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(fechaISO)) {\n return fechaISO;\n }\n\n const original = texto(datos.fecha_ejecucion);\n\n const normalizada = original\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n\n const meses = {\n enero: '01',\n febrero: '02',\n marzo: '03',\n abril: '04',\n mayo: '05',\n junio: '06',\n julio: '07',\n agosto: '08',\n septiembre: '09',\n setiembre: '09',\n octubre: '10',\n noviembre: '11',\n diciembre: '12',\n };\n\n const coincidencia = normalizada.match(\n /(primero|uno|\\d{1,2})\\s+de\\s+(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|setiembre|octubre|noviembre|diciembre)\\s+de\\s+(\\d{4})/\n );\n\n if (!coincidencia) {\n return 'SIN-FECHA';\n }\n\n const diaTexto = coincidencia[1];\n const dia =\n diaTexto === 'primero' || diaTexto === 'uno'\n ? '01'\n : String(Number(diaTexto)).padStart(2, '0');\n\n const mes = meses[coincidencia[2]];\n const anio = coincidencia[3];\n\n return `${anio}-${mes}-${dia}`;\n};\n\nconst fechaCarpeta = convertirFechaEjecucion();\n\nconst marca = limpiarSegmento(datos.marca_cliente, 'SIN-MARCA');\n\nconst propuesta = limpiarSegmento(\n datos.propuesta_referencia,\n 'SIN-REFERENCIA'\n).slice(0, 75);\n\nconst sessionId = texto(datos.session_id);\nconst sessionShort = sessionId.split('_').pop() || 'SIN-ID';\n\nconst carpetaNombre = [\n 'TEST - PE',\n fechaCarpeta,\n marca,\n propuesta,\n sessionShort,\n].join(' - ');\n\nreturn {\n json: {\n ...datos,\n fecha_carpeta: fechaCarpeta,\n carpeta_nombre_drive: carpetaNombre,\n carpeta_estado: 'PENDIENTE_DE_CREACION',\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 256, -272 ], "id": "dff11e59-38e5-48c3-96e8-eba4910fc912", "name": "Code - Preparar carpeta ejecución Drive" }, { "parameters": { "resource": "folder", "name": "={{ $json.carpeta_nombre_drive }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "1q92b3lMw_fcjD1YUtp5YcJKne49zOfpc", "mode": "list", "cachedResultName": "TEST PROPUESTAS EJECUTADAS", "cachedResultUrl": "https://drive.google.com/drive/folders/1q92b3lMw_fcjD1YUtp5YcJKne49zOfpc" }, "options": { "simplifyOutput": true } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 464, -272 ], "id": "3d6d25d5-edb3-4226-b0bc-4efc8a825696", "name": "Drive - Crear Carpeta Ejecución", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const carpetaCreada = $json || {};\n\n// Importante: usa exactamente el nombre del nodo que ya tienes funcionando.\nconst datosReporte = $('Code - Preparar carpeta ejecución Drive').first().json;\n\nconst carpetaId = String(carpetaCreada.id || '').trim();\nconst carpetaNombre = String(carpetaCreada.name || '').trim();\n\nif (!carpetaId) {\n throw new Error('Drive no devolvió el ID de la carpeta de ejecución creada.');\n}\n\nreturn {\n json: {\n ...datosReporte,\n\n carpeta_drive_id: carpetaId,\n carpeta_drive_nombre: carpetaNombre,\n carpeta_drive_url: `https://drive.google.com/drive/folders/${carpetaId}`,\n carpeta_estado: 'CREADA',\n\n presentacion_drive_id: '',\n presentacion_drive_url: '',\n audio_drive_url: '',\n imagenes_drive_urls: [],\n videos_drive_urls: [],\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 672, -272 ], "id": "1bdb3db3-1d41-4d16-a7d5-96760fa452bf", "name": "Code - Guardar Datos Carpeta Ejecución TEST" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst audios = Array.isArray(datos.eventos_audio)\n ? datos.eventos_audio\n : [];\n\nconst audio = audios[0] || {};\n\nconst audioSourceId = String(audio.media_source_id || '').trim();\nconst carpetaId = String(datos.carpeta_drive_id || '').trim();\n\nif (!carpetaId) {\n throw new Error('No se encontró carpeta_drive_id para subir el audio.');\n}\n\nif (!audioSourceId) {\n throw new Error('No se encontró media_source_id del audio para descargarlo desde Telegram.');\n}\n\nconst sessionShort = String(datos.session_id || '').split('_').pop() || 'SIN-ID';\n\nreturn {\n json: {\n ...datos,\n\n archivo_tipo: 'audio',\n archivo_event_id: String(audio.event_id || '').trim(),\n archivo_source_id: audioSourceId,\n archivo_nombre_drive: `01_Audio_Nota_de_Voz_${sessionShort}.oga`,\n archivo_mime_type: String(audio.media_mime_type || 'audio/ogg').trim(),\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 880, -272 ], "id": "429a0e1a-b3a2-4153-894a-84b8672eb7db", "name": "Code - Preparar archivo audio para Drive TEST" }, { "parameters": { "resource": "file", "fileId": "={{ $json.archivo_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 1088, -272 ], "id": "8505a6d1-52cc-48b1-abee-f3462b8e459d", "name": "Telegram - Descargar audio para Drive TEST", "webhookId": "4bbf4b44-e36e-4130-989c-6e1cbb43a2c8", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "name": "={{ $('Code - Preparar archivo audio para Drive TEST').first().json.archivo_nombre_drive }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $('Code - Guardar Datos Carpeta Ejecución TEST').first().json.carpeta_drive_id }}", "mode": "id" }, "options": { "simplifyOutput": true } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 1296, -272 ], "id": "9959df5c-3096-4690-a35e-d6c18e1a3ac1", "name": "Drive - Subir audio ejecución TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const audioSubido = $json || {};\nconst datosReporte = $('Code - Guardar Datos Carpeta Ejecución TEST').first().json;\n\nconst audioId = String(audioSubido.id || '').trim();\nconst audioNombre = String(audioSubido.name || '').trim();\nconst audioMimeType = String(audioSubido.mimeType || 'audio/ogg').trim();\n\nif (!audioId) {\n throw new Error('Drive no devolvió el ID del audio subido.');\n}\n\nreturn {\n json: {\n ...datosReporte,\n\n audio_drive_id: audioId,\n audio_drive_nombre: audioNombre,\n audio_drive_mime_type: audioMimeType,\n audio_drive_url: `https://drive.google.com/file/d/${audioId}/view`,\n\n audio_estado_drive: 'SUBIDO',\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1504, -272 ], "id": "5c2c368e-5cfa-47fa-a558-521cb5fa71f9", "name": "Code - Guardar enlace audio Drive TEST" }, { "parameters": { "jsCode": "const datos = $input.first().json || {};\n\nconst imagenes =\n Array.isArray(datos.eventos_imagen) ? datos.eventos_imagen :\n Array.isArray(datos.eventos_imagenes) ? datos.eventos_imagenes :\n Array.isArray(datos.eventos)\n ? datos.eventos.filter((evento) =>\n String(evento.message_type || '').trim().toLowerCase() === 'image'\n )\n : [];\n\nif (!imagenes.length) {\n throw new Error('No se encontraron imágenes de la ejecución para subir a Drive.');\n}\n\nconst carpetaId = String(datos.carpeta_drive_id || '').trim();\n\nif (!carpetaId) {\n throw new Error('No se encontró carpeta_drive_id para subir las imágenes.');\n}\n\nconst sessionShort = String(datos.session_id || '').split('_').pop() || 'SIN-ID';\n\nreturn imagenes.map((imagen, index) => {\n const sourceId = String(imagen.media_source_id || '').trim();\n\n if (!sourceId) {\n throw new Error(`La imagen ${index + 1} no tiene media_source_id.`);\n }\n\n const extension =\n String(imagen.media_mime_type || '').toLowerCase().includes('png')\n ? 'png'\n : 'jpg';\n\n return {\n json: {\n ...datos,\n\n archivo_tipo: 'imagen',\n archivo_indice: index + 1,\n archivo_event_id: String(imagen.event_id || '').trim(),\n archivo_source_id: sourceId,\n archivo_mime_type: String(imagen.media_mime_type || 'image/jpeg').trim(),\n archivo_nombre_drive: `02_Imagen_${String(index + 1).padStart(2, '0')}_${sessionShort}.${extension}`,\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1712, -272 ], "id": "71b5c186-d1ed-4861-8882-6d7c8dfbb5c8", "name": "Code - Preparar archivos imágenes para Drive TEST" }, { "parameters": { "resource": "file", "fileId": "={{ $json.archivo_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 1920, -272 ], "id": "991476da-845c-425d-aa0f-6e84bbffbe76", "name": "Telegram - Descargar imágenes para Drive TEST", "webhookId": "8076a6ce-3649-4e1f-b56d-3af96d9294a8", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "name": "={{ $('Code - Preparar archivos imágenes para Drive TEST').item.json.archivo_nombre_drive }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $('Code - Guardar enlace audio Drive TEST').first().json.carpeta_drive_id }}", "mode": "id" }, "options": { "simplifyOutput": true } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 2128, -272 ], "id": "2fe2033b-072e-4dea-bb86-05c600165913", "name": "Drive - Subir imágenes ejecución TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "jsCode": "const datosReporte = $('Code - Guardar enlace audio Drive TEST').first().json;\n\nconst imagenesSubidas = $input.all().map((item, index) => {\n const imagen = item.json || {};\n\n const id = String(imagen.id || '').trim();\n const nombre = String(imagen.name || '').trim();\n const mimeType = String(imagen.mimeType || 'image/jpeg').trim();\n\n if (!id) {\n throw new Error(`Drive no devolvió el ID de la imagen subida número ${index + 1}.`);\n }\n\n return {\n indice: index + 1,\n id,\n nombre,\n mime_type: mimeType,\n url: `https://drive.google.com/file/d/${id}/view`,\n };\n});\n\nif (!imagenesSubidas.length) {\n throw new Error('No se recibieron imágenes subidas desde Drive.');\n}\n\nreturn [\n {\n json: {\n ...datosReporte,\n\n imagenes_drive_ids: imagenesSubidas.map((imagen) => imagen.id),\n imagenes_drive_urls: imagenesSubidas.map((imagen) => imagen.url),\n imagenes_drive_detalle: imagenesSubidas,\n\n imagenes_estado_drive: 'SUBIDAS',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2336, -272 ], "id": "f6bb8823-74a2-4c95-a1b3-4cfe5ac4e932", "name": "Code - Guardar enlaces imágenes Drive TEST" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "4b3671bb-c95b-42bb-880e-8a7b9a9beb04", "leftValue": "={{ Number($json.videos_count || 0) > 0 }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 2544, -272 ], "id": "29829153-fe0b-44a4-91b9-0d77b8a1b8f2", "name": "IF - Tiene videos para subir Drive TEST" }, { "parameters": { "jsCode": "const datos = $input.first().json || {};\n\nconst videos =\n Array.isArray(datos.eventos_video) ? datos.eventos_video :\n Array.isArray(datos.eventos_videos) ? datos.eventos_videos :\n Array.isArray(datos.eventos)\n ? datos.eventos.filter((evento) =>\n String(evento.message_type || '').trim().toLowerCase() === 'video'\n )\n : [];\n\nif (!videos.length) {\n throw new Error('La sesión indica videos registrados, pero no se encontraron sus eventos.');\n}\n\nconst carpetaId = String(datos.carpeta_drive_id || '').trim();\n\nif (!carpetaId) {\n throw new Error('No se encontró carpeta_drive_id para subir los videos.');\n}\n\nconst sessionShort = String(datos.session_id || '').split('_').pop() || 'SIN-ID';\n\nreturn videos.map((video, index) => {\n const sourceId = String(video.media_source_id || '').trim();\n\n if (!sourceId) {\n throw new Error(`El video ${index + 1} no tiene media_source_id.`);\n }\n\n return {\n json: {\n ...datos,\n\n archivo_tipo: 'video',\n archivo_indice: index + 1,\n archivo_event_id: String(video.event_id || '').trim(),\n archivo_source_id: sourceId,\n archivo_mime_type: String(video.media_mime_type || 'video/mp4').trim(),\n archivo_nombre_drive: `03_Video_${String(index + 1).padStart(2, '0')}_${sessionShort}.mp4`,\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2864, -336 ], "id": "5be8ee1a-324f-43d9-be08-b691dfd31ba5", "name": "Code - Preparar archivos videos para Drive TEST" }, { "parameters": { "resource": "file", "fileId": "={{ $json.archivo_source_id }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 3104, -336 ], "id": "dc1d44ca-ade9-4ba7-a5be-0a6a6a380542", "name": "Telegram - Descargar videos para Drive TEST", "webhookId": "fccffec5-25ed-434b-933d-5861836ca06c", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "name": "={{ $('Code - Preparar archivos videos para Drive TEST').item.json.archivo_nombre_drive }}", "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $('Code - Guardar enlaces imágenes Drive TEST').first().json.carpeta_drive_id }}", "mode": "id" }, "options": { "simplifyOutput": true } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 3312, -336 ], "id": "966906a3-fd55-4f32-a4b5-8a7a79221f7f", "name": "Drive - Subir videos ejecución TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "jsCode": "const datosReporte = $('Code - Guardar enlaces imágenes Drive TEST').first().json;\n\nconst videosSubidos = $input.all().map((item, index) => {\n const video = item.json || {};\n\n const id = String(video.id || '').trim();\n const nombre = String(video.name || '').trim();\n const mimeType = String(video.mimeType || 'video/mp4').trim();\n\n if (!id) {\n throw new Error(`Drive no devolvió el ID del video subido número ${index + 1}.`);\n }\n\n return {\n indice: index + 1,\n id,\n nombre,\n mime_type: mimeType,\n url: `https://drive.google.com/file/d/${id}/view`,\n };\n});\n\nif (!videosSubidos.length) {\n throw new Error('No se recibieron videos subidos desde Drive.');\n}\n\nreturn [\n {\n json: {\n ...datosReporte,\n\n videos_drive_ids: videosSubidos.map((video) => video.id),\n videos_drive_urls: videosSubidos.map((video) => video.url),\n videos_drive_detalle: videosSubidos,\n\n videos_estado_drive: 'SUBIDOS',\n evidencias_drive_completas: true,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3520, -336 ], "id": "3f4ee6df-64d4-4f82-9194-7e71ca1b4505", "name": "Code - Guardar enlaces videos Drive TEST" }, { "parameters": { "jsCode": "const datos = $input.first().json || {};\n\nconst imagenes = Array.isArray(datos.imagenes_drive_detalle)\n ? datos.imagenes_drive_detalle\n : [];\n\nif (!imagenes.length) {\n throw new Error('No se encontraron imágenes subidas a Drive para analizar con Gemini.');\n}\n\nreturn imagenes.map((imagen, index) => {\n const imagenId = String(imagen.id || '').trim();\n\n if (!imagenId) {\n throw new Error(`La imagen ${index + 1} no tiene ID de Drive.`);\n }\n\n return {\n json: {\n ...datos,\n\n evidencia_analisis_tipo: 'imagen',\n evidencia_analisis_indice: index + 1,\n\n imagen_drive_id: imagenId,\n imagen_drive_nombre: String(imagen.nombre || '').trim(),\n imagen_drive_url: String(imagen.url || '').trim(),\n imagen_drive_mime_type: String(imagen.mime_type || 'image/jpeg').trim(),\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3760, -336 ], "id": "8048a1c9-a559-46a0-ac7d-906b1e8bc636", "name": "Code - Preparar imágenes para análisis Gemini TEST" }, { "parameters": { "operation": "download", "fileId": { "__rl": true, "value": "={{ $json.imagen_drive_id }}", "mode": "id" }, "options": { "fileName": "={{ $json.imagen_drive_nombre }}" } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 3968, -336 ], "id": "ec8ee599-5ac6-41b9-9891-4057ae6451f6", "name": "Drive - Descargar imágenes para Gemini TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "resource": "image", "operation": "analyze", "modelId": { "__rl": true, "value": "models/gemini-2.5-flash-lite", "mode": "list", "cachedResultName": "models/gemini-2.5-flash-lite" }, "text": "Analiza esta imagen como evidencia visual de una propuesta ejecutada de GomezLee Marketing.\n\nExtrae únicamente lo que pueda observarse en la imagen. No inventes marca, cliente, ubicación, fecha ni resultados si no son visibles.\n\nDevuelve únicamente un JSON válido, sin markdown y sin texto adicional, con esta estructura exacta:\n\n{\n \"descripcion_visual\": \"\",\n \"elementos_ejecutados_visibles\": [],\n \"marcas_o_logos_visibles\": [],\n \"textos_visibles_relevantes\": [],\n \"personas_o_interaccion_visible\": \"\",\n \"calidad_evidencia\": \"\",\n \"es_evidencia_util\": true,\n \"alertas\": []\n}\n\nReglas:\n- \"descripcion_visual\" debe describir brevemente lo observado.\n- \"elementos_ejecutados_visibles\" debe listar elementos como stand, exhibidor, material POP, sampling, promotores, pantallas o mobiliario, solo si son visibles.\n- \"marcas_o_logos_visibles\" debe listar únicamente marcas identificables visualmente.\n- \"textos_visibles_relevantes\" debe incluir textos legibles importantes.\n- \"personas_o_interaccion_visible\" debe indicar si se observa público, promotores o interacción.\n- \"calidad_evidencia\" debe indicar si la imagen es clara, parcial, borrosa o insuficiente.\n- \"es_evidencia_util\" debe ser true si la imagen ayuda a documentar la ejecución; de lo contrario false.\n- \"alertas\" debe listar problemas como imagen irrelevante, captura de pantalla, baja calidad o información contradictoria.\n\nReglas adicionales obligatorias:\n- Una captura de pantalla de un chat, aplicación, correo, documento, configuración técnica o interfaz digital NO constituye evidencia visual de una propuesta ejecutada.\n- Si la imagen no muestra físicamente la ejecución, instalación, activación, mobiliario, material promocional, producto exhibido o interacción en el lugar, \"es_evidencia_util\" debe ser false.\n- Si la imagen es una captura de pantalla o no muestra la ejecución física, agrega en \"alertas\": \"No muestra ejecución física de la propuesta\".", "inputType": "binary", "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ 4176, -336 ], "id": "0f02c239-e05d-499f-83a7-00acc6230593", "name": "Gemini - Analizar imágenes ejecución TEST", "credentials": { "googlePalmApi": { "id": "6Jdcd9FONxG35YuJ", "name": "Isaac - Gemini Api Gratis" } } }, { "parameters": { "jsCode": "const resultadosGemini = $input.all();\n\n// Datos completos del reporte, incluyendo enlaces de Drive.\nconst datosReporte = $('Code - Guardar enlaces videos Drive TEST').first().json;\n\n// Metadatos originales de cada imagen antes de pasar por Gemini.\nconst imagenesPreparadas = $('Code - Preparar imágenes para análisis Gemini TEST')\n .all()\n .map((item) => item.json || {});\n\nconst texto = (valor) => String(valor || '').trim();\n\nconst limpiarJsonGemini = (respuesta, index) => {\n const partes = Array.isArray(respuesta.content?.parts)\n ? respuesta.content.parts\n : [];\n\n const contenido = partes\n .map((parte) => texto(parte?.text))\n .join('\\n')\n .trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\n if (!contenido) {\n throw new Error(`Gemini no devolvió análisis para la imagen ${index + 1}.`);\n }\n\n try {\n return JSON.parse(contenido);\n } catch (error) {\n throw new Error(\n `El análisis de la imagen ${index + 1} no llegó como JSON válido. Respuesta: ${contenido}`\n );\n }\n};\n\nconst analisisImagenes = resultadosGemini.map((item, index) => {\n const analisis = limpiarJsonGemini(item.json || {}, index);\n const origen = imagenesPreparadas[index] || {};\n\n return {\n indice: index + 1,\n drive_id: texto(origen.imagen_drive_id),\n drive_nombre: texto(origen.imagen_drive_nombre),\n drive_url: texto(origen.imagen_drive_url),\n descripcion_visual: texto(analisis.descripcion_visual),\n elementos_ejecutados_visibles: Array.isArray(analisis.elementos_ejecutados_visibles)\n ? analisis.elementos_ejecutados_visibles\n : [],\n marcas_o_logos_visibles: Array.isArray(analisis.marcas_o_logos_visibles)\n ? analisis.marcas_o_logos_visibles\n : [],\n textos_visibles_relevantes: Array.isArray(analisis.textos_visibles_relevantes)\n ? analisis.textos_visibles_relevantes\n : [],\n personas_o_interaccion_visible: texto(analisis.personas_o_interaccion_visible),\n calidad_evidencia: texto(analisis.calidad_evidencia),\n es_evidencia_util: analisis.es_evidencia_util === true,\n alertas: Array.isArray(analisis.alertas) ? analisis.alertas : [],\n };\n});\n\nconst imagenesUtiles = analisisImagenes.filter(\n (imagen) => imagen.es_evidencia_util === true\n);\n\nconst imagenesNoUtiles = analisisImagenes.filter(\n (imagen) => imagen.es_evidencia_util !== true\n);\n\nreturn [\n {\n json: {\n ...datosReporte,\n\n analisis_imagenes: analisisImagenes,\n imagenes_analizadas_count: analisisImagenes.length,\n imagenes_utiles_count: imagenesUtiles.length,\n imagenes_no_utiles_count: imagenesNoUtiles.length,\n imagenes_tienen_alertas: analisisImagenes.some(\n (imagen) => imagen.alertas.length > 0\n ),\n analisis_imagenes_estado: 'COMPLETADO',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 4384, -336 ], "id": "327e1e9e-1d6c-42b6-b9ef-a5a416dd7728", "name": "Code - Limpiar análisis imágenes Gemini TEST" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "de236f77-f3e9-4ffb-9a0f-7aab24d7b0ca", "leftValue": "={{ (($json.videos_drive_ids || []).length > 0) }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 4592, -336 ], "id": "f861492b-d613-4ea5-ab40-0dfbf43a586f", "name": "IF - Tiene videos para análisis Gemini TEST" }, { "parameters": { "jsCode": "const datos = $input.first().json || {};\n\nconst videos = Array.isArray(datos.videos_drive_detalle)\n ? datos.videos_drive_detalle\n : [];\n\nif (!videos.length) {\n throw new Error('No se encontraron videos subidos a Drive para analizar con Gemini.');\n}\n\nreturn videos.map((video, index) => {\n const videoId = String(video.id || '').trim();\n\n if (!videoId) {\n throw new Error(`El video ${index + 1} no tiene ID de Drive.`);\n }\n\n return {\n json: {\n ...datos,\n\n evidencia_analisis_tipo: 'video',\n evidencia_analisis_indice: index + 1,\n\n video_drive_id: videoId,\n video_drive_nombre: String(video.nombre || '').trim(),\n video_drive_url: String(video.url || '').trim(),\n video_drive_mime_type: String(video.mime_type || 'video/mp4').trim(),\n },\n };\n});" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 4896, -432 ], "id": "e503dc14-ee51-4a67-8971-5d699535897d", "name": "Code - Preparar videos para análisis Gemini TEST" }, { "parameters": { "operation": "download", "fileId": { "__rl": true, "value": "={{ $json.video_drive_id }}", "mode": "id" }, "options": { "fileName": "={{ $json.video_drive_nombre }}" } }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 5104, -432 ], "id": "2486867b-2c86-4001-ab53-038d86a488ae", "name": "Drive - Descargar videos para Gemini TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "resource": "video", "operation": "analyze", "modelId": { "__rl": true, "value": "models/gemini-2.5-flash-lite", "mode": "list", "cachedResultName": "models/gemini-2.5-flash-lite" }, "text": "Analiza este video como evidencia audiovisual de una propuesta ejecutada de GomezLee Marketing.\n\nExtrae únicamente lo que realmente pueda observarse en el video. No inventes marca, cliente, ubicación, fecha, resultados ni elementos de ejecución si no son visibles.\n\nDevuelve únicamente un JSON válido, sin markdown y sin texto adicional, con esta estructura exacta:\n\n{\n \"descripcion_video\": \"\",\n \"elementos_ejecutados_visibles\": [],\n \"marcas_o_logos_visibles\": [],\n \"textos_visibles_relevantes\": [],\n \"interaccion_o_actividad_visible\": \"\",\n \"calidad_evidencia\": \"\",\n \"es_evidencia_util\": true,\n \"alertas\": []\n}\n\nReglas:\n- \"descripcion_video\" debe resumir brevemente lo observado en el video.\n- \"elementos_ejecutados_visibles\" debe listar elementos físicos de ejecución como stand, exhibidor, material POP, sampling, promotores, pantallas, mobiliario o producto exhibido, solo si son visibles.\n- \"marcas_o_logos_visibles\" debe listar únicamente marcas identificables visualmente.\n- \"textos_visibles_relevantes\" debe incluir textos legibles importantes.\n- \"interaccion_o_actividad_visible\" debe indicar si se observa público, promotores, montaje, activación o interacción real.\n- \"calidad_evidencia\" debe indicar si el video es claro, parcial, borroso, muy corto o insuficiente.\n- \"es_evidencia_util\" debe ser true solo si el video ayuda a documentar físicamente la ejecución realizada.\n- Una pantalla de prueba, barras de color, captura de pantalla, grabación de una interfaz digital, archivo irrelevante o video que no muestre ejecución física NO constituye evidencia útil.\n- Si el video no muestra la ejecución física, \"es_evidencia_util\" debe ser false.\n- Si el video no es útil, agrega en \"alertas\": \"No muestra ejecución física de la propuesta\".", "inputType": "binary", "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ 5312, -432 ], "id": "42886183-7341-4e4c-bc86-4a1da3e018fa", "name": "Gemini - Analizar videos ejecución TEST", "credentials": { "googlePalmApi": { "id": "6Jdcd9FONxG35YuJ", "name": "Isaac - Gemini Api Gratis" } } }, { "parameters": { "jsCode": "const resultadosGemini = $input.all();\n\n// Recupera los datos que ya incluyen audio, enlaces Drive y análisis de imágenes.\nconst datosReporte = $('Code - Limpiar análisis imágenes Gemini TEST').first().json;\n\n// Recupera los metadatos de los videos preparados antes de enviarlos a Gemini.\nconst videosPreparados = $('Code - Preparar videos para análisis Gemini TEST')\n .all()\n .map((item) => item.json || {});\n\nconst texto = (valor) => String(valor || '').trim();\n\nconst limpiarJsonGemini = (respuesta, index) => {\n const partes = Array.isArray(respuesta.content?.parts)\n ? respuesta.content.parts\n : [];\n\n const contenido = partes\n .map((parte) => texto(parte?.text))\n .join('\\n')\n .trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\n if (!contenido) {\n throw new Error(`Gemini no devolvió análisis para el video ${index + 1}.`);\n }\n\n try {\n return JSON.parse(contenido);\n } catch (error) {\n throw new Error(\n `El análisis del video ${index + 1} no llegó como JSON válido. Respuesta: ${contenido}`\n );\n }\n};\n\nconst analisisVideos = resultadosGemini.map((item, index) => {\n const analisis = limpiarJsonGemini(item.json || {}, index);\n const origen = videosPreparados[index] || {};\n\n return {\n indice: index + 1,\n drive_id: texto(origen.video_drive_id),\n drive_nombre: texto(origen.video_drive_nombre),\n drive_url: texto(origen.video_drive_url),\n\n descripcion_video: texto(analisis.descripcion_video),\n\n elementos_ejecutados_visibles: Array.isArray(analisis.elementos_ejecutados_visibles)\n ? analisis.elementos_ejecutados_visibles\n : [],\n\n marcas_o_logos_visibles: Array.isArray(analisis.marcas_o_logos_visibles)\n ? analisis.marcas_o_logos_visibles\n : [],\n\n textos_visibles_relevantes: Array.isArray(analisis.textos_visibles_relevantes)\n ? analisis.textos_visibles_relevantes\n : [],\n\n interaccion_o_actividad_visible: texto(analisis.interaccion_o_actividad_visible),\n calidad_evidencia: texto(analisis.calidad_evidencia),\n es_evidencia_util: analisis.es_evidencia_util === true,\n alertas: Array.isArray(analisis.alertas) ? analisis.alertas : [],\n };\n});\n\nconst videosUtiles = analisisVideos.filter(\n (video) => video.es_evidencia_util === true\n);\n\nconst videosNoUtiles = analisisVideos.filter(\n (video) => video.es_evidencia_util !== true\n);\n\nreturn [\n {\n json: {\n ...datosReporte,\n\n analisis_videos: analisisVideos,\n videos_analizados_count: analisisVideos.length,\n videos_utiles_count: videosUtiles.length,\n videos_no_utiles_count: videosNoUtiles.length,\n videos_tienen_alertas: analisisVideos.some(\n (video) => video.alertas.length > 0\n ),\n analisis_videos_estado: 'COMPLETADO',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 5520, -432 ], "id": "ac45a0ee-9ea9-4852-95a9-17b420636f9d", "name": "Code - Limpiar análisis videos Gemini TEST" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst texto = (valor) => String(valor ?? '').trim();\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\nconst analisisImagenes = lista(datos.analisis_imagenes);\nconst analisisVideos = lista(datos.analisis_videos);\n\nconst imagenesUtiles = analisisImagenes.filter(\n (imagen) => imagen.es_evidencia_util === true\n);\n\nconst videosUtiles = analisisVideos.filter(\n (video) => video.es_evidencia_util === true\n);\n\nconst datosFaltantesAudio = lista(datos.datos_faltantes_audio)\n .map((valor) => texto(valor))\n .filter(Boolean);\n\nconst audioValido =\n datos.audio_analizado === true &&\n Number(datos.audio_count || 0) >= 1 &&\n datosFaltantesAudio.length === 0;\n\n// Regla de negocio:\n// La imagen es obligatoria y debe mostrar evidencia física útil.\n// El video es opcional: si existe pero no es útil, genera alerta, pero no invalida\n// una ejecución que sí tenga imagen útil.\nconst imagenObligatoriaValida = imagenesUtiles.length >= 1;\n\nconst registroAprobadoProduccion =\n audioValido && imagenObligatoriaValida;\n\nconst alertasEvidencias = [\n ...analisisImagenes.flatMap((imagen) => lista(imagen.alertas)),\n ...analisisVideos.flatMap((video) => lista(video.alertas)),\n]\n .map((alerta) => texto(alerta))\n .filter(Boolean)\n .filter((alerta, index, arreglo) => arreglo.indexOf(alerta) === index);\n\nconst estadoRegistroTest = registroAprobadoProduccion\n ? 'TEST_APROBABLE'\n : 'TEST_EVIDENCIA_NO_VALIDA';\n\nconst notaControlTest = registroAprobadoProduccion\n ? 'La evidencia cumple las validaciones mínimas para una prueba.'\n : 'Esta ejecución utiliza evidencia visual no válida para producción. Se permite continuar únicamente para validar el flujo TEST de presentación y mensajería.';\n\nconst contextoReporteFinal = {\n ambiente: 'TEST_TELEGRAM',\n control_produccion: {\n registro_aprobado_produccion: registroAprobadoProduccion,\n audio_valido: audioValido,\n imagen_obligatoria_valida: imagenObligatoriaValida,\n imagenes_utiles_count: imagenesUtiles.length,\n videos_utiles_count: videosUtiles.length,\n alertas_evidencias: alertasEvidencias,\n nota: notaControlTest,\n },\n\n datos_declarados_en_audio: {\n propuesta_referencia: texto(datos.propuesta_referencia),\n marca_cliente: texto(datos.marca_cliente),\n pais: texto(datos.pais),\n ubicacion_ejecucion: texto(datos.ubicacion_ejecucion),\n fecha_ejecucion: texto(datos.fecha_ejecucion),\n implementacion: texto(datos.implementacion),\n comentarios_resultados: texto(datos.comentarios_resultados),\n transcripcion_audio: texto(datos.transcripcion_audio),\n datos_faltantes_audio: datosFaltantesAudio,\n },\n\n evidencias_drive: {\n carpeta_url: texto(datos.carpeta_drive_url),\n audio_url: texto(datos.audio_drive_url),\n imagenes_urls: lista(datos.imagenes_drive_urls),\n videos_urls: lista(datos.videos_drive_urls),\n },\n\n analisis_imagenes: analisisImagenes,\n analisis_videos: analisisVideos,\n};\n\nreturn {\n json: {\n ...datos,\n\n modo_test: true,\n canal_prueba: 'TELEGRAM',\n\n audio_valido_para_registro: audioValido,\n imagen_obligatoria_valida: imagenObligatoriaValida,\n imagenes_utiles_para_registro_count: imagenesUtiles.length,\n videos_utiles_para_registro_count: videosUtiles.length,\n alertas_evidencias: alertasEvidencias,\n\n registro_aprobado_produccion: registroAprobadoProduccion,\n permitir_generar_presentacion_test: true,\n estado_registro_test: estadoRegistroTest,\n nota_control_test: notaControlTest,\n\n contexto_reporte_final_json: JSON.stringify(contextoReporteFinal, null, 2),\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 5728, -432 ], "id": "5236c2e6-a0c1-493c-b952-f31680bdeca1", "name": "Code - Preparar consolidado final TEST" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst contexto = String(datos.contexto_reporte_final_json || '').trim();\n\nif (!contexto) {\n throw new Error('No se encontró contexto_reporte_final_json para generar el resumen final.');\n}\n\nconst prompt = `\nEres un asistente de GomezLee Marketing encargado de documentar propuestas ejecutadas.\n\nDebes generar el resumen final estructurado de una ejecución reportada por un manager.\n\nIMPORTANTE:\n- Este registro pertenece al ambiente TEST_TELEGRAM.\n- La presentación se generará únicamente para probar el flujo.\n- Si \"registro_aprobado_produccion\" es false, debes dejar claro que la evidencia NO es válida para producción.\n- No inventes información.\n- Los datos de propuesta, marca, ubicación, fecha, implementación y resultados provienen de la nota de voz del manager; trátalos como información declarada, no como validación visual.\n- Los hallazgos visuales solo pueden basarse en imágenes o videos marcados como evidencia útil.\n- Una captura de pantalla de Telegram o un video de barras de color no valida una ejecución física.\n- No corrijas automáticamente el nombre de la propuesta aunque parezca tener error de transcripción.\n- No relaciones todavía esta ejecución con una propuesta original del Banco de Propuestas.\n\nDevuelve únicamente un JSON válido, sin markdown y sin texto adicional, con esta estructura exacta:\n\n{\n \"ambiente\": \"TEST_TELEGRAM\",\n \"estatus_validacion\": \"\",\n \"titulo_presentacion\": \"\",\n \"subtitulo_presentacion\": \"\",\n \"resumen_ejecutivo\": \"\",\n \"propuesta_referencia_reportada\": \"\",\n \"marca_cliente\": \"\",\n \"pais\": \"\",\n \"ubicacion_ejecucion\": \"\",\n \"fecha_ejecucion\": \"\",\n \"implementacion_declarada\": \"\",\n \"resultados_declarados\": \"\",\n \"evidencias\": {\n \"audio\": {\n \"recibido\": true,\n \"analizado\": true,\n \"observacion\": \"\"\n },\n \"imagenes\": {\n \"cantidad_recibida\": 0,\n \"cantidad_util\": 0,\n \"observacion\": \"\"\n },\n \"videos\": {\n \"cantidad_recibida\": 0,\n \"cantidad_util\": 0,\n \"observacion\": \"\"\n }\n },\n \"hallazgos_visuales_confirmados\": [],\n \"alertas\": [],\n \"conclusion_control\": \"\",\n \"mensaje_feedback_manager\": \"\"\n}\n\nReglas de redacción:\n- \"estatus_validacion\" debe ser exactamente \"PRUEBA - EVIDENCIA NO VÁLIDA PARA PRODUCCIÓN\" cuando registro_aprobado_produccion sea false.\n- \"titulo_presentacion\" debe comenzar con \"[TEST] Propuesta Ejecutada - \".\n- \"subtitulo_presentacion\" debe incluir marca, país y fecha declarada.\n- \"resumen_ejecutivo\" debe tener máximo 3 oraciones.\n- \"hallazgos_visuales_confirmados\" debe quedar vacío si no existe evidencia visual útil.\n- \"alertas\" debe incluir los problemas detectados en imágenes o videos.\n- \"conclusion_control\" debe explicar claramente si el reporte puede o no registrarse en producción.\n- \"mensaje_feedback_manager\" debe ser un mensaje breve para Telegram. En esta prueba debe indicar que el reporte fue procesado en modo prueba, pero que la evidencia visual no sería válida en producción.\n\nCONTEXTO CONSOLIDADO DEL REPORTE:\n${contexto}\n`;\n\nreturn {\n json: {\n ...datos,\n prompt_resumen_final: prompt.trim(),\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 5936, -432 ], "id": "6a6aa296-583c-43f1-aa64-d2fcb296d3e6", "name": "Code - Preparar prompt resumen final TEST" }, { "parameters": { "modelId": { "__rl": true, "value": "models/gemini-2.5-flash-lite", "mode": "list", "cachedResultName": "models/gemini-2.5-flash-lite" }, "messages": { "values": [ { "content": "={{ $json.prompt_resumen_final }}" } ] }, "builtInTools": {}, "options": {} }, "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ 6144, -432 ], "id": "2e97b216-bc70-4ebe-9c50-04f03748fb5a", "name": "Gemini - Generar resumen final TEST", "credentials": { "googlePalmApi": { "id": "6Jdcd9FONxG35YuJ", "name": "Isaac - Gemini Api Gratis" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const respuestaGemini = $json || {};\n\n// Recupera los datos consolidados previos, sin arrastrar únicamente la respuesta aislada de Gemini.\nconst datosReporte = $('Code - Preparar consolidado final TEST').first().json;\n\nconst partes = Array.isArray(respuestaGemini.content?.parts)\n ? respuestaGemini.content.parts\n : [];\n\nconst textoRespuesta = partes\n .map((parte) => String(parte?.text || '').trim())\n .join('\\n')\n .trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\nif (!textoRespuesta) {\n throw new Error('Gemini no devolvió contenido para el resumen final.');\n}\n\nlet resumenFinal;\n\ntry {\n resumenFinal = JSON.parse(textoRespuesta);\n} catch (error) {\n throw new Error(\n `El resumen final de Gemini no llegó como JSON válido. Respuesta: ${textoRespuesta}`\n );\n}\n\nconst texto = (valor) => String(valor ?? '').trim();\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\nconst estatusValidacion = texto(resumenFinal.estatus_validacion);\n\nif (!estatusValidacion) {\n throw new Error('El resumen final no contiene estatus_validacion.');\n}\n\nreturn {\n json: {\n ...datosReporte,\n\n resumen_final_ia: resumenFinal,\n\n ambiente_resumen: texto(resumenFinal.ambiente),\n estatus_validacion: estatusValidacion,\n titulo_presentacion: texto(resumenFinal.titulo_presentacion),\n subtitulo_presentacion: texto(resumenFinal.subtitulo_presentacion),\n resumen_ejecutivo_final: texto(resumenFinal.resumen_ejecutivo),\n\n propuesta_referencia_final: texto(resumenFinal.propuesta_referencia_reportada),\n marca_cliente_final: texto(resumenFinal.marca_cliente),\n pais_final: texto(resumenFinal.pais),\n ubicacion_ejecucion_final: texto(resumenFinal.ubicacion_ejecucion),\n fecha_ejecucion_final: texto(resumenFinal.fecha_ejecucion),\n implementacion_declarada_final: texto(resumenFinal.implementacion_declarada),\n resultados_declarados_final: texto(resumenFinal.resultados_declarados),\n\n evidencias_resumen_final: resumenFinal.evidencias || {},\n hallazgos_visuales_confirmados_final: lista(resumenFinal.hallazgos_visuales_confirmados),\n alertas_finales: lista(resumenFinal.alertas),\n conclusion_control_final: texto(resumenFinal.conclusion_control),\n mensaje_feedback_manager_final: texto(resumenFinal.mensaje_feedback_manager),\n\n resumen_final_estado: 'GENERADO',\n permitir_generar_slides_test: true,\n bloquear_registro_banco_real: datosReporte.registro_aprobado_produccion !== true,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 6496, -432 ], "id": "b6847a73-d0cd-4e61-8b18-fa787f74cb5f", "name": "Code - Limpiar resumen final Gemini TEST" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datos = $json || {};\n\nconst texto = (valor) => String(valor ?? '').trim();\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\nconst limpiarNombreArchivo = (valor) =>\n texto(valor)\n .replace(/[\\\\/:*?\"<>|]/g, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 130);\n\nconst ejecucionId =\n texto(datos.ejecucion_id) ||\n texto(datos.session_id) ||\n `TEST_${Date.now()}`;\n\nconst marca =\n texto(datos.marca_cliente_final) ||\n texto(datos.marca_cliente) ||\n 'Marca no especificada';\n\nconst cliente =\n texto(datos.cliente_final) ||\n texto(datos.cliente) ||\n 'No especificado en audio';\n\nconst propuestaReferencia =\n texto(datos.propuesta_referencia_final) ||\n texto(datos.propuesta_referencia) ||\n 'Propuesta no especificada';\n\nconst pais =\n texto(datos.pais_final) ||\n texto(datos.pais) ||\n 'País no especificado';\n\nconst ubicacion =\n texto(datos.ubicacion_ejecucion_final) ||\n texto(datos.ubicacion_ejecucion) ||\n 'Ubicación no especificada';\n\nconst fechaEjecucion =\n texto(datos.fecha_ejecucion_final) ||\n texto(datos.fecha_ejecucion) ||\n 'Fecha no especificada';\n\nconst managerNombre =\n texto(datos.manager_nombre) ||\n 'Manager no especificado';\n\nconst fotosCount = Number(datos.imagenes_count || 0);\nconst videosCount = Number(datos.videos_count || 0);\nconst audiosCount = Number(datos.audio_count || 0);\n\nconst resumenIA =\n texto(datos.resumen_ejecutivo_final) ||\n 'No se generó resumen ejecutivo.';\n\nconst descripcionEjecucion = [\n texto(datos.implementacion_declarada_final)\n ? `Implementación declarada: ${texto(datos.implementacion_declarada_final)}`\n : '',\n texto(datos.resultados_declarados_final)\n ? `Resultados declarados: ${texto(datos.resultados_declarados_final)}`\n : '',\n texto(datos.conclusion_control_final)\n ? `Control de evidencia: ${texto(datos.conclusion_control_final)}`\n : '',\n]\n .filter(Boolean)\n .join('\\n\\n');\n\nconst hallazgosVisuales = lista(datos.hallazgos_visuales_confirmados_final)\n .map((hallazgo) => texto(hallazgo))\n .filter(Boolean);\n\nconst elementosDetectados = hallazgosVisuales.length\n ? hallazgosVisuales.map((hallazgo) => `• ${hallazgo}`).join('\\n')\n : 'No se confirmaron elementos físicos a partir de la evidencia visual enviada.';\n\nconst estadoCorto = datos.registro_aprobado_produccion === true\n ? 'VALIDADO PARA REGISTRO'\n : 'TEST | EVIDENCIA NO VÁLIDA PARA PRODUCCIÓN';\n\nconst tags = [\n datos.modo_test === true ? 'TEST' : '',\n marca,\n pais,\n datos.registro_aprobado_produccion === true\n ? 'Evidencia validada'\n : 'Evidencia no válida',\n]\n .filter(Boolean)\n .map((tag) => `• ${tag}`)\n .join('\\n');\n\nconst mediaFolderUrl =\n texto(datos.carpeta_drive_url) ||\n 'Carpeta no disponible';\n\nconst nombrePresentacion = limpiarNombreArchivo(\n `[TEST] Propuesta Ejecutada - ${marca} - ${fechaEjecucion} - ${ejecucionId}`\n);\n\nconst reemplazosSlides = {\n '{{ESTATUS_VALIDACION}}': estadoCorto,\n '{{MARCA}}': marca,\n '{{CLIENTE}}': cliente,\n '{{PROPUESTA_REFERENCIA}}': propuestaReferencia,\n '{{PAIS}}': pais,\n '{{UBICACION}}': ubicacion,\n '{{FECHA_EJECUCION}}': fechaEjecucion,\n '{{EJECUCION_ID}}': ejecucionId,\n '{{MANAGER_NOMBRE}}': managerNombre,\n '{{FOTOS_COUNT}}': String(fotosCount),\n '{{VIDEOS_COUNT}}': String(videosCount),\n '{{AUDIOS_COUNT}}': String(audiosCount),\n '{{RESUMEN_IA}}': resumenIA,\n '{{DESCRIPCION_EJECUCION}}': descripcionEjecucion,\n '{{ELEMENTOS_DETECTADOS}}': elementosDetectados,\n '{{TAGS}}': tags,\n '{{MEDIA_FOLDER_URL}}': mediaFolderUrl,\n};\n\nconst solicitudesReemplazoSlides = Object.entries(reemplazosSlides).map(\n ([placeholder, valor]) => ({\n replaceAllText: {\n containsText: {\n text: placeholder,\n matchCase: true,\n },\n replaceText: valor,\n },\n })\n);\n\nreturn {\n json: {\n ...datos,\n\n ejecucion_id_slides: ejecucionId,\n presentacion_nombre_drive: nombrePresentacion,\n\n estatus_validacion_slides: estadoCorto,\n cliente_slides: cliente,\n elementos_detectados_slides: elementosDetectados,\n tags_slides: tags,\n\n reemplazos_slides: reemplazosSlides,\n solicitudes_reemplazo_slides: solicitudesReemplazoSlides,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 6704, -432 ], "id": "9e4c81b5-bf81-438f-97b1-5fc2a55546f7", "name": "Code - Preparar presentación Slides TEST" }, { "parameters": { "operation": "copy", "fileId": { "__rl": true, "value": "10PZtoMFuiXczGM9Itbcjce4EKBUhiztFbXZiTWX2DKc", "mode": "list", "cachedResultName": "GLM_Propuesta_Ejecutada_Fulgencio_TEMPLATE_V2", "cachedResultUrl": "https://docs.google.com/presentation/d/10PZtoMFuiXczGM9Itbcjce4EKBUhiztFbXZiTWX2DKc/edit?usp=drivesdk" }, "name": "={{ $json.presentacion_nombre_drive }}", "sameFolder": false, "driveId": { "__rl": true, "mode": "list", "value": "My Drive" }, "folderId": { "__rl": true, "value": "={{ $json.carpeta_drive_id }}", "mode": "id" }, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 6912, -432 ], "id": "58e0e4dd-acf7-4b03-aadd-c8099fa8899b", "name": "Drive - Copiar template presentación TEST", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const copiaPresentacion = $json || {};\n\n// Recupera todos los datos preparados antes de copiar la plantilla.\nconst datosReporte = $('Code - Preparar presentación Slides TEST').first().json;\n\nconst texto = (valor) => String(valor ?? '').trim();\n\nconst presentacionId = texto(copiaPresentacion.id);\n\nif (!presentacionId) {\n throw new Error('No se recibió el ID de la presentación copiada desde Google Drive.');\n}\n\nconst presentacionNombre =\n texto(copiaPresentacion.name) ||\n texto(datosReporte.presentacion_nombre_drive);\n\nconst presentacionUrl =\n `https://docs.google.com/presentation/d/${presentacionId}/edit`;\n\nconst solicitudesReemplazo = Array.isArray(datosReporte.solicitudes_reemplazo_slides)\n ? datosReporte.solicitudes_reemplazo_slides\n : [];\n\nif (!solicitudesReemplazo.length) {\n throw new Error('No se encontraron solicitudes_reemplazo_slides para actualizar la presentación.');\n}\n\nreturn {\n json: {\n ...datosReporte,\n\n presentacion_drive_id: presentacionId,\n presentacion_drive_nombre: presentacionNombre,\n presentacion_drive_url: presentacionUrl,\n presentacion_mime_type: texto(copiaPresentacion.mimeType),\n\n slides_reemplazos_count: solicitudesReemplazo.length,\n slides_batch_update_body: {\n requests: solicitudesReemplazo,\n },\n\n presentacion_copia_estado: 'CREADA',\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 7120, -432 ], "id": "81201353-8cb8-4e4e-856b-9bdc37bce81f", "name": "Code - Guardar datos presentación copiada TEST" }, { "parameters": { "method": "POST", "url": "={{ 'https://slides.googleapis.com/v1/presentations/' + $json.presentacion_drive_id + ':batchUpdate' }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "googleOAuth2Api", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ $json.slides_batch_update_body }}", "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 7360, -432 ], "id": "ae880386-1743-46b9-b799-a2b7cab21ac2", "name": "HTTP Request - Reemplazar textos Slides TEST", "credentials": { "googleOAuth2Api": { "id": "eHseMeH39kRcXgOF", "name": "Google account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Respuesta del HTTP Request de Google Slides.\n// Si llegamos aquí, los placeholders de la presentación ya fueron reemplazados.\nconst respuestaSlides = $json || {};\n\n// Recuperamos todos los datos completos que existían antes del HTTP Request.\nconst datos = $('Code - Guardar datos presentación copiada TEST').first().json || {};\n\nconst texto = (valor) => String(valor ?? '').trim();\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\n// -----------------------------\n// Validación de presentación\n// -----------------------------\nconst presentacionId =\n texto(respuestaSlides.presentationId) ||\n texto(datos.presentacion_drive_id);\n\nif (!presentacionId) {\n throw new Error('No se encontró el ID de la presentación generada.');\n}\n\nconst presentacionUrl =\n texto(datos.presentacion_drive_url) ||\n `https://docs.google.com/presentation/d/${presentacionId}/edit`;\n\n// -----------------------------\n// Identificadores principales\n// -----------------------------\nconst sessionId = texto(datos.session_id);\n\nconst ejecucionId =\n texto(datos.ejecucion_id_slides) ||\n texto(datos.ejecucion_id) ||\n sessionId;\n\nif (!ejecucionId) {\n throw new Error('No se encontró ejecucion_id ni session_id para guardar el registro.');\n}\n\n// -----------------------------\n// Datos declarados en audio\n// -----------------------------\nconst propuestaReferencia =\n texto(datos.propuesta_referencia_final) ||\n texto(datos.propuesta_referencia) ||\n texto(datos.propuesta_referencia_reportada);\n\nconst marca =\n texto(datos.marca_cliente_final) ||\n texto(datos.marca_cliente);\n\nconst cliente =\n texto(datos.cliente_final) ||\n texto(datos.cliente) ||\n 'No especificado en audio';\n\nconst pais =\n texto(datos.pais_final) ||\n texto(datos.pais);\n\nconst ubicacion =\n texto(datos.ubicacion_ejecucion_final) ||\n texto(datos.ubicacion_ejecucion);\n\nconst fechaEjecucion =\n texto(datos.fecha_ejecucion_final) ||\n texto(datos.fecha_ejecucion);\n\nconst implementacion =\n texto(datos.implementacion_declarada_final) ||\n texto(datos.implementacion);\n\nconst comentarioOriginal =\n texto(datos.resultados_declarados_final) ||\n texto(datos.comentarios_resultados);\n\nconst transcripcionAudio =\n texto(datos.transcripcion_audio) ||\n texto(datos.transcripcion);\n\n// -----------------------------\n// Datos generados / validados\n// -----------------------------\nconst resumenIA =\n texto(datos.resumen_ejecutivo_final) ||\n texto(datos.resumen_ejecutivo) ||\n texto(datos.reemplazos_slides?.['{{RESUMEN_IA}}']);\n\nconst descripcionEjecucion =\n texto(datos.reemplazos_slides?.['{{DESCRIPCION_EJECUCION}}']) ||\n [\n implementacion ? `Implementación declarada: ${implementacion}` : '',\n comentarioOriginal ? `Resultados declarados: ${comentarioOriginal}` : '',\n texto(datos.conclusion_control_final)\n ? `Control de evidencia: ${texto(datos.conclusion_control_final)}`\n : '',\n ]\n .filter(Boolean)\n .join('\\n\\n');\n\nconst elementosDetectados =\n texto(datos.elementos_detectados_slides) ||\n lista(datos.hallazgos_visuales_confirmados_final)\n .map((item) => texto(item))\n .filter(Boolean)\n .map((item) => `• ${item}`)\n .join('\\n') ||\n 'No se confirmaron elementos físicos a partir de la evidencia visual enviada.';\n\nconst tags =\n texto(datos.tags_slides) ||\n ['TEST', marca, pais, 'Evidencia no válida']\n .filter(Boolean)\n .map((item) => `• ${item}`)\n .join('\\n');\n\n// -----------------------------\n// Enlaces y conteos\n// -----------------------------\nconst mediaFolderUrl = texto(datos.carpeta_drive_url);\n\nconst fotosCount = Number(datos.imagenes_count || 0);\nconst videosCount = Number(datos.videos_count || 0);\nconst audiosCount = Number(datos.audio_count || 0);\n\n// -----------------------------\n// Fecha de recepción\n// -----------------------------\nconst fechaRecepcion =\n texto(datos.fecha_recepcion) ||\n texto(datos.fecha_inicio) ||\n texto(datos.eventos_audio?.[0]?.fecha_recepcion) ||\n new Date().toISOString();\n\n// -----------------------------\n// Estados de esta prueba\n// -----------------------------\n// No vinculamos con el banco real porque es una ejecución TEST\n// y la evidencia no valida una ejecución física real.\nconst estadoRevision = 'TEST_NO_VALIDO_PRODUCCION';\n\nconst motivoRevision =\n texto(datos.conclusion_control_final) ||\n 'Evidencia enviada únicamente para probar el flujo. No valida una ejecución física real.';\n\nconst propuestaMatchRevision =\n 'No se realizó match contra el banco de propuestas porque esta ejecución corresponde a una prueba TEST con evidencia no válida para producción.';\n\n// -----------------------------\n// Fila final para Google Sheets\n// Los nombres coinciden exactamente con los encabezados A:AE.\n// -----------------------------\nreturn {\n json: {\n ejecucion_id: ejecucionId,\n fecha_recepcion: fechaRecepcion,\n fecha_ejecucion: fechaEjecucion,\n manager_nombre: texto(datos.manager_nombre),\n manager_telefono: texto(datos.manager_telefono),\n\n propuesta_referencia: propuestaReferencia,\n propuesta_match_estado: 'NO_EVALUADO_TEST',\n propuesta_match_confianza: 0,\n propuesta_nombre_banco: '',\n propuesta_link_banco: '',\n\n marca: marca,\n cliente: cliente,\n pais: pais,\n ubicacion: ubicacion,\n comentario_original: comentarioOriginal,\n\n resumen_ia: resumenIA,\n descripcion_ejecucion: descripcionEjecucion,\n elementos_detectados: elementosDetectados,\n tags: tags,\n\n media_folder_url: mediaFolderUrl,\n presentacion_ejecucion_url: presentacionUrl,\n\n fotos_count: fotosCount,\n videos_count: videosCount,\n audios_count: audiosCount,\n\n estado_revision: estadoRevision,\n motivo_revision: motivoRevision,\n ultima_actualizacion: new Date().toISOString(),\n\n session_id: sessionId,\n canal_origen: texto(datos.canal_origen) || 'TELEGRAM',\n transcripcion_audio: transcripcionAudio,\n propuesta_match_revision: propuestaMatchRevision,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 7824, -432 ], "id": "287a8282-ce4a-4560-bff3-8a6bf00bb68c", "name": "Code - Preparar fila propuestas ejecutadas TEST" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 737153956, "mode": "list", "cachedResultName": "propuestas_ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit#gid=737153956" }, "columns": { "mappingMode": "autoMapInputData", "value": { "ejecucion_id": "={{ $json.ejecucion_id }}", "fecha_recepcion": "={{ $json.fecha_recepcion }}", "fecha_ejecucion": "={{ $json.fecha_ejecucion }}", "manager_nombre": "={{ $json.manager_nombre }}", "manager_telefono": "={{ $json.manager_telefono }}", "propuesta_referencia": "={{ $json.propuesta_referencia }}", "propuesta_match_estado": "={{ $json.propuesta_match_estado }}", "propuesta_match_confianza": "={{ $json.propuesta_match_confianza }}", "propuesta_nombre_banco": "={{ $json.propuesta_nombre_banco }}", "propuesta_link_banco": "={{ $json.propuesta_link_banco }}", "marca": "={{ $json.marca }}", "cliente": "={{ $json.cliente }}", "pais": "={{ $json.cliente }}", "ubicacion": "={{ $json.ubicacion }}", "comentario_original": "={{ $json.comentario_original }}", "resumen_ia": "={{ $json.resumen_ia }}", "descripcion_ejecucion": "={{ $json.descripcion_ejecucion }}", "elementos_detectados": "={{ $json.elementos_detectados }}", "tags": "={{ $json.tags }}", "media_folder_url": "={{ $json.media_folder_url }}", "presentacion_ejecucion_url": "={{ $json.presentacion_ejecucion_url }}", "fotos_count": "={{ $json.fotos_count }}", "videos_count": "={{ $json.videos_count }}", "audios_count": "={{ $json.audios_count }}", "estado_revision": "={{ $json.estado_revision }}", "motivo_revision": "={{ $json.motivo_revision }}", "ultima_actualizacion": "={{ $json.ultima_actualizacion }}", "session_id": "={{ $json.session_id }}", "canal_origen": "={{ $json.canal_origen }}", "transcripcion_audio": "={{ $json.transcripcion_audio }}", "propuesta_match_revision": "={{ $json.propuesta_match_revision }}" }, "matchingColumns": [], "schema": [ { "id": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_recepcion", "displayName": "fecha_recepcion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_ejecucion", "displayName": "fecha_ejecucion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_referencia", "displayName": "propuesta_referencia", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_match_estado", "displayName": "propuesta_match_estado", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_match_confianza", "displayName": "propuesta_match_confianza", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_nombre_banco", "displayName": "propuesta_nombre_banco", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_link_banco", "displayName": "propuesta_link_banco", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "marca", "displayName": "marca", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "cliente", "displayName": "cliente", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "pais", "displayName": "pais", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ubicacion", "displayName": "ubicacion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "comentario_original", "displayName": "comentario_original", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "resumen_ia", "displayName": "resumen_ia", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "descripcion_ejecucion", "displayName": "descripcion_ejecucion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "elementos_detectados", "displayName": "elementos_detectados", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "tags", "displayName": "tags", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "media_folder_url", "displayName": "media_folder_url", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "presentacion_ejecucion_url", "displayName": "presentacion_ejecucion_url", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fotos_count", "displayName": "fotos_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audios_count", "displayName": "audios_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "estado_revision", "displayName": "estado_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actualizacion", "displayName": "ultima_actualizacion", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "transcripcion_audio", "displayName": "transcripcion_audio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "propuesta_match_revision", "displayName": "propuesta_match_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 8048, -432 ], "id": "2e1fb406-81b3-45a0-b305-8900ed0ede71", "name": "Sheets - Guardar propuesta ejecutada TEST", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const registroFinal = $json || {};\n\nconst texto = (valor) => String(valor ?? '').trim();\n\nconst sessionId = texto(registroFinal.session_id);\nconst ejecucionId = texto(registroFinal.ejecucion_id);\n\nif (!sessionId) {\n throw new Error('No se recibió session_id para cerrar la sesión.');\n}\n\nif (!ejecucionId) {\n throw new Error('No se recibió ejecucion_id para relacionarlo con la sesión.');\n}\n\nreturn {\n json: {\n session_id: sessionId,\n\n ultima_actividad: new Date().toISOString(),\n etapa: 'FINALIZADA',\n estado: 'COMPLETADA',\n\n ejecucion_id: ejecucionId,\n motivo_revision: texto(registroFinal.estado_revision) || 'TEST_NO_VALIDO_PRODUCCION',\n\n // Conservamos datos para el mensaje final de Telegram.\n manager_nombre: texto(registroFinal.manager_nombre),\n manager_telefono: texto(registroFinal.manager_telefono),\n telegram_chat_id: texto(registroFinal.manager_telefono),\n canal_origen: texto(registroFinal.canal_origen) || 'TELEGRAM',\n\n marca: texto(registroFinal.marca),\n propuesta_referencia: texto(registroFinal.propuesta_referencia),\n estado_revision: texto(registroFinal.estado_revision),\n media_folder_url: texto(registroFinal.media_folder_url),\n presentacion_ejecucion_url: texto(registroFinal.presentacion_ejecucion_url),\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 8272, -432 ], "id": "d1f119b8-803d-4212-b414-c00f38f8d53a", "name": "Code - Preparar actualización sesión final TEST" }, { "parameters": { "operation": "update", "documentId": { "__rl": true, "value": "1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ", "mode": "list", "cachedResultName": "TEST - Fulgencio - Propuestas Ejecutadas", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1SEzELmiQO6EOBZ7AARfwzfrZpQ3AfSS_Y1VRHgb8ziQ/edit?usp=drivesdk" }, "sheetName": { "__rl": true, "value": 616166581, "mode": "list", "cachedResultName": "wa_ejecuciones_sesiones", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nbeuaW2lxvWJYVFD-E0uJRhTGzUa-nuH2WYItHdTsKk/edit#gid=616166581" }, "columns": { "mappingMode": "defineBelow", "value": { "session_id": "={{ $json.session_id }}", "ultima_actividad": "={{ $json.ultima_actividad }}", "etapa": "={{ $json.etapa }}", "estado": "={{ $json.estado }}", "ejecucion_id": "={{ $json.ejecucion_id }}", "motivo_revision": "={{ $json.motivo_revision }}" }, "matchingColumns": [ "session_id" ], "schema": [ { "id": "session_id", "displayName": "session_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "manager_telefono", "displayName": "manager_telefono", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "manager_nombre", "displayName": "manager_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "canal_origen", "displayName": "canal_origen", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "fecha_inicio", "displayName": "fecha_inicio", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "ultima_actividad", "displayName": "ultima_actividad", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "etapa", "displayName": "etapa", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "audio_count", "displayName": "audio_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "imagenes_count", "displayName": "imagenes_count", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "videos_count", "displayName": "videos_count", "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": "ejecucion_id", "displayName": "ejecucion_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "motivo_revision", "displayName": "motivo_revision", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true }, { "id": "row_number", "displayName": "row_number", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "readOnly": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 8560, -432 ], "id": "a894d4b8-a9ec-4d0a-8785-036dd3da80f8", "name": "Sheets - Actualizar sesión finalizada TEST", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Recuperamos los datos antes de actualizar la sesión,\n// porque el nodo de Google Sheets solo devuelve las columnas actualizadas.\nconst datosSesion =\n $('Code - Preparar actualización sesión final TEST').first().json || {};\n\nconst registroFinal =\n $('Code - Preparar fila propuestas ejecutadas TEST').first().json || {};\n\n// ------------------------------\n// Funciones auxiliares\n// ------------------------------\nconst texto = (valor) => String(valor ?? '').trim();\n\nconst escapeHtml = (valor) =>\n texto(valor)\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nconst escapeUrlHtml = (valor) =>\n texto(valor)\n .replace(/&/g, '&')\n .replace(/\"/g, '"');\n\n// ------------------------------\n// Datos principales del cierre\n// ------------------------------\nconst chatId =\n texto(datosSesion.telegram_chat_id) ||\n texto(datosSesion.manager_telefono) ||\n texto(registroFinal.manager_telefono);\n\nconst ejecucionId =\n texto(registroFinal.ejecucion_id) ||\n texto(datosSesion.ejecucion_id) ||\n texto(datosSesion.session_id);\n\nconst propuestaReferencia =\n texto(registroFinal.propuesta_referencia) ||\n 'No especificada';\n\nconst marca =\n texto(registroFinal.marca) ||\n 'No especificada';\n\nconst presentacionUrl =\n texto(registroFinal.presentacion_ejecucion_url);\n\nconst carpetaUrl =\n texto(registroFinal.media_folder_url);\n\nconst estadoRevision =\n texto(registroFinal.estado_revision) ||\n texto(datosSesion.estado_revision) ||\n texto(datosSesion.motivo_revision);\n\n// ------------------------------\n// Validaciones obligatorias\n// ------------------------------\nif (!chatId) {\n throw new Error(\n 'No se encontró telegram_chat_id o manager_telefono para enviar el mensaje final.'\n );\n}\n\nif (!presentacionUrl) {\n throw new Error(\n 'No se encontró presentacion_ejecucion_url para enviar el cierre.'\n );\n}\n\nif (!carpetaUrl) {\n throw new Error(\n 'No se encontró media_folder_url para enviar el cierre.'\n );\n}\n\n// ------------------------------\n// Estado mostrado al usuario\n// ------------------------------\nlet bloqueResultado = '';\n\nif (\n estadoRevision === 'TEST_NO_VALIDO_PRODUCCION' ||\n estadoRevision === 'NO_VALIDO_PRODUCCION'\n) {\n bloqueResultado = [\n '',\n '⚠️ Resultado de prueba: la evidencia enviada no es válida para registro en producción.',\n ].join('\\n');\n} else if (\n estadoRevision === 'VALIDADO' ||\n estadoRevision === 'APROBADO' ||\n estadoRevision === 'VALIDO_PRODUCCION'\n) {\n bloqueResultado = [\n '',\n '✅ Resultado: la evidencia fue procesada correctamente para registro.',\n ].join('\\n');\n} else if (estadoRevision) {\n bloqueResultado = [\n '',\n `ℹ️ Estado de revisión: ${escapeHtml(estadoRevision)}`,\n ].join('\\n');\n}\n\n// ------------------------------\n// Mensaje final en formato HTML\n// Importante: el nodo Telegram debe usar Parse Mode = HTML.\n// ------------------------------\nconst mensaje = [\n '✅ Reporte procesado correctamente.',\n bloqueResultado,\n '',\n `Propuesta: ${escapeHtml(propuestaReferencia)}`,\n `Marca: ${escapeHtml(marca)}`,\n `ID de ejecución: ${escapeHtml(ejecucionId)}`,\n '',\n 'Presentación generada:',\n `Abrir presentación de ejecución`,\n '',\n 'Carpeta de evidencias:',\n `Abrir carpeta de evidencias`,\n '',\n 'Este reporte quedó cerrado. Ya puedes iniciar uno nuevo escribiendo: /start',\n].join('\\n');\n\n// ------------------------------\n// Salida para Telegram y compatibilidad\n// ------------------------------\nreturn {\n json: {\n ...registroFinal,\n\n telegram_chat_id: chatId,\n telegram_text: mensaje,\n\n // Compatibilidad con configuraciones previas del nodo final.\n whatsapp_to: chatId,\n whatsapp_text: mensaje,\n },\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 8768, -432 ], "id": "4748701d-2799-48e6-9a64-0c339ca7f156", "name": "Code - Preparar mensaje final Telegram TEST" }, { "parameters": { "chatId": "={{ $json.telegram_chat_id || $json.whatsapp_to }}", "text": "={{ $json.telegram_text }}", "additionalFields": { "parse_mode": "HTML" } }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 11040, -864 ], "id": "0788a62e-a657-4700-aafa-0854b5ce548f", "name": "Telegram - Enviar cierre final TEST", "webhookId": "7c767a53-6a2c-4607-bf7d-77425d598c3f", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "chatId": "={{ $json.telegram_chat_id || $json.whatsapp_to }}", "text": "={{ $json.telegram_text || $json.whatsapp_text }}", "additionalFields": {} }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ -2624, 448 ], "id": "e5ec9510-06a7-49e0-8af5-08de4b33aea8", "name": "Telegram - Enviar aviso procesamiento TEST", "webhookId": "7c767a53-6a2c-4607-bf7d-77425d598c3f", "credentials": { "telegramApi": { "id": "lN5LCJWC94k6r5fE", "name": "Telegram account" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const datosSesion = $('Sheets - Actualizar sesión a procesando').first().json;\n\nreturn {\n json: datosSesion,\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -2208, 416 ], "id": "c09e5d72-1a11-4e44-895e-ab9904a6e798", "name": "Code - Recuperar datos después aviso procesamiento TEST" }, { "parameters": { "jsCode": "const datos = $json || {};\n\nreturn [\n {\n json: {\n ...datos,\n\n videos_drive_ids: [],\n videos_drive_urls: [],\n videos_drive_detalle: [],\n\n videos_estado_drive: 'SIN_VIDEO',\n videos_subidos_drive: false,\n videos_drive_completos: true,\n\n evidencias_drive_completas: true,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2768, -80 ], "id": "611cfc04-64dc-4bb0-9dde-f1db296725f3", "name": "Code - Continuar sin videos Drive TEST" }, { "parameters": { "jsCode": "const datos = $json || {};\n\nconst lista = (valor) => Array.isArray(valor) ? valor : [];\n\nreturn [\n {\n json: {\n ...datos,\n\n // Aseguramos que no haya videos en esta ruta\n videos_drive_ids: lista(datos.videos_drive_ids),\n videos_drive_urls: lista(datos.videos_drive_urls),\n videos_drive_detalle: lista(datos.videos_drive_detalle),\n\n // Salida compatible para los nodos siguientes\n analisis_videos: [],\n videos_analizados: false,\n videos_estado_analisis: 'SIN_VIDEO',\n videos_alertas: [],\n videos_utiles_count: 0,\n videos_con_evidencia_util: 0,\n videos_sin_evidencia_util: 0,\n\n // Confirmación interna de continuidad\n ruta_video: 'SIN_VIDEO',\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 4880, -160 ], "id": "ee345096-4deb-46d1-9e3a-ef32e6886c4d", "name": "Code - Continuar sin análisis videos TEST" } ], "connections": { "Code - Normalizar evento WhatsApp": { "main": [ [ { "node": "IF - Mensaje entrante válido", "type": "main", "index": 0 } ] ] }, "Respond - OK Evolution": { "main": [ [] ] }, "IF - Mensaje entrante válido": { "main": [ [ { "node": "IF - Es comando de cierre", "type": "main", "index": 0 } ] ] }, "Code - Preparar formato WhatsApp": { "main": [ [ { "node": "HTTP Request - Enviar mensaje WhatsApp", "type": "main", "index": 0 } ] ] }, "Sheets - Guardar evento WhatsApp": { "main": [ [ { "node": "Code - Preparar confirmación recibido", "type": "main", "index": 0 } ] ] }, "Code - Preparar confirmación recibido": { "main": [ [ { "node": "HTTP Request - Enviar mensaje WhatsApp", "type": "main", "index": 0 } ] ] }, "Code - Preparar respuesta LISTO": { "main": [ [ { "node": "HTTP Request - Enviar mensaje WhatsApp", "type": "main", "index": 0 } ] ] }, "Sheets - Leer eventos pendientes": { "main": [ [ { "node": "Code - Consolidar sesión LISTO", "type": "main", "index": 0 } ] ] }, "Code - Consolidar sesión LISTO": { "main": [ [ { "node": "IF - Sesión lista para procesar", "type": "main", "index": 0 } ] ] }, "IF - Sesión lista para procesar": { "main": [ [ { "node": "Code - Preparar carpeta ejecución", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar respuesta LISTO", "type": "main", "index": 0 } ] ] }, "Code - Preparar carpeta ejecución": { "main": [ [ { "node": "Drive - Crear carpeta ejecución", "type": "main", "index": 0 } ] ] }, "Drive - Crear carpeta ejecución": { "main": [ [ { "node": "Code - Preparar análisis IA ejecución", "type": "main", "index": 0 } ] ] }, "Code - Preparar análisis IA ejecución": { "main": [ [ { "node": "Gemini - Analizar texto ejecución", "type": "main", "index": 0 } ] ] }, "Gemini - Analizar texto ejecución": { "main": [ [ { "node": "Code - Preparar fila propuesta ejecutada", "type": "main", "index": 0 } ] ] }, "Code - Preparar fila propuesta ejecutada": { "main": [ [ { "node": "Drive - Copiar template presentación", "type": "main", "index": 0 } ] ] }, "Sheets - Guardar propuesta ejecutada": { "main": [ [ { "node": "Code - Preparar eventos para marcar procesados", "type": "main", "index": 0 } ] ] }, "Code - Preparar eventos para marcar procesados": { "main": [ [ { "node": "Sheets - Marcar eventos procesados", "type": "main", "index": 0 } ] ] }, "Sheets - Marcar eventos procesados": { "main": [ [ { "node": "Code - Preparar confirmación procesado", "type": "main", "index": 0 } ] ] }, "Code - Preparar confirmación procesado": { "main": [ [ { "node": "HTTP Request - Enviar mensaje WhatsApp", "type": "main", "index": 0 } ] ] }, "Webhook": { "main": [ [] ] }, "Drive - Copiar template presentación": { "main": [ [ { "node": "Code - Preparar reemplazos Slides", "type": "main", "index": 0 } ] ] }, "Code - Preparar reemplazos Slides": { "main": [ [ { "node": "HTTP Request - Reemplazar textos Slides", "type": "main", "index": 0 } ] ] }, "HTTP Request - Reemplazar textos Slides": { "main": [ [ { "node": "Code - Actualizar link presentación", "type": "main", "index": 0 } ] ] }, "Code - Actualizar link presentación": { "main": [ [ { "node": "Sheets - Guardar propuesta ejecutada", "type": "main", "index": 0 } ] ] }, "When Executed by Another Workflow": { "main": [ [] ] }, "ANTERIOR - Switch Comando WhatsApp": { "main": [ [ { "node": "Code - Preparar formato WhatsApp", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer eventos pendientes", "type": "main", "index": 0 } ], [ { "node": "Sheets - Guardar evento WhatsApp", "type": "main", "index": 0 } ] ] }, "Code - Resolver inicio sesión": { "main": [ [ { "node": "IF - Crear nueva sesión", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones existentes": { "main": [ [ { "node": "Code - Resolver inicio sesión", "type": "main", "index": 0 } ] ] }, "IF - Crear nueva sesión": { "main": [ [ { "node": "Sheets - Crear sesión reporte", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar aviso sesión activa", "type": "main", "index": 0 } ] ] }, "Sheets - Crear sesión reporte": { "main": [ [ { "node": "Code - Preparar bienvenida y solicitud audio", "type": "main", "index": 0 } ] ] }, "Code - Preparar bienvenida y solicitud audio": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Code - Preparar aviso sesión activa": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Switch - Acción flujo guiado": { "main": [ [ { "node": "Sheets - Leer sesiones existentes", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para audio", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para imagen", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para fotos listas", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para video", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para cierre", "type": "main", "index": 0 } ], [ { "node": "Sheets - Leer sesiones para cierre", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para audio": { "main": [ [ { "node": "Code - Resolver sesión para audio", "type": "main", "index": 0 } ] ] }, "Code - Resolver sesión para audio": { "main": [ [ { "node": "IF - Audio válido para sesión", "type": "main", "index": 0 } ] ] }, "IF - Audio válido para sesión": { "main": [ [ { "node": "Sheets - Guardar evento audio", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo audio", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo audio": { "main": [ [] ] }, "Sheets - Guardar evento audio": { "main": [ [ { "node": "Sheets - Actualizar sesión tras audio", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión tras audio": { "main": [ [ { "node": "Code - Preparar solicitud imágenes", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para imagen": { "main": [ [ { "node": "Code - Resolver sesión para imagen", "type": "main", "index": 0 } ] ] }, "Code - Resolver sesión para imagen": { "main": [ [ { "node": "IF - Imagen válida para sesión", "type": "main", "index": 0 } ] ] }, "IF - Imagen válida para sesión": { "main": [ [ { "node": "Sheets - Guardar evento imagen", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo imagen", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo imagen": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Guardar evento imagen": { "main": [ [ { "node": "Sheets - Actualizar sesión tras imagen", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión tras imagen": { "main": [ [ { "node": "Code - Preparar confirmación imagen recibida", "type": "main", "index": 0 } ] ] }, "Code - Preparar confirmación imagen recibida": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para fotos listas": { "main": [ [ { "node": "Code - Validar fotos listas", "type": "main", "index": 0 } ] ] }, "Code - Validar fotos listas": { "main": [ [ { "node": "IF - Puede pasar a videos", "type": "main", "index": 0 } ] ] }, "IF - Puede pasar a videos": { "main": [ [ { "node": "Sheets - Actualizar sesión a video", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo fotos listas", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo fotos listas": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión a video": { "main": [ [ { "node": "Code - Preparar solicitud videos", "type": "main", "index": 0 } ] ] }, "Code - Preparar solicitud videos": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para video": { "main": [ [ { "node": "Code - Resolver sesión para video", "type": "main", "index": 0 } ] ] }, "Code - Resolver sesión para video": { "main": [ [ { "node": "IF - Video válido para sesión", "type": "main", "index": 0 } ] ] }, "IF - Video válido para sesión": { "main": [ [ { "node": "Sheets - Guardar evento video", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo video", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo video": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Guardar evento video": { "main": [ [ { "node": "Sheets - Actualizar sesión tras video", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión tras video": { "main": [ [ { "node": "Code - Preparar confirmación video recibido", "type": "main", "index": 0 } ] ] }, "Code - Preparar confirmación video recibido": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Code - Preparar solicitud imágenes": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Leer sesiones para cierre": { "main": [ [ { "node": "Code - Validar cierre reporte", "type": "main", "index": 0 } ] ] }, "Code - Validar cierre reporte": { "main": [ [ { "node": "IF - Cierre válido", "type": "main", "index": 0 } ] ] }, "Code - Preparar rechazo cierre": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión a procesando": { "main": [ [ { "node": "Code - Preparar aviso procesamiento", "type": "main", "index": 0 } ] ] }, "Code - Preparar procesamiento final guiado": { "main": [ [ { "node": "Sheets - Leer eventos de sesión final", "type": "main", "index": 0 } ] ] }, "Sheets - Leer eventos de sesión final": { "main": [ [ { "node": "Code - Consolidar sesión guiada para IA", "type": "main", "index": 0 } ] ] }, "Code - Consolidar sesión guiada para IA": { "main": [ [ { "node": "IF - Sesión completa para analizar", "type": "main", "index": 0 } ] ] }, "IF - Sesión completa para analizar": { "main": [ [ { "node": "Code - Preparar descarga audio Telegram", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar error sesión incompleta", "type": "main", "index": 0 } ] ] }, "Code - Preparar error sesión incompleta": { "main": [ [ { "node": "Telegram - Enviar mensaje", "type": "main", "index": 0 } ] ] }, "Telegram Trigger": { "main": [ [ { "node": "Code - Normalizar evento WhatsApp", "type": "main", "index": 0 } ] ] }, "HTTP Request - Obtener audio Evolution": { "main": [ [] ] }, "IF - Cierre válido": { "main": [ [ { "node": "Sheets - Actualizar sesión a procesando", "type": "main", "index": 0 } ], [ { "node": "Code - Preparar rechazo cierre", "type": "main", "index": 0 } ] ] }, "Code - Preparar aviso procesamiento": { "main": [ [ { "node": "Telegram - Enviar aviso procesamiento TEST", "type": "main", "index": 0 } ] ] }, "IF - Es comando de cierre": { "main": [ [ { "node": "Sheets - Leer sesiones para cierre", "type": "main", "index": 0 } ], [ { "node": "Switch - Acción flujo guiado", "type": "main", "index": 0 } ] ] }, "Code - Preparar descarga audio Telegram": { "main": [ [ { "node": "Telegram - Descargar audio", "type": "main", "index": 0 } ] ] }, "Telegram - Descargar audio": { "main": [ [ { "node": "Gemini - Analizar nota de voz", "type": "main", "index": 0 } ] ] }, "Code - Preparar descargas imágenes Telegram": { "main": [ [ { "node": "Telegram - Descargar imágenes", "type": "main", "index": 0 } ] ] }, "IF - Tiene videos para descargar": { "main": [ [ { "node": "Code - Preparar descargas videos Telegram", "type": "main", "index": 0 } ] ] }, "Code - Preparar descargas videos Telegram": { "main": [ [ { "node": "Telegram - Descargar videos", "type": "main", "index": 0 } ] ] }, "Gemini - Analizar nota de voz": { "main": [ [ { "node": "Code - Limpiar resultado audio Gemini", "type": "main", "index": 0 } ] ] }, "Code - Limpiar resultado audio Gemini": { "main": [ [ { "node": "Code - Preparar carpeta ejecución Drive", "type": "main", "index": 0 } ] ] }, "Code - Preparar carpeta ejecución Drive": { "main": [ [ { "node": "Drive - Crear Carpeta Ejecución", "type": "main", "index": 0 } ] ] }, "Drive - Crear Carpeta Ejecución": { "main": [ [ { "node": "Code - Guardar Datos Carpeta Ejecución TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar Datos Carpeta Ejecución TEST": { "main": [ [ { "node": "Code - Preparar archivo audio para Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar archivo audio para Drive TEST": { "main": [ [ { "node": "Telegram - Descargar audio para Drive TEST", "type": "main", "index": 0 } ] ] }, "Telegram - Descargar audio para Drive TEST": { "main": [ [ { "node": "Drive - Subir audio ejecución TEST", "type": "main", "index": 0 } ] ] }, "Drive - Subir audio ejecución TEST": { "main": [ [ { "node": "Code - Guardar enlace audio Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar enlace audio Drive TEST": { "main": [ [ { "node": "Code - Preparar archivos imágenes para Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar archivos imágenes para Drive TEST": { "main": [ [ { "node": "Telegram - Descargar imágenes para Drive TEST", "type": "main", "index": 0 } ] ] }, "Telegram - Descargar imágenes para Drive TEST": { "main": [ [ { "node": "Drive - Subir imágenes ejecución TEST", "type": "main", "index": 0 } ] ] }, "Drive - Subir imágenes ejecución TEST": { "main": [ [ { "node": "Code - Guardar enlaces imágenes Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar enlaces imágenes Drive TEST": { "main": [ [ { "node": "IF - Tiene videos para subir Drive TEST", "type": "main", "index": 0 } ] ] }, "IF - Tiene videos para subir Drive TEST": { "main": [ [ { "node": "Code - Preparar archivos videos para Drive TEST", "type": "main", "index": 0 } ], [ { "node": "Code - Continuar sin videos Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar archivos videos para Drive TEST": { "main": [ [ { "node": "Telegram - Descargar videos para Drive TEST", "type": "main", "index": 0 } ] ] }, "Telegram - Descargar videos para Drive TEST": { "main": [ [ { "node": "Drive - Subir videos ejecución TEST", "type": "main", "index": 0 } ] ] }, "Drive - Subir videos ejecución TEST": { "main": [ [ { "node": "Code - Guardar enlaces videos Drive TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar enlaces videos Drive TEST": { "main": [ [ { "node": "Code - Preparar imágenes para análisis Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar imágenes para análisis Gemini TEST": { "main": [ [ { "node": "Drive - Descargar imágenes para Gemini TEST", "type": "main", "index": 0 } ] ] }, "Drive - Descargar imágenes para Gemini TEST": { "main": [ [ { "node": "Gemini - Analizar imágenes ejecución TEST", "type": "main", "index": 0 } ] ] }, "Gemini - Analizar imágenes ejecución TEST": { "main": [ [ { "node": "Code - Limpiar análisis imágenes Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Limpiar análisis imágenes Gemini TEST": { "main": [ [ { "node": "IF - Tiene videos para análisis Gemini TEST", "type": "main", "index": 0 } ] ] }, "IF - Tiene videos para análisis Gemini TEST": { "main": [ [ { "node": "Code - Preparar videos para análisis Gemini TEST", "type": "main", "index": 0 } ], [ { "node": "Code - Continuar sin análisis videos TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar videos para análisis Gemini TEST": { "main": [ [ { "node": "Drive - Descargar videos para Gemini TEST", "type": "main", "index": 0 } ] ] }, "Drive - Descargar videos para Gemini TEST": { "main": [ [ { "node": "Gemini - Analizar videos ejecución TEST", "type": "main", "index": 0 } ] ] }, "Gemini - Analizar videos ejecución TEST": { "main": [ [ { "node": "Code - Limpiar análisis videos Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Limpiar análisis videos Gemini TEST": { "main": [ [ { "node": "Code - Preparar consolidado final TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar consolidado final TEST": { "main": [ [ { "node": "Code - Preparar prompt resumen final TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar prompt resumen final TEST": { "main": [ [ { "node": "Gemini - Generar resumen final TEST", "type": "main", "index": 0 } ] ] }, "Gemini - Generar resumen final TEST": { "main": [ [ { "node": "Code - Limpiar resumen final Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Limpiar resumen final Gemini TEST": { "main": [ [ { "node": "Code - Preparar presentación Slides TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar presentación Slides TEST": { "main": [ [ { "node": "Drive - Copiar template presentación TEST", "type": "main", "index": 0 } ] ] }, "Drive - Copiar template presentación TEST": { "main": [ [ { "node": "Code - Guardar datos presentación copiada TEST", "type": "main", "index": 0 } ] ] }, "Code - Guardar datos presentación copiada TEST": { "main": [ [ { "node": "HTTP Request - Reemplazar textos Slides TEST", "type": "main", "index": 0 } ] ] }, "HTTP Request - Reemplazar textos Slides TEST": { "main": [ [ { "node": "Code - Preparar fila propuestas ejecutadas TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar fila propuestas ejecutadas TEST": { "main": [ [ { "node": "Sheets - Guardar propuesta ejecutada TEST", "type": "main", "index": 0 } ] ] }, "Sheets - Guardar propuesta ejecutada TEST": { "main": [ [ { "node": "Code - Preparar actualización sesión final TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar actualización sesión final TEST": { "main": [ [ { "node": "Sheets - Actualizar sesión finalizada TEST", "type": "main", "index": 0 } ] ] }, "Sheets - Actualizar sesión finalizada TEST": { "main": [ [ { "node": "Code - Preparar mensaje final Telegram TEST", "type": "main", "index": 0 } ] ] }, "Code - Preparar mensaje final Telegram TEST": { "main": [ [ { "node": "Telegram - Enviar cierre final TEST", "type": "main", "index": 0 } ] ] }, "Telegram - Enviar aviso procesamiento TEST": { "main": [ [ { "node": "Code - Recuperar datos después aviso procesamiento TEST", "type": "main", "index": 0 } ] ] }, "Code - Recuperar datos después aviso procesamiento TEST": { "main": [ [ { "node": "Code - Preparar procesamiento final guiado", "type": "main", "index": 0 } ] ] }, "Code - Continuar sin videos Drive TEST": { "main": [ [ { "node": "IF - Tiene videos para análisis Gemini TEST", "type": "main", "index": 0 } ] ] }, "Code - Continuar sin análisis videos TEST": { "main": [ [ { "node": "Code - Preparar consolidado final TEST", "type": "main", "index": 0 } ] ] } }, "authors": "Isaac Aracena", "name": "Version 0447b997", "description": "", "autosaved": true, "workflowPublishHistory": [ { "createdAt": "2026-06-08T18:29:26.414Z", "id": 1206, "workflowId": "4eCPQcE3FDIll8bW", "versionId": "0447b997-2ad2-444c-a43a-d537e1f2d704", "event": "activated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" } ] } }