{
  "name": "Tickets de gasto: foto, lectura con IA y aprobación",
  "nodes": [
    {
      "parameters": {
        "content": "> Plantilla de partida de fernandorivera.es. Úsala y adáptala libremente; sin garantía; revisa los datos que tratas.\n\n## Tickets de gasto con IA\n1. Quien hace el gasto abre el formulario en el móvil, elige tienda y motivo y sube la **foto del ticket**.\n2. La foto se guarda en Drive y una IA con visión lee fecha, proveedor, NIF, base, IVA y total.\n3. Un nodo de código **comprueba** los datos (base + IVA = total, NIF, fecha, importe) y guarda una fila en la hoja.\n4. Si algo no cuadra, avisa por correo a quien revisa.\n5. Abajo: cada noche, los tickets que una persona ha marcado como **Aprobado** se envían a Holded (rama desactivada hasta que la configures).\n\nEs una **plantilla de partida, no probada en tu cuenta**: revísala nodo a nodo y pruébala con tickets de prueba antes de usarla de verdad.\n\nExplicación completa en «Monta tu app: tickets de gasto con foto e IA», en fernandorivera.es.",
        "height": 420,
        "width": 520,
        "color": 4
      },
      "id": "fbb703a1-dd49-5daf-8ee7-0ca5a607cabc",
      "name": "Nota: qué hace",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        -560
      ]
    },
    {
      "parameters": {
        "content": "## 1. Prepara la hoja\nPestaña **Tickets**, con estas columnas en la fila 1 (mismo texto exacto):\n\n`ID` · `Recibido` · `Tienda` · `Empleado` · `Motivo` · `Fecha` · `Proveedor` · `NIF` · `Número` · `Base` · `Tipo IVA` · `Cuota IVA` · `Total` · `Tipo documento` · `Estado` · `Motivos de revisión` · `Foto` · `Registrado en Holded`\n\nEn la columna **Estado**, pon una validación de datos con estas opciones: `Pendiente de aprobar`, `Revisar`, `Aprobado`, `Rechazado`.\n\nEl flujo **nunca** escribe `Aprobado`: eso lo hace una persona.",
        "height": 380,
        "width": 520,
        "color": 5
      },
      "id": "328d36ba-a612-5450-8617-c2aaf4ee2e5f",
      "name": "Nota: la hoja",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        -600
      ]
    },
    {
      "parameters": {
        "content": "## 2. Configura\n- **Formulario del ticket**: cambia las tiendas y los motivos. Copia la URL de producción del formulario y guárdala en el móvil de cada tienda.\n- **Configuración**: URL y modelo de tu IA (debe aceptar imágenes), tolerancia, días máximos, importe a partir del cual se revisa siempre y correo de quien revisa.\n- **IA lee el ticket**: crea una credencial *Header Auth* con `Authorization` = `Bearer TU_CLAVE` (la clave nunca dentro del flujo).\n- **Guardar foto en Drive**: elige la carpeta (`TU_ID_DE_CARPETA`).\n- Nodos de Google Sheets: cambia `TU_ID_DE_HOJA` (está en la URL, entre `/d/` y `/edit`).\n\nLa petición usa el formato *chat/completions* con imagen. Si usas Claude o Gemini, cambia URL, cabeceras y cuerpo según su documentación, o usa el nodo de IA de n8n de tu proveedor.",
        "height": 400,
        "width": 560,
        "color": 5
      },
      "id": "8c33ae83-9924-51ff-bcaf-e48bc879d9e5",
      "name": "Nota: configura",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        360,
        -600
      ]
    },
    {
      "parameters": {
        "content": "### Qué no subir a la IA\nUn ticket puede llevar el nombre de quien paga, parte de la tarjeta o datos de terceros (comidas, hoteles). Pide al equipo que **haga la foto solo del ticket**, sin el resguardo de la tarjeta ni documentos personales. Usa la API con una cuenta de empresa, no un chat personal. La instrucción pide a la IA que no copie datos personales.",
        "height": 300,
        "width": 420,
        "color": 3
      },
      "id": "50548af9-ea4e-57a9-81a6-d42801cafad4",
      "name": "Nota: datos personales",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        960,
        -600
      ]
    },
    {
      "parameters": {
        "formTitle": "Ticket de gasto",
        "formDescription": "Haz la foto solo del ticket, recto y con luz. No incluyas el resguardo de la tarjeta.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Tienda",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Tienda Centro"
                  },
                  {
                    "option": "Tienda Norte"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Empleado",
              "placeholder": "Tu nombre o código",
              "requiredField": true
            },
            {
              "fieldLabel": "Motivo",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Material de tienda"
                  },
                  {
                    "option": "Limpieza"
                  },
                  {
                    "option": "Reparación"
                  },
                  {
                    "option": "Envíos y mensajería"
                  },
                  {
                    "option": "Desplazamiento"
                  },
                  {
                    "option": "Otro"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Foto del ticket",
              "fieldType": "file",
              "multipleFiles": false,
              "acceptFileTypes": ".jpg,.jpeg,.png,.pdf",
              "requiredField": true
            }
          ]
        },
        "options": {}
      },
      "id": "337e1bb7-c3b4-5145-9ca7-4213e9f78a14",
      "name": "Formulario del ticket",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.2,
      "position": [
        -700,
        0
      ],
      "webhookId": "17bf9615-c254-5dc6-8dac-a45de2029c29"
    },
    {
      "parameters": {
        "jsCode": "// Deja la foto en el campo binario «foto», sea cual sea el nombre que le ponga el formulario.\nreturn $input.all().map((item, i) => {\n  const claves = Object.keys(item.binary || {});\n  if (!claves.length) throw new Error('El envío no trae foto');\n  const f = item.json;\n  return {\n    json: {\n      ID: 'T-' + $now.toFormat('yyyyMMdd-HHmmss') + '-' + i,\n      Recibido: $now.toFormat('yyyy-MM-dd HH:mm'),\n      Tienda: f['Tienda'],\n      Empleado: f['Empleado'],\n      Motivo: f['Motivo'],\n    },\n    binary: { foto: item.binary[claves[0]] },\n  };\n});"
      },
      "id": "03ef64b2-f710-5ead-b08e-f33231651a1c",
      "name": "Preparar foto",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -480,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e78667b3-95a6-5d75-970b-7662b77fc6ae",
              "name": "url_api_ia",
              "value": "https://api.openai.com/v1/chat/completions",
              "type": "string"
            },
            {
              "id": "c1c90f59-8c2c-54c5-ab87-cec4c2453489",
              "name": "modelo",
              "value": "ELIGE_UN_MODELO_CON_VISION",
              "type": "string"
            },
            {
              "id": "e1ff843d-b58d-5f1b-a8bb-99c790b46239",
              "name": "tolerancia_euros",
              "value": 0.02,
              "type": "number"
            },
            {
              "id": "3a52f683-8280-5c89-810b-39e047d16aba",
              "name": "dias_maximos",
              "value": 60,
              "type": "number"
            },
            {
              "id": "55906801-f395-5fb5-8a0a-aca18d50016b",
              "name": "importe_revisar_siempre",
              "value": 150,
              "type": "number"
            },
            {
              "id": "5ecae6c3-fb32-51c7-858b-5532cacbf63f",
              "name": "email_revision",
              "value": "TU_CORREO",
              "type": "string"
            }
          ]
        },
        "options": {
          "includeBinary": true
        },
        "includeOtherFields": true
      },
      "id": "cdf41c48-ca39-551c-a81d-db59f3445923",
      "name": "Configuración",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -260,
        0
      ]
    },
    {
      "parameters": {
        "name": "={{ $json.ID }} - {{ $json.Tienda }}",
        "driveId": {
          "__rl": true,
          "value": "My Drive",
          "mode": "list",
          "cachedResultName": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "TU_ID_DE_CARPETA",
          "mode": "id"
        },
        "inputDataFieldName": "foto",
        "options": {}
      },
      "id": "df63d1ab-2f92-5738-8862-c7294eaa8ed6",
      "name": "Guardar foto en Drive",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        -20,
        -180
      ]
    },
    {
      "parameters": {
        "jsCode": "const cfg = $('Configuración').first().json;\nconst PROMPT = \"Lee la foto de este ticket o factura de gasto de un negocio en España. Devuelve SOLO un objeto JSON con estas claves: fecha (AAAA-MM-DD), proveedor, nif, numero, base, tipo_iva, cuota_iva, total, tipo_documento (\\\"ticket\\\", \\\"factura simplificada\\\" o \\\"factura\\\"), confianza (\\\"alta\\\", \\\"media\\\" o \\\"baja\\\") y dudas. Importes como número con punto decimal y sin símbolo de euro. Si hay varios tipos de IVA, pon en base y cuota_iva la suma, en tipo_iva \\\"varios\\\" y explícalo en dudas. Si un dato no aparece o no se lee con claridad, pon null y dilo en dudas. No adivines ni calcules ningún dato que no esté impreso. No copies nombres de personas, números de tarjeta ni otros datos personales que aparezcan en el ticket.\";\nconst salida = [];\nconst items = $input.all();\nfor (let i = 0; i < items.length; i++) {\n  const bin = items[i].binary.foto;\n  if (bin.mimeType === 'application/pdf') {\n    // Muchas APIs no aceptan PDF como imagen: se marca para revisar a mano.\n    salida.push({ json: { ...items[i].json, body: null, pdf: true } });\n    continue;\n  }\n  const buf = await this.helpers.getBinaryDataBuffer(i, 'foto');\n  const dataUrl = 'data:' + (bin.mimeType || 'image/jpeg') + ';base64,' + buf.toString('base64');\n  salida.push({ json: { ...items[i].json, pdf: false, body: {\n    model: cfg.modelo,\n    temperature: 0,\n    response_format: { type: 'json_object' },\n    messages: [{ role: 'user', content: [\n      { type: 'text', text: PROMPT },\n      { type: 'image_url', image_url: { url: dataUrl } }\n    ] }]\n  } } });\n}\nreturn salida;"
      },
      "id": "bb3b32cc-e58d-5ed4-b49d-cbcad8f37363",
      "name": "Preparar petición a la IA",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -20,
        120
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Configuración').first().json.url_api_ia }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "f1f774dd-1607-5bd3-99b1-e2312dd7021f",
      "name": "IA lee el ticket",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        200,
        120
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// Las comprobaciones las hace el código, no la IA.\nconst cfg = $('Configuración').first().json;\nconst previos = $('Preparar petición a la IA').all();\nconst fotos = $('Guardar foto en Drive').all();\nconst num = v => { if (v === null || v === undefined || v === '') return null; const n = parseFloat(String(v).replace(/\\s|€/g, '').replace(',', '.')); return isNaN(n) ? null : n; };\nconst nifOk = v => /^[A-Z0-9][0-9]{7}[A-Z0-9]$/.test(String(v || '').toUpperCase().replace(/[^A-Z0-9]/g, ''));\nreturn $input.all().map((item, i) => {\n  const base = previos[i].json;\n  const motivos = [];\n  let d = {};\n  if (base.pdf) motivos.push('PDF: revisar a mano');\n  else {\n    try { d = JSON.parse(item.json.choices[0].message.content); }\n    catch (e) { motivos.push('La IA no devolvió datos legibles'); }\n  }\n  const b = num(d.base), c = num(d.cuota_iva), t = num(d.total);\n  for (const k of ['fecha', 'proveedor', 'total']) if (d[k] === null || d[k] === undefined || d[k] === '') motivos.push('Falta ' + k);\n  if (b !== null && c !== null && t !== null && Math.abs(b + c - t) > cfg.tolerancia_euros) motivos.push('Base + IVA no da el total');\n  if (d.nif && !nifOk(d.nif)) motivos.push('NIF con formato raro');\n  if (!d.nif) motivos.push('Sin NIF');\n  const f = DateTime.fromISO(String(d.fecha || ''));\n  if (d.fecha && !f.isValid) motivos.push('Fecha no válida');\n  if (f.isValid && f > $now) motivos.push('Fecha futura');\n  if (f.isValid && $now.diff(f, 'days').days > cfg.dias_maximos) motivos.push('Ticket de hace más de ' + cfg.dias_maximos + ' días');\n  if (t !== null && t >= cfg.importe_revisar_siempre) motivos.push('Importe alto');\n  if (d.confianza === 'baja') motivos.push('La IA no lo ve claro');\n  if (d.dudas) motivos.push('Dudas de la IA: ' + d.dudas);\n  const idFoto = fotos[i] && fotos[i].json && fotos[i].json.id;\n  const graves = motivos.filter(m => !m.startsWith('Dudas de la IA'));\n  return { json: {\n    ID: base.ID, Recibido: base.Recibido, Tienda: base.Tienda, Empleado: base.Empleado, Motivo: base.Motivo,\n    Fecha: d.fecha ?? '', Proveedor: d.proveedor ?? '', NIF: d.nif ?? '', 'Número': d.numero ?? '',\n    Base: b ?? '', 'Tipo IVA': d.tipo_iva ?? '', 'Cuota IVA': c ?? '', Total: t ?? '',\n    'Tipo documento': d.tipo_documento ?? '',\n    Estado: graves.length ? 'Revisar' : 'Pendiente de aprobar',\n    'Motivos de revisión': motivos.join(' · '),\n    Foto: idFoto ? 'https://drive.google.com/file/d/' + idFoto + '/view' : '',\n    'Registrado en Holded': ''\n  } };\n});"
      },
      "id": "03920521-5a0f-56b4-b564-8f56d4d825fc",
      "name": "Validar datos",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        420,
        120
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Tickets",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "15805aaf-136e-502b-bb90-2a24294da141",
      "name": "Guardar en la hoja",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        640,
        120
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "df5b4ec7-10d0-5a8f-87ec-c7176efa7040",
              "leftValue": "={{ $('Validar datos').item.json.Estado }}",
              "rightValue": "Revisar",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "a5c5627f-97b9-5642-89e5-7b4274e1528a",
      "name": "¿Hay que revisarlo?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        860,
        120
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Configuración').first().json.email_revision }}",
        "subject": "=Ticket por revisar: {{ $('Validar datos').item.json.Tienda }} · {{ $('Validar datos').item.json.Proveedor || 'proveedor sin leer' }}",
        "emailType": "text",
        "message": "=Hola:\n\nEste ticket necesita una revisión antes de aprobarlo.\n\nID: {{ $('Validar datos').item.json.ID }}\nTienda: {{ $('Validar datos').item.json.Tienda }} · Motivo: {{ $('Validar datos').item.json.Motivo }}\nProveedor: {{ $('Validar datos').item.json.Proveedor }} · Total: {{ $('Validar datos').item.json.Total }} €\nPor qué: {{ $('Validar datos').item.json['Motivos de revisión'] }}\nFoto: {{ $('Validar datos').item.json.Foto }}\n\nCorrige los datos en la hoja y cambia el Estado a Aprobado o Rechazado.\n\nAviso automático del flujo de tickets de gasto.",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "a5cb2da5-991c-5877-b601-7aefe7e44aa4",
      "name": "Avisar a quien revisa",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1080,
        20
      ],
      "webhookId": "3f0ebf05-2fb2-5e67-a11d-f4af2dbd74d0"
    },
    {
      "parameters": {},
      "id": "7a7a5757-cdce-5ada-8e63-31d2d0cdefc4",
      "name": "Correcto: espera aprobación",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1080,
        220
      ]
    },
    {
      "parameters": {
        "content": "## 3. Aprobados a Holded (desactivado)\nCada noche lee la hoja, coge las filas con Estado **Aprobado** y sin valor en `Registrado en Holded`, y crea un documento de compra en Holded con la API (`POST https://api.holded.com/api/invoicing/v1/documents/purchase`, cabecera `key`).\n\nAntes de activarlo:\n- Crea una credencial *Header Auth* con nombre `key` y tu clave de API de Holded.\n- Revisa en developers.holded.com («Create Document») los campos del cuerpo, el formato de la fecha y los impuestos. Pruébalo con un solo ticket.\n- Habla con tu gestoría: cuenta de gasto, si el ticket simple vale para deducir IVA y qué hacer con el papel.\n\n**Cadena con SAP u otro ERP**: no conectes nada al ERP. Borra esta rama, filtra la hoja por Aprobado y exporta el CSV con las columnas que pida administración.",
        "height": 440,
        "width": 560,
        "color": 6
      },
      "id": "97e984be-b9bd-55bb-9f42-39233b909791",
      "name": "Nota: Holded",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        420
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 21,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "id": "17b9de53-26b0-51dd-9267-74a29aab9a3b",
      "name": "Cada noche 21:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -140,
        520
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Tickets",
          "mode": "name"
        },
        "options": {}
      },
      "id": "e3e9700b-dcb2-5112-85f4-2512d50e5c28",
      "name": "Leer tickets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        80,
        520
      ]
    },
    {
      "parameters": {
        "jsCode": "const num = v => parseFloat(String(v).replace(',', '.')) || 0;\nreturn $input.all()\n  .filter(r => String(r.json['Estado']).trim() === 'Aprobado' && !String(r.json['Registrado en Holded'] || '').trim())\n  .map(r => {\n    const j = r.json;\n    const tipo = num(j['Tipo IVA']);\n    return { json: {\n      ID: j['ID'],\n      holded: {\n        contactName: j['Proveedor'],\n        contactCode: j['NIF'],\n        // Revisa en la documentación de Holded el formato de fecha que espera (aquí, segundos Unix).\n        date: Math.floor(DateTime.fromISO(String(j['Fecha'])).toSeconds()),\n        isReceipt: true,\n        notes: 'Ticket ' + j['ID'] + ' · ' + j['Tienda'] + ' · ' + j['Motivo'],\n        items: [{ name: j['Motivo'], units: 1, subtotal: num(j['Base']), tax: tipo }]\n      }\n    } };\n  });"
      },
      "id": "9cddb276-608b-546e-9144-4861101f8fd1",
      "name": "Aprobados sin registrar",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        300,
        520
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.holded.com/api/invoicing/v1/documents/purchase",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.holded) }}",
        "options": {}
      },
      "id": "c6d5350f-12e8-5108-a00e-fdd65aaad096",
      "name": "Crear gasto en Holded",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        520,
        520
      ],
      "disabled": true
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Tickets",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "ID": "={{ $('Aprobados sin registrar').item.json.ID }}",
            "Registrado en Holded": "={{ $json.id || 'revisar respuesta' }}"
          },
          "matchingColumns": [
            "ID"
          ],
          "schema": []
        },
        "options": {}
      },
      "id": "e7204c88-e7f4-5dde-947f-278d02c1dd9d",
      "name": "Marcar como registrado",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        740,
        520
      ],
      "disabled": true
    }
  ],
  "connections": {
    "Formulario del ticket": {
      "main": [
        [
          {
            "node": "Preparar foto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar foto": {
      "main": [
        [
          {
            "node": "Configuración",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuración": {
      "main": [
        [
          {
            "node": "Guardar foto en Drive",
            "type": "main",
            "index": 0
          },
          {
            "node": "Preparar petición a la IA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar petición a la IA": {
      "main": [
        [
          {
            "node": "IA lee el ticket",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IA lee el ticket": {
      "main": [
        [
          {
            "node": "Validar datos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validar datos": {
      "main": [
        [
          {
            "node": "Guardar en la hoja",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar en la hoja": {
      "main": [
        [
          {
            "node": "¿Hay que revisarlo?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "¿Hay que revisarlo?": {
      "main": [
        [
          {
            "node": "Avisar a quien revisa",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Correcto: espera aprobación",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cada noche 21:00": {
      "main": [
        [
          {
            "node": "Leer tickets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer tickets": {
      "main": [
        [
          {
            "node": "Aprobados sin registrar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aprobados sin registrar": {
      "main": [
        [
          {
            "node": "Crear gasto en Holded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear gasto en Holded": {
      "main": [
        [
          {
            "node": "Marcar como registrado",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "tags": []
}
