Files
pymesbot-infra/n8n_workflow_V5_LOOP_TELEGRAM.json

502 lines
22 KiB
JSON

{
"name": "PymesBot — Loop V5 con Telegram",
"nodes": [
{
"parameters": {
"rule": {
"interval": [{"field": "minutes", "minutesInterval": 5}]
}
},
"id": "cron-trigger",
"name": "⏰ Inicio Loop",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [0, 500]
},
{
"parameters": {
"url": "https://gitea.cbcren.online/api/v1/repos/renato97/pymesbot-infra/contents/tasks.json",
"authentication": "none",
"headerParameters": {
"parameters": [
{"name": "Authorization", "value": "token efeed2af00597883adb04da70bd6a7c2993ae92d"}
]
},
"options": {"timeout": 60000, "response": {"responseFormat": "json"}}
},
"id": "http-read-tasks",
"name": "📥 Leer tasks.json",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [220, 500]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=📥 tasks.json leído"}
]
},
"options": {"timeout": 60000}
},
"id": "tg-read-ok",
"name": "📢 TG: Leído",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [440, 580]
},
{
"parameters": {
"jsCode": "const response = $input.item.json;\n\n// La respuesta de Gitea tiene 'content' en base64\nlet content = response.content;\nif (typeof content === 'string') {\n content = Buffer.from(content, 'base64').toString('utf-8');\n}\n\nconst tasks = typeof content === 'string' ? JSON.parse(content) : content;\n\nreturn [{\n json: {\n tareas: tasks.tareas,\n key_pool: tasks.key_pool,\n config: tasks.config,\n meta: tasks.meta,\n file_sha: response.sha,\n raw_tasks: tasks\n }\n}];"
},
"id": "parse-tasks",
"name": "📝 Parsear JSON",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [660, 500]
},
{
"parameters": {
"jsCode": "const tareas = $input.item.json.tareas;\nconst key_pool = $input.item.json.key_pool;\nconst file_sha = $input.item.json.file_sha;\nconst raw_tasks = $input.item.json.raw_tasks;\n\nconst completadas = new Set(tareas.filter(t => t.status === 'done').map(t => t.id));\n\nconst candidatas = tareas\n .filter(t => {\n if (t.status !== 'pending') return false;\n if (t.intentos >= 3) return false;\n const depsPendientes = (t.dependencias || []).filter(dep => !completadas.has(dep));\n return depsPendientes.length === 0;\n })\n .sort((a, b) => {\n if (a.sprint !== b.sprint) return a.sprint - b.sprint;\n return a.prioridad - b.prioridad;\n });\n\nif (candidatas.length === 0) {\n const pendientes = tareas.filter(t => t.status === 'pending').length;\n return [{\n json: {\n hay_tarea: false,\n motivo: pendientes === 0 ? 'TODAS_COMPLETADAS' : 'DEPENDENCIAS_BLOQUEADAS',\n stats: { total: tareas.length, completadas: completadas.size, pendientes }\n }\n }];\n}\n\nconst tarea = candidatas[0];\n\nconst ahora = Date.now();\nconst keysDisponibles = key_pool\n .filter(k => k.disponible)\n .filter(k => {\n if (!k.ultimo_uso) return true;\n const elapsed = (ahora - new Date(k.ultimo_uso).getTime()) / 1000;\n return elapsed >= k.cooldown_segundos;\n })\n .sort((a, b) => {\n if (!a.ultimo_uso) return -1;\n if (!b.ultimo_uso) return 1;\n return new Date(a.ultimo_uso) - new Date(b.ultimo_uso);\n });\n\nlet keyElegida = keysDisponibles.find(k => k.servicio === tarea.modelo_preferido);\nif (!keyElegida) keyElegida = keysDisponibles[0];\n\nif (!keyElegida) {\n return [{\n json: {\n hay_tarea: false,\n motivo: 'TODAS_KEYS_EN_COOLDOWN',\n stats: { total: tareas.length, completadas: completadas.size }\n }\n }];\n}\n\nreturn [{\n json: {\n hay_tarea: true,\n tarea: tarea,\n key_elegida: keyElegida,\n tareas_all: tareas,\n key_pool: key_pool,\n file_sha: file_sha,\n raw_tasks: raw_tasks\n }\n}];"
},
"id": "select-task",
"name": "🎯 Seleccionar tarea",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [880, 500]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=🎯 Tarea: {{ $json.hay_tarea ? $json.tarea.id + ' - ' + $json.tarea.titulo : 'NO disponible (' + $json.motivo + ')' }}"}
]
},
"options": {"timeout": 60000}
},
"id": "tg-tarea-selected",
"name": "📢 TG: Tarea",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1100, 580]
},
{
"parameters": {
"conditions": {
"options": {"caseSensitive": true},
"conditions": [
{
"id": "check-tarea",
"leftValue": "={{ $json.hay_tarea }}",
"rightValue": true,
"operator": {"type": "boolean", "operation": "equals"}
}
],
"combinator": "and"
}
},
"id": "if-hay-tarea",
"name": "¿Hay tarea?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [1320, 500]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=⏸️ Esperando... {{ $json.motivo }}. Total: {{ $json.stats.total }} | Hechas: {{ $json.stats.completadas }}"}
]
},
"options": {"timeout": 60000}
},
"id": "tg-no-tarea",
"name": "📢 TG: Espera",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1540, 650]
},
{
"parameters": {
"jsCode": "// Esperar próximo ciclo\nreturn [{ json: { fin_ciclo: true, motivo: 'sin_tarea' } }];"
},
"id": "wait-next",
"name": "⏳ Esperar 5min",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1760, 650]
},
{
"parameters": {
"url": "={{ $('🎯 Seleccionar tarea').item.json.tarea.proyecto === 'pymesbot-installer' ? 'https://gitea.cbcren.online/api/v1/repos/renato97/pymesbot-infra/contents/specs/02_PYMESBOT_INSTALLER_SPEC.md' : 'https://gitea.cbcren.online/api/v1/repos/renato97/pymesbot-infra/contents/specs/01_PYMESBOT_PROJECT_SPEC.md' }}",
"authentication": "none",
"headerParameters": {
"parameters": [
{"name": "Authorization", "value": "token efeed2af00597883adb04da70bd6a7c2993ae92d"}
]
},
"options": {"timeout": 60000}
},
"id": "http-read-spec",
"name": "📖 Leer spec",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1540, 300]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=📖 Spec leído"}
]
},
"options": {"timeout": 60000}
},
"id": "tg-spec-ok",
"name": "📢 TG: Spec",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1760, 380]
},
{
"parameters": {
"jsCode": "const tarea = $('🎯 Seleccionar tarea').item.json.tarea;\nconst specResponse = $input.item.json;\nconst specContent = Buffer.from(specResponse.content, 'base64').toString('utf-8');\n\nconst archivoPrincipal = (tarea.archivos_a_crear && tarea.archivos_a_crear.length > 0)\n ? tarea.archivos_a_crear[0]\n : tarea.archivo_destino;\n\nconst prompt = `Sos un desarrollador Python/HTML/JS senior. Implementa código de producción limpio y completo.\n\n## TAREA\nID: ${tarea.id}\nTítulo: ${tarea.titulo}\nDescripción: ${tarea.descripcion}\n\n## ARCHIVO\n${archivoPrincipal}\n\n## CONTEXTO DEL SPEC\n${specContent}\n\n## REGLAS\n1. Respondé SÓLO con código. Sin markdown, sin explicaciones.\n2. Python 3.11+ válido con type hints.\n3. Comentarios en español.\n4. Código completo y funcional.\n\nCÓDIGO:`;\n\nconst GLM_KEY_1 = '6fef8efda3d24eb9ad3d718daf1ae9a1.RcFc7QPe5uZLr2mS';\nconst MINIMAX_KEY_1 = 'sk-cp-XC8cbgbVBuv1g8mMcao0ABeZu_rGEN_S22EhBUqo4lJbY_UJVqUVO5XF8hVobp8gE_39JbgQggr00TQwNdV9vP458Y_MBC_8GstvzmwhuukEGY4a2I5_L6A';\n\nconst API_CONFIGS = {\n glm: { url: 'https://api.z.ai/api/paas/v4/chat/completions', model: 'glm-4.7', key: GLM_KEY_1 },\n minimax: { url: 'https://api.minimax.io/v1/chat/completions', model: 'MiniMax-M2.5', key: MINIMAX_KEY_1 }\n};\n\nconst key_elegida = $('🎯 Seleccionar tarea').item.json.key_elegida;\nconst apiConfig = API_CONFIGS[key_elegida.servicio];\n\nreturn [{\n json: {\n ...$('🎯 Seleccionar tarea').item.json,\n spec_content: specContent,\n prompt: prompt,\n archivo_objetivo: archivoPrincipal,\n api_url: apiConfig.url,\n api_model: apiConfig.model,\n api_key: apiConfig.key\n }\n}];"
},
"id": "build-prompt",
"name": "🔨 Build prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1980, 300]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=🤖 Llamando a {{ $('🎯 Seleccionar tarea').item.json.key_elegida.servicio }} ({{ $json.api_model }})"}
]
},
"options": {"timeout": 60000}
},
"id": "tg-ai-call",
"name": "📢 TG: Llamando IA",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [2200, 380]
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.api_url }}",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "model", "value": "={{ $json.api_model }}"},
{"name": "messages", "value": "={{ [{\"role\": \"system\", \"content\": \"Sos un desarrollador senior. Respondés SOLO con código. Sin explicaciones. Sin markdown.\"}, {\"role\": \"user\", \"content\": $json.prompt}] }}"},
{"name": "max_tokens", "value": "4000"},
{"name": "temperature", "value": "0.1"}
]
},
"headerParameters": {
"parameters": [
{"name": "Authorization", "value": "=Bearer {{ $json.api_key }}"},
{"name": "Content-Type", "value": "application/json"}
]
},
"options": {"timeout": 60000}
},
"id": "http-call-ai",
"name": "🤖 Llamar IA",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [2420, 300]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=✅ Código recibido ({{ $json.choices[0].message.content.length }} chars)"}
]
},
"options": {"timeout": 60000}
},
"id": "tg-ai-response",
"name": "📢 TG: Respuesta IA",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [2640, 380]
},
{
"parameters": {
"jsCode": "const response = $input.item.json;\nlet codigo = response.choices?.[0]?.message?.content || '';\ncodigo = codigo.replace(/^```[a-z]*\\n?/gm, '').replace(/^```\\n?/gm, '').trim();\n\nreturn [{\n json: {\n ...$('🔨 Build prompt').item.json,\n codigo_generado: codigo\n }\n}];"
},
"id": "parse-code",
"name": "📝 Extraer código",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [2860, 300]
},
{
"parameters": {
"conditions": {
"options": {"caseSensitive": true},
"conditions": [
{
"id": "check-codigo",
"leftValue": "={{ $json.codigo_generado }}",
"rightValue": "",
"operator": {"type": "string", "operation": "notEmpty"}
}
],
"combinator": "and"
}
},
"id": "if-codigo-ok",
"name": "¿Código OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [3080, 300]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=❌ Error: código vacío para {{ $('🎯 Seleccionar tarea').item.json.tarea.id }}"}
]
},
"options": {"timeout": 60000}
},
"id": "tg-error-no-code",
"name": "📢 TG: Error código",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [3300, 400]
},
{
"parameters": {
"method": "PUT",
"url": "={{ 'https://gitea.cbcren.online/api/v1/repos/renato97/' + $('🎯 Seleccionar tarea').item.json.tarea.proyecto + '/contents/' + $('📝 Extraer código').item.json.archivo_objetivo }}",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "message", "value": "= [BOT] {{ $('🎯 Seleccionar tarea').item.json.tarea.id }} — {{ $('🎯 Seleccionar tarea').item.json.tarea.titulo }}"},
{"name": "content", "value": "={{ Buffer.from($('📝 Extraer código').item.json.codigo_generado).toString('base64') }}"},
{"name": "branch", "value": "main"}
]
},
"headerParameters": {
"parameters": [
{"name": "Authorization", "value": "token efeed2af00597883adb04da70bd6a7c2993ae92d"}
]
},
"options": {"timeout": 60000}
},
"id": "http-commit",
"name": "📦 Commit a Gitea",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [3300, 200]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=📦 Commit OK: {{ $('📝 Extraer código').item.json.archivo_objetivo }}"}
]
},
"options": {"timeout": 60000}
},
"id": "tg-commit-ok",
"name": "📢 TG: Commit",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [3520, 280]
},
{
"parameters": {
"jsCode": "const tareas = $('🎯 Seleccionar tarea').item.json.tareas_all;\nconst key_pool = $('🎯 Seleccionar tarea').item.json.key_pool;\nconst tarea = $('🎯 Seleccionar tarea').item.json.tarea;\nconst file_sha = $('🎯 Seleccionar tarea').item.json.file_sha;\nconst raw_tasks = $('🎯 Seleccionar tarea').item.json.raw_tasks;\nconst key_usada = $('🎯 Seleccionar tarea').item.json.key_elegida.id;\n\nconst tareas_actualizadas = tareas.map(t => {\n if (t.id !== tarea.id) return t;\n return { ...t, status: 'done', intentos: t.intentos + 1, modelo_usado: key_usada, completada_el: new Date().toISOString() };\n});\n\nconst key_pool_actualizado = key_pool.map(k => {\n if (k.id !== key_usada) return k;\n return { ...k, ultimo_uso: new Date().toISOString(), usos_hoy: (k.usos_hoy || 0) + 1 };\n});\n\nconst nuevo_tasks_json = { ...raw_tasks, tareas: tareas_actualizadas, key_pool: key_pool_actualizado };\nconst contenidoBase64 = Buffer.from(JSON.stringify(nuevo_tasks_json, null, 2)).toString('base64');\n\nreturn [{\n json: {\n url: 'https://gitea.cbcren.online/api/v1/repos/renato97/pymesbot-infra/contents/tasks.json',\n method: 'PUT',\n body: {\n message: `[BOT] ${tarea.id} marcada como done`,\n content: contenidoBase64,\n sha: file_sha,\n branch: 'main'\n },\n tarea_id: tarea.id\n }\n}];"
},
"id": "build-update",
"name": "🔨 Preparar update",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3740, 200]
},
{
"parameters": {
"method": "PUT",
"url": "={{ $json.url }}",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "body", "value": "={{ $json.body }}"}
]
},
"headerParameters": {
"parameters": [
{"name": "Authorization", "value": "token efeed2af00597883adb04da70bd6a7c2993ae92d"},
{"name": "Content-Type", "value": "application/json"}
]
},
"options": {"timeout": 60000}
},
"id": "http-update-tasks",
"name": "✍️ Update tasks.json",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [3960, 200]
},
{
"parameters": {
"url": "https://api.telegram.org/bot8551922819:AAHIXNbavzcI90eEIGVx1NIisYHecfcBYtU/sendMessage",
"authentication": "none",
"sendBody": true,
"bodyParameters": {
"parameters": [
{"name": "chat_id", "value": "692714536"},
{"name": "text", "value": "=🔄 Tarea {{ $('🔨 Preparar update').item.json.tarea_id }} completada. Reiniciando loop..."}
]
},
"options": {"timeout": 60000}
},
"id": "tg-success-loop",
"name": "📢 TG: Fin loop",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [4180, 280]
},
{
"parameters": {
"jsCode": "// Pausa antes de reiniciar\nreturn [{ json: { loop_restart: true } }];"
},
"id": "loop-restart",
"name": "🔄 Reiniciar",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [4400, 280]
}
],
"connections": {
"⏰ Inicio Loop": {
"main": [[{"node": "📥 Leer tasks.json", "type": "main", "index": 0}]]
},
"📥 Leer tasks.json": {
"main": [
[{"node": "📢 TG: Leído", "type": "main", "index": 0}]
]
},
"📢 TG: Leído": {
"main": [[{"node": "📝 Parsear JSON", "type": "main", "index": 0}]]
},
"📝 Parsear JSON": {
"main": [[{"node": "🎯 Seleccionar tarea", "type": "main", "index": 0}]]
},
"🎯 Seleccionar tarea": {
"main": [[{"node": "📢 TG: Tarea", "type": "main", "index": 0}]]
},
"📢 TG: Tarea": {
"main": [[{"node": "¿Hay tarea?", "type": "main", "index": 0}]]
},
"¿Hay tarea?": {
"main": [
[{"node": "📖 Leer spec", "type": "main", "index": 0}],
[{"node": "📢 TG: Espera", "type": "main", "index": 0}]
]
},
"📢 TG: Espera": {
"main": [[{"node": "⏳ Esperar 5min", "type": "main", "index": 0}]]
},
"⏳ Esperar 5min": {
"main": [[{"node": "🔄 Reiniciar", "type": "main", "index": 0}]]
},
"📖 Leer spec": {
"main": [[{"node": "📢 TG: Spec", "type": "main", "index": 0}]]
},
"📢 TG: Spec": {
"main": [[{"node": "🔨 Build prompt", "type": "main", "index": 0}]]
},
"🔨 Build prompt": {
"main": [[{"node": "📢 TG: Llamando IA", "type": "main", "index": 0}]]
},
"📢 TG: Llamando IA": {
"main": [[{"node": "🤖 Llamar IA", "type": "main", "index": 0}]]
},
"🤖 Llamar IA": {
"main": [[{"node": "📢 TG: Respuesta IA", "type": "main", "index": 0}]]
},
"📢 TG: Respuesta IA": {
"main": [[{"node": "📝 Extraer código", "type": "main", "index": 0}]]
},
"📝 Extraer código": {
"main": [[{"node": "¿Código OK?", "type": "main", "index": 0}]]
},
"¿Código OK?": {
"main": [
[{"node": "📦 Commit a Gitea", "type": "main", "index": 0}],
[{"node": "📢 TG: Error código", "type": "main", "index": 0}]
]
},
"📢 TG: Error código": {
"main": [[{"node": "🔄 Reiniciar", "type": "main", "index": 0}]]
},
"📦 Commit a Gitea": {
"main": [[{"node": "📢 TG: Commit", "type": "main", "index": 0}]]
},
"📢 TG: Commit": {
"main": [[{"node": "🔨 Preparar update", "type": "main", "index": 0}]]
},
"🔨 Preparar update": {
"main": [[{"node": "✍️ Update tasks.json", "type": "main", "index": 0}]]
},
"✍️ Update tasks.json": {
"main": [[{"node": "📢 TG: Fin loop", "type": "main", "index": 0}]]
},
"📢 TG: Fin loop": {
"main": [[{"node": "🔄 Reiniciar", "type": "main", "index": 0}]]
},
"🔄 Reiniciar": {
"main": [[{"node": "⏰ Inicio Loop", "type": "main", "index": 0}]]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": ["pymesbot"],
"meta": {"instanceId": "pymesbot-v5-loop"}
}