{
  "name": "Logixb2b - Auditor tasa respuesta outbound (preflight)",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Manual → Config fila outbound → Validar → riskScore 0-100 + semaforo → Slack alerta si bloqueo → Sheets Auditor_Respuesta_Outbound + Historial (o skip demo) → Slack OK → resumen local. Europe/Madrid. REEMPLAZA_*. Sin SMTP. Sin LangChain. Demo completa sin credenciales."
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "active": false,
  "pinData": {},
  "tags": [
    {
      "name": "Logixb2b"
    },
    {
      "name": "recursos"
    },
    {
      "name": "outbound"
    },
    {
      "name": "auditor"
    }
  ],
  "nodes": [
    {
      "id": "ar-sticky",
      "name": "INSTRUCCIONES (conectar + VPS)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -920,
        -40
      ],
      "parameters": {
        "content": "## INSTRUCCIONES - Auditor tasa respuesta outbound\n\n**Recurso:** `/recursos/auditor-tasa-respuesta-outbound-n8n`\n**Guia:** /errores-tasa-respuesta-outbound-automatizado\n**Zona:** Europe/Madrid · active: false al importar.\n\n### 1) Importar\nn8n → ⋯ → **Import from File** → este JSON (no pegues texto).\n\n### 2) Demo YA (sin credenciales)\nEjecuta **Manual Trigger**. Con REEMPLAZA_* el flujo pone skipSheets/skipSlack y termina en **Resumen final local** con riskScore, riskLevel, killerFlags y allowSend.\n\n### 3) Produccion\n1. Google Sheets OAuth + pestanas **Auditor_Respuesta_Outbound** y **Auditor_Respuesta_Historial**.\n2. Sustituye sheetsId y slackWebhookUrl (quita REEMPLAZA).\n3. Cablea Form Trigger o Schedule (en lienzo, desconectados) al Config.\n4. NO envia SMTP: solo audita la fila ANTES del send.\n\n### 4) Que resuelve\nDetecta killers de tasa de respuesta (consentimiento, personalizacion, opt-out, delay, volumen vs techo, asunto largo, lista no verificada, HITL) y semaforo verde/ambar/rojo.\n\n### 5) VPS UE\nDocker + Postgres + HTTPS + GENERIC_TIMEZONE=Europe/Madrid.\n\n### 6) RGPD / AI Act (deployer)\nMinimiza PII en Sheets; logs n8n; human-in-the-loop (hitlApproved). Informativo, no asesoramiento legal.\n/uso-responsable-de-ia-y-transparencia\n",
        "height": 1680,
        "width": 700,
        "color": 4
      }
    },
    {
      "id": "ar-sticky-cols",
      "name": "COLUMNAS SHEETS",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -920,
        1320
      ],
      "parameters": {
        "content": "## Auditor_Respuesta_Outbound\nfecha | batchLabel | runSource | channel | riskScore | riskLevel | allowSend | killerFlagsText | consentOptIn | hasPersonalizationToken | hasOptOutLine | delayHoursSinceLastTouch | dailyVolume | dailyCap | subjectLength | bodyLength | listVerified | hitlApproved | recommendation | executiveBrief | complianceFlag\n\n## Auditor_Respuesta_Historial\nfecha | batchLabel | runSource | channel | riskScore | riskLevel | allowSend | killerFlagsText",
        "height": 420,
        "width": 700,
        "color": 5
      }
    },
    {
      "id": "ar-trigger",
      "name": "Manual Trigger auditor",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        420
      ],
      "parameters": {}
    },
    {
      "id": "ar-form",
      "name": "Form Trigger (produccion - cablear)",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.2,
      "position": [
        0,
        680
      ],
      "parameters": {
        "formTitle": "Auditor tasa respuesta outbound",
        "formDescription": "Audita una fila de Lead/mensaje ANTES del envio. Europe/Madrid. Sin SMTP.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "batchLabel",
              "fieldType": "text",
              "requiredField": true
            },
            {
              "fieldLabel": "channel",
              "fieldType": "text",
              "requiredField": true
            },
            {
              "fieldLabel": "consentOptIn",
              "fieldType": "text",
              "requiredField": true
            },
            {
              "fieldLabel": "hasPersonalizationToken",
              "fieldType": "text",
              "requiredField": true
            },
            {
              "fieldLabel": "hasOptOutLine",
              "fieldType": "text",
              "requiredField": true
            },
            {
              "fieldLabel": "delayHoursSinceLastTouch",
              "fieldType": "number",
              "requiredField": true
            },
            {
              "fieldLabel": "dailyVolume",
              "fieldType": "number",
              "requiredField": true
            },
            {
              "fieldLabel": "dailyCap",
              "fieldType": "number",
              "requiredField": true
            },
            {
              "fieldLabel": "subjectLength",
              "fieldType": "number",
              "requiredField": true
            },
            {
              "fieldLabel": "bodyLength",
              "fieldType": "number",
              "requiredField": true
            },
            {
              "fieldLabel": "listVerified",
              "fieldType": "text",
              "requiredField": true
            },
            {
              "fieldLabel": "hitlApproved",
              "fieldType": "text",
              "requiredField": true
            }
          ]
        },
        "responseMode": "onReceived",
        "options": {}
      }
    },
    {
      "id": "ar-schedule",
      "name": "Schedule diario (opcional - cablear)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        900
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 8
            }
          ]
        }
      }
    },
    {
      "id": "ar-config",
      "name": "Config fila outbound",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        280,
        420
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "includeOtherFields": false,
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "batchLabel",
              "value": "auditor-respuesta-demo-2026-08-11",
              "type": "string"
            },
            {
              "id": "a2",
              "name": "demoMode",
              "value": "true",
              "type": "string"
            },
            {
              "id": "a3",
              "name": "skipSheets",
              "value": "false",
              "type": "string"
            },
            {
              "id": "a4",
              "name": "skipSlack",
              "value": "false",
              "type": "string"
            },
            {
              "id": "a5",
              "name": "consentOptIn",
              "value": "true",
              "type": "string"
            },
            {
              "id": "a6",
              "name": "hasPersonalizationToken",
              "value": "true",
              "type": "string"
            },
            {
              "id": "a7",
              "name": "hasOptOutLine",
              "value": "true",
              "type": "string"
            },
            {
              "id": "a8",
              "name": "delayHoursSinceLastTouch",
              "value": "72",
              "type": "string"
            },
            {
              "id": "a9",
              "name": "dailyVolume",
              "value": "35",
              "type": "string"
            },
            {
              "id": "a10",
              "name": "dailyCap",
              "value": "50",
              "type": "string"
            },
            {
              "id": "a11",
              "name": "subjectLength",
              "value": "48",
              "type": "string"
            },
            {
              "id": "a12",
              "name": "bodyLength",
              "value": "420",
              "type": "string"
            },
            {
              "id": "a13",
              "name": "channel",
              "value": "email",
              "type": "string"
            },
            {
              "id": "a14",
              "name": "listVerified",
              "value": "true",
              "type": "string"
            },
            {
              "id": "a15",
              "name": "hitlApproved",
              "value": "true",
              "type": "string"
            },
            {
              "id": "a16",
              "name": "sheetsId",
              "value": "REEMPLAZA_SHEETS_ID",
              "type": "string"
            },
            {
              "id": "a17",
              "name": "slackWebhookUrl",
              "value": "https://hooks.example.com/services/REEMPLAZA_SLACK",
              "type": "string"
            },
            {
              "id": "a18",
              "name": "timezone",
              "value": "Europe/Madrid",
              "type": "string"
            },
            {
              "id": "a19",
              "name": "runSource",
              "value": "manual",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "ar-validate",
      "name": "Validar Config",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        540,
        420
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const c = $input.first().json;\nconst n = (k) => {\n  const v = Number(String(c[k] ?? '').replace(',', '.').trim());\n  return Number.isFinite(v) ? v : NaN;\n};\nconst errors = [];\nif (!c.batchLabel || String(c.batchLabel).trim().length < 3) errors.push('batchLabel corto');\nconst channel = String(c.channel || '').trim().toLowerCase();\nif (!['email', 'linkedin', 'whatsapp'].includes(channel)) {\n  errors.push('channel debe ser email|linkedin|whatsapp');\n}\nconst numericKeys = [\n  'delayHoursSinceLastTouch',\n  'dailyVolume',\n  'dailyCap',\n  'subjectLength',\n  'bodyLength'\n];\nfor (const k of numericKeys) {\n  const v = n(k);\n  if (!Number.isFinite(v) || v < 0) errors.push(k + ' invalido');\n}\nif (n('dailyCap') === 0) errors.push('dailyCap debe ser > 0');\nconst sheetsPlaceholder = String(c.sheetsId || '').includes('REEMPLAZA');\nconst slackPlaceholder = String(c.slackWebhookUrl || '').includes('REEMPLAZA');\nconst skipSheets = String(c.skipSheets || 'false') === 'true' || sheetsPlaceholder;\nconst skipSlack = String(c.skipSlack || 'false') === 'true' || slackPlaceholder;\nreturn [{\n  json: {\n    ...c,\n    channel,\n    configValid: errors.length === 0,\n    configErrors: errors.join('; ') || '',\n    skipSheets,\n    skipSlack,\n    runSource: c.runSource || 'manual',\n    timezone: c.timezone || 'Europe/Madrid'\n  }\n}];"
      }
    },
    {
      "id": "ar-if-valid",
      "name": "IF config valida",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        780,
        420
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "ar-if-valid-c",
              "leftValue": "={{ $json.configValid }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ar-slack-err",
      "name": "Slack config invalida",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1020,
        640
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"Logixb2b auditor respuesta ERROR config · {{ $json.batchLabel }} · {{ $json.configErrors }} · Europe/Madrid\"\n}",
        "options": {}
      },
      "continueOnFail": true
    },
    {
      "id": "ar-stop",
      "name": "Stop (config invalida)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1260,
        640
      ],
      "parameters": {}
    },
    {
      "id": "ar-calc",
      "name": "Calcular riskScore",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1020,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const c = $('Validar Config').first().json;\nconst n = (k) => {\n  const v = Number(String(c[k] ?? '').replace(',', '.').trim());\n  return Number.isFinite(v) ? v : 0;\n};\nconst bool = (k) => {\n  const v = String(c[k] ?? '').trim().toLowerCase();\n  return v === 'true' || v === '1' || v === 'yes' || v === 'si';\n};\nconst channel = String(c.channel || 'email').toLowerCase();\nconst consentOptIn = bool('consentOptIn');\nconst hasPersonalizationToken = bool('hasPersonalizationToken');\nconst hasOptOutLine = bool('hasOptOutLine');\nconst listVerified = bool('listVerified');\nconst hitlApproved = bool('hitlApproved');\nconst delayHours = n('delayHoursSinceLastTouch');\nconst dailyVolume = n('dailyVolume');\nconst dailyCap = Math.max(1, n('dailyCap'));\nconst subjectLength = n('subjectLength');\nconst bodyLength = n('bodyLength');\n\nconst minDelayByChannel = { email: 48, linkedin: 72, whatsapp: 24 };\nconst maxSubjectByChannel = { email: 60, linkedin: 80, whatsapp: 40 };\nconst minDelay = minDelayByChannel[channel] ?? 48;\nconst maxSubject = maxSubjectByChannel[channel] ?? 60;\n\nconst killerFlags = [];\nlet score = 0;\n\nif (!consentOptIn) {\n  score += 25;\n  killerFlags.push('no_consent');\n}\nif (!hasPersonalizationToken) {\n  score += 12;\n  killerFlags.push('no_personalization');\n}\nif (!hasOptOutLine) {\n  score += 15;\n  killerFlags.push('no_optout');\n}\nif (delayHours < minDelay) {\n  score += 10;\n  killerFlags.push('delay_too_short');\n}\nif (dailyVolume > dailyCap) {\n  score += 12;\n  killerFlags.push('volume_over_cap');\n}\nif (subjectLength > maxSubject) {\n  score += 8;\n  killerFlags.push('subject_too_long');\n}\nif (!listVerified) {\n  score += 10;\n  killerFlags.push('list_not_verified');\n}\nif (!hitlApproved) {\n  score += 8;\n  killerFlags.push('hitl_false');\n}\n\nconst riskScore = Math.min(100, Math.max(0, score));\nconst riskLevel = riskScore < 25 ? 'verde' : riskScore < 55 ? 'ambar' : 'rojo';\nconst allowSend =\n  riskLevel !== 'rojo' &&\n  consentOptIn &&\n  !killerFlags.includes('no_consent');\n\nlet recommendation;\nif (!consentOptIn) {\n  recommendation =\n    'BLOQUEO: sin consentOptIn no envies. Documenta base legal RGPD antes de cualquier toque.';\n} else if (riskLevel === 'rojo') {\n  recommendation =\n    'Pausa el lote. Corrige ' +\n    killerFlags.slice(0, 3).join(', ') +\n    ' y vuelve a auditar antes de Active.';\n} else if (riskLevel === 'ambar') {\n  recommendation =\n    'Envio condicionado: reduce volumen o alarga delay, revisa personalizacion/opt-out y confirma HITL.';\n} else {\n  recommendation =\n    'Semaforo verde: permite envio del lote con trazabilidad Sheets y techo diario activo.';\n}\n\nconst executiveBrief =\n  'Auditor respuesta outbound · score ' +\n  riskScore +\n  ' · nivel ' +\n  riskLevel +\n  ' · allowSend=' +\n  allowSend +\n  ' · canal ' +\n  channel +\n  ' · flags [' +\n  (killerFlags.join('|') || 'ninguno') +\n  '] · ' +\n  recommendation;\n\nreturn [{\n  json: {\n    batchLabel: c.batchLabel,\n    runSource: c.runSource || 'manual',\n    demoMode: String(c.demoMode) === 'true',\n    skipSheets: !!c.skipSheets,\n    skipSlack: !!c.skipSlack,\n    channel,\n    consentOptIn,\n    hasPersonalizationToken,\n    hasOptOutLine,\n    delayHoursSinceLastTouch: delayHours,\n    dailyVolume,\n    dailyCap,\n    subjectLength,\n    bodyLength,\n    listVerified,\n    hitlApproved,\n    minDelayHours: minDelay,\n    maxSubjectLength: maxSubject,\n    riskScore,\n    riskLevel,\n    killerFlags,\n    killerFlagsText: killerFlags.join('|') || 'ninguno',\n    allowSend,\n    recommendation,\n    executiveBrief,\n    sheetsId: c.sheetsId,\n    slackWebhookUrl: c.slackWebhookUrl,\n    timezone: c.timezone || 'Europe/Madrid',\n    complianceFlag: 'rgpd_preflight_outbound_sin_envio_smtp',\n    _generatedAt: new Date().toISOString()\n  }\n}];"
      }
    },
    {
      "id": "ar-if-allow",
      "name": "IF allowSend",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1260,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "ar-if-allow-c",
              "leftValue": "={{ $json.allowSend }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ar-pass-ok",
      "name": "Passthrough allowSend OK",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1500,
        200
      ],
      "parameters": {}
    },
    {
      "id": "ar-if-slack-block",
      "name": "IF avisar Slack bloqueo",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1500,
        420
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "ar-if-slack-block-c",
              "leftValue": "={{ String($json.skipSlack) }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ar-slack-block",
      "name": "Slack alerta riesgo outbound",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1740,
        360
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"Logixb2b auditor respuesta · BLOQUEO · {{ $json.batchLabel }} · score {{ $json.riskScore }} · {{ $json.riskLevel }} · flags {{ $json.killerFlagsText }} · {{ $json.recommendation }} · Europe/Madrid\"\n}",
        "options": {}
      },
      "continueOnFail": true
    },
    {
      "id": "ar-skip-block",
      "name": "Skip Slack bloqueo (demo)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1740,
        520
      ],
      "parameters": {}
    },
    {
      "id": "ar-restore-allow",
      "name": "Restaurar tras allowSend",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1980,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "return [$('Calcular riskScore').first()];"
      }
    },
    {
      "id": "ar-sanitize",
      "name": "Limpiar fila Sheets",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2220,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const j = $input.first().json;\nreturn [{\n  json: {\n    batchLabel: j.batchLabel,\n    runSource: j.runSource || 'manual',\n    channel: j.channel,\n    riskScore: j.riskScore,\n    riskLevel: j.riskLevel,\n    allowSend: j.allowSend,\n    killerFlagsText: j.killerFlagsText,\n    consentOptIn: j.consentOptIn,\n    hasPersonalizationToken: j.hasPersonalizationToken,\n    hasOptOutLine: j.hasOptOutLine,\n    delayHoursSinceLastTouch: j.delayHoursSinceLastTouch,\n    dailyVolume: j.dailyVolume,\n    dailyCap: j.dailyCap,\n    subjectLength: j.subjectLength,\n    bodyLength: j.bodyLength,\n    listVerified: j.listVerified,\n    hitlApproved: j.hitlApproved,\n    recommendation: j.recommendation,\n    executiveBrief: String(j.executiveBrief || '').slice(0, 1200),\n    complianceFlag: j.complianceFlag,\n    sheetsId: j.sheetsId,\n    slackWebhookUrl: j.slackWebhookUrl,\n    skipSheets: !!j.skipSheets,\n    skipSlack: !!j.skipSlack,\n    timezone: j.timezone || 'Europe/Madrid'\n  }\n}];"
      }
    },
    {
      "id": "ar-if-sheets",
      "name": "IF escribir Sheets",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2460,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "ar-if-sheets-c",
              "leftValue": "={{ String($json.skipSheets) }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ar-sheets",
      "name": "Append Sheets Auditor_Respuesta_Outbound",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2700,
        180
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Auditor_Respuesta_Outbound",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $json.batchLabel }}",
            "runSource": "={{ $json.runSource }}",
            "channel": "={{ $json.channel }}",
            "riskScore": "={{ $json.riskScore }}",
            "riskLevel": "={{ $json.riskLevel }}",
            "allowSend": "={{ $json.allowSend }}",
            "killerFlagsText": "={{ $json.killerFlagsText }}",
            "consentOptIn": "={{ $json.consentOptIn }}",
            "hasPersonalizationToken": "={{ $json.hasPersonalizationToken }}",
            "hasOptOutLine": "={{ $json.hasOptOutLine }}",
            "delayHoursSinceLastTouch": "={{ $json.delayHoursSinceLastTouch }}",
            "dailyVolume": "={{ $json.dailyVolume }}",
            "dailyCap": "={{ $json.dailyCap }}",
            "subjectLength": "={{ $json.subjectLength }}",
            "bodyLength": "={{ $json.bodyLength }}",
            "listVerified": "={{ $json.listVerified }}",
            "hitlApproved": "={{ $json.hitlApproved }}",
            "recommendation": "={{ $json.recommendation }}",
            "executiveBrief": "={{ $json.executiveBrief }}",
            "complianceFlag": "={{ $json.complianceFlag }}"
          }
        },
        "options": {}
      }
    },
    {
      "id": "ar-sheets-hist",
      "name": "Append Sheets Auditor_Respuesta_Historial",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2940,
        180
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Limpiar fila Sheets').item.json.sheetsId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Auditor_Respuesta_Historial",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "fecha": "={{ $now.setZone('Europe/Madrid').toFormat('yyyy-MM-dd HH:mm') }}",
            "batchLabel": "={{ $('Limpiar fila Sheets').item.json.batchLabel }}",
            "runSource": "={{ $('Limpiar fila Sheets').item.json.runSource }}",
            "channel": "={{ $('Limpiar fila Sheets').item.json.channel }}",
            "riskScore": "={{ $('Limpiar fila Sheets').item.json.riskScore }}",
            "riskLevel": "={{ $('Limpiar fila Sheets').item.json.riskLevel }}",
            "allowSend": "={{ $('Limpiar fila Sheets').item.json.allowSend }}",
            "killerFlagsText": "={{ $('Limpiar fila Sheets').item.json.killerFlagsText }}"
          }
        },
        "options": {}
      }
    },
    {
      "id": "ar-sheets-skip",
      "name": "Skip Sheets (demo)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        2700,
        420
      ],
      "parameters": {}
    },
    {
      "id": "ar-restore-sheets",
      "name": "Restaurar cifras post Sheets",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3180,
        180
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "return [$('Limpiar fila Sheets').first()];"
      }
    },
    {
      "id": "ar-if-slack-ok",
      "name": "IF Slack OK",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        3420,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "ar-if-slack-ok-c",
              "leftValue": "={{ String($json.skipSlack) }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "ar-slack-ok",
      "name": "Slack resumen auditor OK",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3660,
        180
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.slackWebhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"Logixb2b auditor respuesta · {{ $json.batchLabel }} · score {{ $json.riskScore }} · {{ $json.riskLevel }} · allowSend={{ $json.allowSend }} · flags {{ $json.killerFlagsText }} · {{ $json.recommendation }} · Europe/Madrid\"\n}",
        "options": {}
      },
      "continueOnFail": true
    },
    {
      "id": "ar-slack-skip",
      "name": "Skip Slack OK (demo)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        3660,
        420
      ],
      "parameters": {}
    },
    {
      "id": "ar-done",
      "name": "Resumen final local",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3900,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const j = $input.first().json;\nreturn [{\n  json: {\n    ok: true,\n    batchLabel: j.batchLabel,\n    channel: j.channel,\n    riskScore: j.riskScore,\n    riskLevel: j.riskLevel,\n    allowSend: j.allowSend,\n    killerFlagsText: j.killerFlagsText,\n    recommendation: j.recommendation,\n    executiveBrief: j.executiveBrief,\n    skipSheets: j.skipSheets,\n    skipSlack: j.skipSlack,\n    message: 'Auditor tasa respuesta outbound · Europe/Madrid · Logixb2b · Import OK'\n  }\n}];"
      }
    }
  ],
  "connections": {
    "Manual Trigger auditor": {
      "main": [
        [
          {
            "node": "Config fila outbound",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config fila outbound": {
      "main": [
        [
          {
            "node": "Validar Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validar Config": {
      "main": [
        [
          {
            "node": "IF config valida",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF config valida": {
      "main": [
        [
          {
            "node": "Calcular riskScore",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack config invalida",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack config invalida": {
      "main": [
        [
          {
            "node": "Stop (config invalida)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calcular riskScore": {
      "main": [
        [
          {
            "node": "IF allowSend",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF allowSend": {
      "main": [
        [
          {
            "node": "Passthrough allowSend OK",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "IF avisar Slack bloqueo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passthrough allowSend OK": {
      "main": [
        [
          {
            "node": "Restaurar tras allowSend",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF avisar Slack bloqueo": {
      "main": [
        [
          {
            "node": "Slack alerta riesgo outbound",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Slack bloqueo (demo)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack alerta riesgo outbound": {
      "main": [
        [
          {
            "node": "Restaurar tras allowSend",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip Slack bloqueo (demo)": {
      "main": [
        [
          {
            "node": "Restaurar tras allowSend",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restaurar tras allowSend": {
      "main": [
        [
          {
            "node": "Limpiar fila Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limpiar fila Sheets": {
      "main": [
        [
          {
            "node": "IF escribir Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF escribir Sheets": {
      "main": [
        [
          {
            "node": "Append Sheets Auditor_Respuesta_Outbound",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Sheets (demo)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Sheets Auditor_Respuesta_Outbound": {
      "main": [
        [
          {
            "node": "Append Sheets Auditor_Respuesta_Historial",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Sheets Auditor_Respuesta_Historial": {
      "main": [
        [
          {
            "node": "Restaurar cifras post Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restaurar cifras post Sheets": {
      "main": [
        [
          {
            "node": "IF Slack OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip Sheets (demo)": {
      "main": [
        [
          {
            "node": "IF Slack OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Slack OK": {
      "main": [
        [
          {
            "node": "Slack resumen auditor OK",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Slack OK (demo)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack resumen auditor OK": {
      "main": [
        [
          {
            "node": "Resumen final local",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip Slack OK (demo)": {
      "main": [
        [
          {
            "node": "Resumen final local",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
