{
  "name": "Workshop · Admisión de comprobantes - Workflow 3",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "workshop-kapso",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -1472,
        -160
      ],
      "id": "84a98d2b-8caf-4cc0-be2b-9556d162a451",
      "name": "3 · Webhook de Kapso",
      "webhookId": "0511ec3e-2236-46d0-9e1c-e12b9991fc70"
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "={{ $json.body }}",
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.5,
      "position": [
        -1232,
        -160
      ],
      "id": "4dabc916-f4f1-4a6a-a1b1-8c0b91ba606f",
      "name": "3 · Aplanar el body"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Mensaje del usuario: {{ $json.message.text ? $json.message.text.body : \"(sin texto, revisar adjunto)\" }}\n\nAdjunto: {{ $json.message.kapso.has_media ? ($json.message.kapso.media_data?.url ?? $json.message.kapso.media_url) : \"sin adjunto\" }}\nTipo de archivo: {{ $json.message.kapso.has_media ? ($json.message.kapso.media_data?.content_type ?? \"desconocido\") : \"sin adjunto\" }}",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "=# OBJETIVO\n\nSos el asistente de WhatsApp del Frigorífico del Oeste. Atendés a carnicerías que compran en cuenta corriente.\n\nHoy tu único trabajo es recibir comprobantes de transferencia y decirle al cliente qué pasó con el suyo.\n\nTodavía NO tomás pedidos, NO informás saldos, NO cotizás precios y NO confirmás entregas.\n\n# TAREAS\n\n**Si el mensaje trae adjunto**\n\n1. Llamá a `leer_comprobante` con la URL y el tipo de archivo, copiados exactamente como los recibiste.\n2. Mirá el campo `listo_para_registrar` de lo que devolvió:\n   - **`true`** → llamá a `registrar_en_pipeline` con los campos que devolvió `leer_comprobante`, sin modificar ninguno.\n   - **`false`** → NO registres nada. El campo `motivo` te dice por qué.\n3. Contale al cliente el resultado.\n\n**Si el mensaje NO trae adjunto**\n\n- Si dice que pagó, pedile la foto o el PDF del comprobante.\n- Si es otra cosa, respondé que por ahora solo podés recibir comprobantes.\n- No llames ninguna herramienta.\n\n# REGLAS\n\n**Vos no evaluás si el comprobante está completo**\n\nEso ya lo decidió `leer_comprobante` y viene resuelto en `listo_para_registrar`. Confiá en ese campo.\n\nNo mires los campos uno por uno para sacar tu propia conclusión. En particular: **un comprobante con CBU/CVU y sin alias, o con alias y sin CBU/CVU, está perfecto**. Alcanza con uno de los dos, y eso ya está contemplado.\n\nSi `listo_para_registrar` es `true`, se registra. Punto.\n\n**Sobre inventar datos**\n\n- NUNCA afirmes un monto, una fecha, un número de operación o un titular que `leer_comprobante` no te haya devuelto.\n- NUNCA pases a `registrar_en_pipeline` un campo que no vino de `leer_comprobante`.\n- NUNCA confirmes un pago que no se haya registrado.\n- Si un campo vino vacío, mandalo vacío. No lo completes.\n\n**Sobre el orden**\n\n- Primero leer, después registrar. Nunca al revés, nunca en paralelo.\n- Una sola pasada por mensaje. Si ya tenés el resultado, respondé con eso.\n\n**Qué responder según el `motivo`**\n\n- `ok` y registrado → confirmá con el monto y la fecha que devolvió la lectura. Estado: `procesado`.\n- `campos_ilegibles: ...` → nombrá exactamente esos campos y pedí una foto más clara. Estado: `manual_ilegible`.\n- `faltan_datos_obligatorios` → decí que el comprobante no muestra todos los datos y pedí que mande la vista completa. Estado: `manual_ilegible`.\n- `sin_cuenta_destino` → decí que no se ve a qué cuenta se transfirió. Estado: `manual_ilegible`, `requiere_humano: true`.\n- `no_es_comprobante_de_transferencia` → si `tipo_documento` es `echeque`, decí que lo revisa una persona, estado `manual_echeque` y `requiere_humano: true`. Si es otra cosa, decí que eso no parece un comprobante, estado `rechazado_no_comprobante`.\n- `formato_no_soportado` → explicá que solo leés imágenes (JPG, PNG) o PDF. Estado: `rechazado_no_comprobante`.\n- `documento_no_recibido` → pedí que reenvíe el archivo. Estado: `manual_ilegible`, `requiere_humano: true`.\n- El registro devuelve duplicado o cuenta incorrecta → explicalo. Estado: `duplicado` o `cuenta_incorrecta`, `requiere_humano: true`.\n\n**Sobre cómo escribir**\n\n- Español rioplatense, voseo, cordial y directo.\n- Mensajes cortos. Es WhatsApp, no un mail.\n- Sin emojis salvo que el usuario los use primero.\n\n**Valores válidos de estado**\n\nprocesado · duplicado · cuenta_incorrecta · manual_ilegible · manual_echeque · rechazado_no_comprobante · sin_comprobante · fuera_de_alcance\n\n# HERRAMIENTAS\n\nTenés dos, y se usan en este orden.\n\n## 1. `leer_comprobante`\n\n**Qué hace:** descarga el adjunto, lee el documento y evalúa si está completo. No guarda nada.\n**Cuándo:** siempre que haya adjunto, como primer paso.\n**Qué pasarle:** `media_url` y `content_type`, exactos.\n**Qué devuelve:** los campos del comprobante, más `listo_para_registrar` y `motivo`, que son los dos que tenés que mirar para decidir.\n\n## 2. `registrar_en_pipeline`\n\n**Qué hace:** valida la cuenta destino contra las cuentas del frigorífico, chequea duplicados y guarda la fila.\n**Cuándo:** solo si `listo_para_registrar` vino en `true`.\n**Qué pasarle:** los campos tal cual los devolvió `leer_comprobante`.\n**Nunca:** con campos propios, corregidos o completados por vos.\n\n# FORMATO DE SALIDA\n\nDevolvés siempre:\n- `respuesta`: el texto exacto que se le manda al cliente por WhatsApp\n- `estado`: uno de los valores válidos\n- `requiere_humano`: true si alguien del equipo tiene que mirarlo",
          "passthroughBinaryImages": false,
          "passthroughBinaryPdfs": false
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        -816,
        -160
      ],
      "id": "86258c5f-067e-4ad6-81aa-e24f5d33ee62",
      "name": "3 · Bot de WhatsApp"
    },
    {
      "parameters": {
        "modelName": "models/gemini-3.5-flash-lite",
        "options": {}
      },
      "id": "b27ef3e5-5cec-4c46-8ac2-8a0533428c75",
      "name": "3 · Modelo del bot",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1.1,
      "position": [
        -1184,
        112
      ]
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('3 · Aplanar el body').item.json.conversation.id }}",
        "contextWindowLength": 10
      },
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.4,
      "position": [
        -1040,
        96
      ],
      "id": "034854bc-a852-43cb-b838-ec1f725fde3b",
      "name": "3 · Memoria de conversación"
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n  \"respuesta\": \"Listo, registramos tu transferencia de 125430.50 del 14/08. Gracias!\",\n  \"estado\": \"procesado\",\n  \"requiere_humano\": false\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        -464,
        32
      ],
      "id": "d416f381-4e52-47d4-9337-99d2593813f9",
      "name": "3 · Formato de respuesta"
    },
    {
      "parameters": {
        "phoneNumberId": "",
        "to": "={{ $('3 · Webhook de Kapso').item.json.body.message.from }}",
        "message": "={{ $json.output.respuesta }}"
      },
      "type": "@kapso/n8n-nodes-kapso.kapso",
      "typeVersion": 1,
      "position": [
        -320,
        -160
      ],
      "id": "9fa0a8f0-d10b-4cb4-b513-c950df95b78f",
      "name": "3 · Responder por WhatsApp"
    },
    {
      "parameters": {
        "description": "Registra un comprobante ya extraído en la planilla del frigorífico.\n\nUsala SOLO después de haber leído el documento y solo si es un comprobante de transferencia con todos los campos legibles.\n\nCorre la validación de cuenta destino y el chequeo de duplicados, y devuelve el resultado.",
        "workflowId": {
          "__rl": true,
          "value": "U5Kd6nJ96fI4ESk9",
          "mode": "list",
          "cachedResultUrl": "/workflow/U5Kd6nJ96fI4ESk9",
          "cachedResultName": "n8n Argentina — Workshop - Workflow 1"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "monto": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('monto', ``, 'number') }}",
            "fecha": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('fecha', ``, 'string') }}",
            "numero_operacion": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('numero_operacion', ``, 'string') }}",
            "titular_origen": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('titular_origen', ``, 'string') }}",
            "cbu_cvu_destino": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('cbu_cvu_destino', ``, 'string') }}",
            "alias_destino": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('alias_destino', ``, 'string') }}",
            "banco_origen": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('banco_origen', ``, 'string') }}",
            "cuenta_origen": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('cuenta_origen', ``, 'string') }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "monto",
              "displayName": "monto",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "number"
            },
            {
              "id": "fecha",
              "displayName": "fecha",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "numero_operacion",
              "displayName": "numero_operacion",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "titular_origen",
              "displayName": "titular_origen",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "cbu_cvu_destino",
              "displayName": "cbu_cvu_destino",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "alias_destino",
              "displayName": "alias_destino",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "banco_origen",
              "displayName": "banco_origen",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "cuenta_origen",
              "displayName": "cuenta_origen",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [
        -624,
        112
      ],
      "id": "84e0a205-6603-4e10-8c3c-64090f44975c",
      "name": "registrar_en_pipeline"
    },
    {
      "parameters": {
        "content": "## FLUJO 3 · Bot general de WhatsApp\n### Bloque 3 · el cierre\n\nAcá el pipeline deja de ser el protagonista y pasa a ser **una herramienta**.\n\nEl bot tiene dos, y las usa en orden:\n\n**1 · `leer_comprobante`** → sub-workflow aparte. Baja el archivo, lo lee, devuelve el esquema. No guarda nada.\n\n**2 · `registrar_en_pipeline`** → el flujo 1 entero, sin un solo cambio.\n\nLo que se armó a las 10:20 es lo que el bot llama a las 12:40. Eso es la tesis.\n\n---\n\n### Por qué la lectura es un WORKFLOW y no un sub-agente\n\nEsto lo aprendimos rompiéndolo.\n\nUn **agente anidado** como herramienta recibe TEXTO. Nunca el binario del padre. Así que no puede ver la imagen, y como el esquema le exige todos los campos, **inventa un comprobante verosimil**.\n\nUn **sub-workflow** tiene su propio workflow. El binario vive adentro y el agente lo ve de verdad.\n\nLa frontera de una herramienta es JSON. Si tu herramienta necesita ver un archivo, tiene que ser un workflow.",
        "height": 892,
        "width": 460,
        "color": 7
      },
      "id": "5fe10c51-cd65-40e9-b5d5-2477da5091b5",
      "name": "Nota 3 · Cabecera",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1984,
        -272
      ]
    },
    {
      "parameters": {
        "content": "### Los prompts hacen cosas distintas\n\n**Bot de WhatsApp** define el ALCANCE y el ORDEN.\nQué sabe hacer, qué NO, y que primero se lee y después se registra.\n\n**Leer el comprobante** (en el otro workflow) define la EXTRACCIÓN.\nUna sola regla manda: nunca escribas un valor que no estés leyendo ahora.\n\n**Los esquemas** definen el CONTRATO.\n\n---\n\n### Los dos campos que arreglaron los dos bugs\n\n**`documento_recibido`** nació de la alucinación. Sin él, un modelo que no vio el documento igual tenía que completar el esquema, así que lo completaba.\n\n**`listo_para_registrar`** nació de un falso negativo. El modelo listaba `alias_destino` como ilegible cuando el comprobante simplemente no lo trae, y eso frenaba un pago perfectamente válido.\n\nDos formas de la misma leccion: **el modelo reporta, la logica decide**. Cuando le pediste al prompt que decidiera completitud, se equivoco. Cuando la regla paso a un nodo, dejo de equivocarse.\n\n---\n\n### Ilegible no es lo mismo que ausente\n\n`campos_ilegibles` = esta en el documento pero no se lee.\nCampo vacio = el comprobante no lo incluye.\n\nCasi ningun comprobante trae CBU/CVU y alias a la vez. Con uno alcanza.",
        "height": 688,
        "width": 1376,
        "color": 6
      },
      "id": "59243875-605a-4eee-8ded-44eae734ae01",
      "name": "Nota 3 · Prompts",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1472,
        288
      ]
    },
    {
      "parameters": {
        "content": "## Por qué esto se llama \"función 1\"\n### El camino acumulativo\n\nEl bot de acá hoy sabe hacer **una sola cosa**. No es poco: es lo único que hoy da valor medible.\n\nCada función nueva es una herramienta más colgada del mismo agente, sin tocar nada de lo que ya funciona.\n\n**1 · Admisión de comprobantes** ← lo de hoy\n\n**2 · Estado de cuenta corriente**\n\"¿Cuánto debo?\" → lee la planilla, devuelve saldo\nMétrica: consultas que Marcela deja de contestar\n\n**3 · Receptor de pedidos**\n\"Mandame 20kg de asado para el jueves\" → valida, registra, avisa\nMétrica: pedidos cargados sin intervención\n\n**4 · Seguimientos**\nRecordatorios de pago, avisos de entrega, confirmaciones\nMétrica: días promedio de cobranza\n\n---\n\nCuando existen tres o cuatro de estas, el bot conversacional **no es un proyecto nuevo**. Es una capa de ruteo arriba de cosas que ya funcionan y ya se pagaron solas.\n\nEse es el punto fuerte de n8n: lo que armás hoy no hay que reescribirlo para que sea la herramienta de mañana.",
        "height": 860,
        "width": 460,
        "color": 4
      },
      "id": "7633a0e5-dbc2-4403-8ab5-436a4cb53501",
      "name": "Nota 3 · Roadmap",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -80,
        -672
      ]
    },
    {
      "parameters": {
        "description": "Lee un comprobante de transferencia a partir de un adjunto de WhatsApp y devuelve sus datos.\n\nUsala SOLO cuando el mensaje trae un adjunto.\n\nQue le pasas: media_url y content_type, copiados exactamente como vinieron.\n\nQue devuelve, siempre con la misma forma:\n- documento_recibido: si se pudo leer el archivo o no\n- tipo_documento: comprobante_transferencia, echeque, otro_documento o no_es_documento\n- monto, fecha, numero_operacion, titular_origen, cbu_cvu_destino, alias_destino, banco_origen\n- campos_ilegibles: lista de los campos que NO se pudieron leer\n\nNo registra nada. Solo lee y reporta.",
        "workflowId": {
          "__rl": true,
          "value": "tkxQt52xVrWUM3j9",
          "mode": "list",
          "cachedResultUrl": "/workflow/tkxQt52xVrWUM3j9",
          "cachedResultName": "n8n Argentina — Workshop · Leer comprobante - Workflow 4 (SW)"
        },
        "workflowInputs": {
          "attemptToConvertTypes": false,
          "convertFieldsToString": true,
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "schema": [
            {
              "canBeUsedToMatch": true,
              "defaultMatch": false,
              "display": true,
              "displayName": "media_url",
              "id": "media_url",
              "removed": false,
              "required": false,
              "type": "string"
            },
            {
              "canBeUsedToMatch": true,
              "defaultMatch": false,
              "display": true,
              "displayName": "content_type",
              "id": "content_type",
              "removed": false,
              "required": false,
              "type": "string"
            }
          ],
          "value": {
            "content_type": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('content_type', `El content_type del adjunto, por ejemplo image/jpeg`, 'string') }}",
            "media_url": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('media_url', `La URL de media de Kapso, copiada exactamente sin modificar`, 'string') }}"
          }
        }
      },
      "id": "044585bd-2e1e-4296-a5ed-0f68c3a3d8cf",
      "name": "leer_comprobante",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [
        -864,
        112
      ]
    },
    {
      "parameters": {
        "content": "## Connectar Sub-Workflows\n\n### leer_comprobante = Workflow 4\n\n### registrar_en_pipeline = Workflow 1",
        "width": 416
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        208
      ],
      "typeVersion": 1,
      "id": "fd7dfe48-fee1-40fa-a0bf-d449e39f6806",
      "name": "Sticky Note"
    }
  ],
  "connections": {
    "3 · Webhook de Kapso": {
      "main": [
        [
          {
            "node": "3 · Aplanar el body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3 · Bot de WhatsApp": {
      "main": [
        [
          {
            "node": "3 · Responder por WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3 · Modelo del bot": {
      "ai_languageModel": [
        [
          {
            "node": "3 · Bot de WhatsApp",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "3 · Memoria de conversación": {
      "ai_memory": [
        [
          {
            "node": "3 · Bot de WhatsApp",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "3 · Formato de respuesta": {
      "ai_outputParser": [
        [
          {
            "node": "3 · Bot de WhatsApp",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "3 · Aplanar el body": {
      "main": [
        [
          {
            "node": "3 · Bot de WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "leer_comprobante": {
      "ai_tool": [
        [
          {
            "node": "3 · Bot de WhatsApp",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "registrar_en_pipeline": {
      "ai_tool": [
        [
          {
            "node": "3 · Bot de WhatsApp",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": true
  },
  "versionId": "3c968b16-db21-4e13-a4ba-3dfc6b321273",
  "meta": {
    "aiBuilderAssisted": true,
    "builderVariant": "mcp",
    "templateCredsSetupCompleted": true,
    "instanceId": "5368e3d42db5094d7b9c43512651646c73789011a338d7142afe9f0fc528d5ec"
  },
  "nodeGroups": [],
  "id": "H1TZOYc1YuBH0gDQ",
  "tags": []
}
