{
  "name": "Cola de aprobación humana (plano de Saphia Labs)",
  "nodes": [
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "id": "t",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Entrada esperada: { flujo, resumen, salida, monto, destinoExterno, clienteSensible, confianza }\n// Umbral de riesgo: ajusta estas reglas con quien responde por el proceso\nconst j = $input.first().json;\nconst motivos = [];\nif ((j.monto || 0) > 500000) motivos.push('monto > 500.000');\nif (j.destinoExterno) motivos.push('sale de la organización');\nif (j.clienteSensible) motivos.push('cliente sensible');\nif ((j.confianza || 'alta') === 'baja') motivos.push('confianza baja del modelo');\nreturn [{ json: { ...j, requiereAprobacion: motivos.length > 0, motivoRevision: motivos.join('; ') } }];"
      },
      "id": "ev",
      "name": "Code: evaluar riesgo",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c",
              "leftValue": "={{ $json.requiereAprobacion }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if",
      "name": "IF: requiere aprobación",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        520,
        0
      ]
    },
    {
      "parameters": {
        "operation": "sendAndWait",
        "chatId": "TU_CHAT_ID",
        "message": "=⏸ Aprobación pendiente · {{ $json.flujo }}\nMotivo: {{ $json.motivoRevision }}\n\n{{ $json.resumen }}\n\n— Salida propuesta —\n{{ String($json.salida).slice(0, 1500) }}",
        "responseType": "approval",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "approveLabel": "✅ Aprobar",
            "disapproveLabel": "❌ Rechazar"
          }
        },
        "options": {
          "limitWaitTime": {
            "values": {
              "limitType": "afterTimeInterval",
              "resumeAmount": 4,
              "resumeUnit": "hours"
            }
          },
          "appendAttribution": false
        }
      },
      "id": "tgw",
      "name": "Telegram: enviar y esperar aprobación",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        780,
        -100
      ],
      "webhookId": "saphia-cola-aprobacion-1"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "a",
              "leftValue": "={{ $json.approved }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "ifa",
      "name": "IF: aprobado",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1040,
        -100
      ]
    },
    {
      "parameters": {
        "chatId": "TU_CHAT_ID",
        "text": "=✅ Aprobado. Se ejecuta: {{ $('Code: evaluar riesgo').item.json.resumen }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "ok",
      "name": "Telegram: confirmar ejecución",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1300,
        -200
      ]
    },
    {
      "parameters": {
        "chatId": "TU_CHAT_ID",
        "text": "=❌ Rechazado y registrado: {{ $('Code: evaluar riesgo').item.json.resumen }}\nResponde a este mensaje con el motivo para ajustar el flujo.",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "no",
      "name": "Telegram: confirmar rechazo",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1300,
        0
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "TU_SPREADSHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "aprobaciones"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.toISO() }}",
            "flujo": "={{ $('Code: evaluar riesgo').item.json.flujo }}",
            "resumen": "={{ $('Code: evaluar riesgo').item.json.resumen }}",
            "monto": "={{ $('Code: evaluar riesgo').item.json.monto }}",
            "motivo_revision": "={{ $('Code: evaluar riesgo').item.json.motivoRevision }}",
            "decision": "={{ $('Telegram: enviar y esperar aprobación').item.json.approved ? 'aprobado' : 'rechazado' }}",
            "quien": "={{ $('Telegram: enviar y esperar aprobación').item.json.approved !== undefined ? 'Telegram' : '' }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "fecha",
              "displayName": "fecha",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "flujo",
              "displayName": "flujo",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "resumen",
              "displayName": "resumen",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "monto",
              "displayName": "monto",
              "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": "decision",
              "displayName": "decision",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "quien",
              "displayName": "quien",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "log",
      "name": "Sheets: registrar decisión",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1560,
        -100
      ]
    },
    {
      "parameters": {
        "jsCode": "// Bajo el umbral: sigue sin parar. Devuelve la salida tal cual para que el flujo que llamó continúe.\nreturn [{ json: { ...$json, decision: 'automatico' } }];"
      },
      "id": "auto",
      "name": "Code: sigue sin aprobación",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        120
      ]
    }
  ],
  "connections": {
    "Execute Workflow Trigger": {
      "main": [
        [
          {
            "node": "Code: evaluar riesgo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: evaluar riesgo": {
      "main": [
        [
          {
            "node": "IF: requiere aprobación",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: requiere aprobación": {
      "main": [
        [
          {
            "node": "Telegram: enviar y esperar aprobación",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code: sigue sin aprobación",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram: enviar y esperar aprobación": {
      "main": [
        [
          {
            "node": "IF: aprobado",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: aprobado": {
      "main": [
        [
          {
            "node": "Telegram: confirmar ejecución",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram: confirmar rechazo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram: confirmar ejecución": {
      "main": [
        [
          {
            "node": "Sheets: registrar decisión",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram: confirmar rechazo": {
      "main": [
        [
          {
            "node": "Sheets: registrar decisión",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "America/Bogota"
  },
  "meta": {
    "templateCredsSetupCompleted": false,
    "probadoEn": "n8n 2.41.6, octubre 2026",
    "fuente": "https://saphialabs.com/recursos/flujos/#f10",
    "preparacion": "Es un subflujo: llámalo desde cualquier flujo con IA con el nodo Execute Workflow (modo passthrough) pasando { flujo, resumen, salida, monto, destinoExterno, clienteSensible, confianza }. Crea una pestaña \"aprobaciones\" con encabezado fecha, flujo, resumen, monto, motivo_revision, decision, quien. Telegram rechaza botones con enlaces a localhost: define WEBHOOK_URL con una URL pública (n8n Cloud, tu servidor o un túnel). Reemplaza TU_CHAT_ID y TU_SPREADSHEET_ID."
  }
}