{ "name": "GLM Hub - Generar ícono con Gemini (Nodo nativo)", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "glm-hub-generar-icono", "responseMode": "responseNode", "options": { "allowedOrigins": "*" } }, "id": "47ec047e-250d-4056-a2af-2bb6c5f072b0", "name": "Webhook - Generar ícono", "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -1120, 0 ], "webhookId": "bc20a398-5ecf-42a2-9d17-9396aa278708" }, { "parameters": { "jsCode": "const request = $input.first().json ?? {};\nconst rawBody = request.body ?? request;\nlet body = rawBody;\n\nif (typeof rawBody === 'string') {\n try {\n body = JSON.parse(rawBody);\n } catch {\n body = {};\n }\n}\n\nconst headers = request.headers ?? {};\nconst headerAuthorization = String(headers.authorization ?? headers.Authorization ?? '').trim();\nconst accessToken = String(body.accessToken ?? '').trim();\nconst authorization = headerAuthorization.toLowerCase().startsWith('bearer ')\n ? headerAuthorization\n : accessToken\n ? `Bearer ${accessToken}`\n : '';\nconst name = String(body.name ?? '').trim();\nconst description = String(body.description ?? '').trim();\n\nlet error = '';\nif (!authorization.toLowerCase().startsWith('bearer ')) {\n error = 'La sesión de Supabase no fue enviada o no es válida.';\n} else if (name.length < 2 || name.length > 60) {\n error = 'El nombre debe contener entre 2 y 60 caracteres.';\n} else if (!description || description.length > 180) {\n error = 'La descripción debe contener entre 1 y 180 caracteres.';\n}\n\nreturn [{ json: { valid: !error, error, authorization, name, description } }];" }, "id": "934fb8be-c5bc-43bd-b44a-53f9db105b32", "name": "Validar solicitud", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -860, 0 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "308c498d-7c3f-4414-a21f-571276aee28b", "leftValue": "={{ $json.valid }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "3344b885-db2b-473d-a444-a51ca00d063d", "name": "¿Solicitud válida?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ -640, 0 ] }, { "parameters": { "respondWith": "json", "responseBody": "={{ { ok: false, error: $json.error } }}", "options": { "responseCode": 400, "responseHeaders": { "entries": [ { "name": "Cache-Control", "value": "no-store" } ] } } }, "id": "af555c88-6ec6-4e0f-9d01-64b6cb16a5c5", "name": "Responder 400", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [ -400, 180 ] }, { "parameters": { "method": "POST", "url": "={{ ($env.SUPABASE_URL || '').replace(/\\/$/, '') + '/rest/v1/rpc/glm_hub_icon_generation_access' }}", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "={{ $env.SUPABASE_ANON_KEY }}" }, { "name": "Authorization", "value": "={{ $('Validar solicitud').item.json.authorization }}" } ] }, "sendBody": true, "contentType": "raw", "rawContentType": "application/json", "body": "{}", "options": { "response": { "response": { "neverError": true, "responseFormat": "json" } }, "timeout": 30000 } }, "id": "42ba23d6-9f3a-4b67-ae48-8253adfb7110", "name": "Validar administrador en Supabase", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ -400, -80 ] }, { "parameters": { "jsCode": "const permission = $input.first().json ?? {};\nconst request = $('Validar solicitud').first().json;\nconst authorized = permission.authorized === true;\nreturn [{\n json: {\n authorized,\n error: authorized ? '' : 'Tu sesión no tiene permisos de administrador para generar íconos.',\n name: request.name,\n description: request.description,\n email: String(permission.email ?? '')\n }\n}];" }, "id": "489c0999-9ca3-4361-8262-18404f5aad35", "name": "Confirmar autorización", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -180, -80 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "c9275da4-0bca-4ce9-bd75-cc7d06e4cf56", "leftValue": "={{ $json.authorized }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "7c199474-f2b6-4c1c-873d-e578d1f7d500", "name": "¿Es administrador?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 40, -80 ] }, { "parameters": { "respondWith": "json", "responseBody": "={{ { ok: false, error: $json.error } }}", "options": { "responseCode": 403, "responseHeaders": { "entries": [ { "name": "Cache-Control", "value": "no-store" } ] } } }, "id": "1b105633-29f8-4e75-bead-d3022420dfed", "name": "Responder 403", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [ 280, 100 ] }, { "parameters": { "jsCode": "const { name, description } = $input.first().json;\nconst prompt = `Crea un ícono cuadrado 1:1, profesional, minimalista y fácil de reconocer para una aplicación interna de GomezLee Marketing.\n\nNombre de la aplicación: ${name}\nDescripción funcional: ${description}\n\nRequisitos visuales obligatorios:\n- Representa la función principal con un único símbolo simple, claro y reconocible.\n- Estética corporativa moderna, limpia y tecnológica.\n- Paleta inspirada en GLM: azul grisáceo, verde claro y verde azulado.\n- Fondo blanco o transparente.\n- Composición centrada, con buen espacio de respiración.\n- Sin texto, sin letras, sin siglas, sin números, sin marcas comerciales y sin logotipos existentes.\n- Sin mockups, sin fotografías, sin personas, sin sombras exageradas y sin fondos complejos.\n- Debe seguir siendo legible al reducirse a 32 x 32 píxeles.\n- Entrega solamente el ícono final.`;\nreturn [{ json: { prompt } }];" }, "id": "1ae24908-c67d-4742-917e-573b69f4ca19", "name": "Construir prompt del ícono", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 280, -180 ] }, { "parameters": { "resource": "image", "operation": "generate", "modelId": { "__rl": true, "value": "models/gemini-3-pro-image", "mode": "list", "cachedResultName": "models/gemini-3-pro-image (Nano Banana Pro)" }, "prompt": "={{ $json.prompt }}", "options": { "binaryPropertyOutput": "data" } }, "id": "bdeed7c1-71ba-4af4-9424-b248c06c9e75", "name": "Generate an image", "type": "@n8n/n8n-nodes-langchain.googleGemini", "typeVersion": 1.2, "position": [ 520, -180 ], "onError": "continueErrorOutput" }, { "parameters": { "respondWith": "binary", "responseDataSource": "set", "inputFieldName": "data", "options": { "responseCode": 200, "responseHeaders": { "entries": [ { "name": "Cache-Control", "value": "no-store" }, { "name": "Content-Disposition", "value": "inline; filename=\"glm-hub-icon.png\"" } ] } } }, "id": "7349fabd-6d25-48b1-94be-64e36eef5f7e", "name": "Responder con ícono", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [ 800, -260 ] }, { "parameters": { "jsCode": "const input = $input.first().json ?? {};\nconst rawError = input.error ?? input.message ?? input.description ?? input;\nlet message = 'Gemini no pudo generar el ícono. Revisa la credencial, el modelo y la cuota disponible.';\n\nif (typeof rawError === 'string' && rawError.trim()) {\n message = rawError.trim();\n} else if (rawError && typeof rawError === 'object') {\n message = String(rawError.message ?? rawError.description ?? rawError.status ?? message);\n}\n\nreturn [{ json: { ok: false, error: message } }];" }, "id": "fc7b61a0-fb00-42a4-bc4a-2e530da36a12", "name": "Preparar error de Gemini", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 800, -80 ] }, { "parameters": { "respondWith": "json", "responseBody": "={{ { ok: false, error: $json.error } }}", "options": { "responseCode": 502, "responseHeaders": { "entries": [ { "name": "Cache-Control", "value": "no-store" } ] } } }, "id": "c0bc7fee-a55e-4f40-90b5-f875c41b131c", "name": "Responder error de Gemini", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [ 1040, -80 ] }, { "parameters": { "content": "## CONFIGURACIÓN ÚNICA\n\nEn el nodo **Generate an image**, confirma la credencial existente **Isaac - Gemini Api Pago** después de importar.\n\nEl webhook devuelve la imagen directamente como archivo binario al GLM Hub.", "height": 220, "width": 380, "color": 5 }, "id": "fd8a87b4-3924-412b-8d3d-b7707bf6388b", "name": "Nota de configuración", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 420, 100 ] } ], "pinData": {}, "connections": { "Webhook - Generar ícono": { "main": [ [ { "node": "Validar solicitud", "type": "main", "index": 0 } ] ] }, "Validar solicitud": { "main": [ [ { "node": "¿Solicitud válida?", "type": "main", "index": 0 } ] ] }, "¿Solicitud válida?": { "main": [ [ { "node": "Validar administrador en Supabase", "type": "main", "index": 0 } ], [ { "node": "Responder 400", "type": "main", "index": 0 } ] ] }, "Validar administrador en Supabase": { "main": [ [ { "node": "Confirmar autorización", "type": "main", "index": 0 } ] ] }, "Confirmar autorización": { "main": [ [ { "node": "¿Es administrador?", "type": "main", "index": 0 } ] ] }, "¿Es administrador?": { "main": [ [ { "node": "Construir prompt del ícono", "type": "main", "index": 0 } ], [ { "node": "Responder 403", "type": "main", "index": 0 } ] ] }, "Construir prompt del ícono": { "main": [ [ { "node": "Generate an image", "type": "main", "index": 0 } ] ] }, "Generate an image": { "main": [ [ { "node": "Responder con ícono", "type": "main", "index": 0 } ], [ { "node": "Preparar error de Gemini", "type": "main", "index": 0 } ] ] }, "Preparar error de Gemini": { "main": [ [ { "node": "Responder error de Gemini", "type": "main", "index": 0 } ] ] } }, "active": false, "settings": { "executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner" }, "versionId": "ae79a50a-79d1-43f8-b856-b4106bae3e60", "meta": { "templateCredsSetupCompleted": false }, "tags": [] }