{
  "name": "FerreBot \u2014 Agente Ventas Ferreter\u00eda WhatsApp",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "whatsapp-ferreteria",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "d6b7a8ae-83d3-4dfd-8b69-36c7a2a6a91f",
      "name": "WhatsApp Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        180,
        300
      ]
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "const body = $input.first().json;\nlet messageText = '';\nlet from = '';\nlet messageId = '';\nlet wabaId = '';\nif (body?.entry?.[0]?.changes?.[0]?.value?.messages) {\n  const msg = body.entry[0].changes[0].value.messages[0];\n  if (!['text','interactive','button'].includes(msg.type)) {\n    return [{ json: { skip: true, reason: 'non-text message' } }];\n  }\n  messageText = msg.text?.body || msg.interactive?.button_reply?.title || msg.interactive?.list_reply?.title || msg.button?.text || '';\n  from = msg.from;\n  messageId = msg.id;\n  wabaId = body.entry[0].changes[0].value.metadata?.phone_number_id;\n} else if (body?.['hub.challenge']) {\n  return [{ json: { skip: true, challenge: body['hub.challenge'] } }];\n} else {\n  return [{ json: { skip: true, reason: 'unknown format' } }];\n}\nif (!messageText.trim() || !from) {\n  return [{ json: { skip: true, reason: 'empty message' } }];\n}\nreturn [{ json: { skip: false, messageText: messageText.trim(), from, messageId, wabaId, timestamp: new Date().toISOString() } }];"
      },
      "id": "7cbc0b7b-c526-40f5-9647-3636d9ba73e2",
      "name": "Parsear Mensaje",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-skip",
              "leftValue": "={{ $json.skip }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "bcb0e952-2472-4362-ac67-4e2810eb7dd6",
      "name": "\u00bfMensaje V\u00e1lido?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        620,
        300
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "OK",
        "options": {
          "responseCode": 200
        }
      },
      "id": "37e5ea27-0517-4b2b-91d7-f76e9ee327df",
      "name": "Responder 200 OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        840,
        460
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "key": "={{ 'ferrebot_session_' + $json.from }}",
        "options": {}
      },
      "id": "ef38d860-182d-42c2-bc05-155f05d6bae0",
      "name": "Obtener Sesi\u00f3n Redis",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        840,
        300
      ],
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "const sessionRaw = $input.first().json?.value ?? null;\nconst msg = $('Parsear Mensaje').first().json;\nlet session;\ntry { session = sessionRaw ? JSON.parse(sessionRaw) : null; } catch (e) { session = null; }\nif (!session) {\n  session = { from: msg.from, state: 'inicio', cart: [], customerName: '', address: '', paymentMethod: '', orderId: '', history: [], createdAt: new Date().toISOString() };\n}\nsession.history = session.history || [];\nsession.history.push({ role: 'user', content: msg.messageText, ts: new Date().toISOString() });\nif (session.history.length > 20) { session.history = session.history.slice(-20); }\nsession.lastMessage = msg.messageText;\nsession.lastActivity = new Date().toISOString();\nreturn [{ json: { session, messageText: msg.messageText, from: msg.from, wabaId: msg.wabaId, messageId: msg.messageId } }];"
      },
      "id": "93d9b699-e3eb-4e00-972e-f5730c80e7ed",
      "name": "Construir Sesi\u00f3n",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1060,
        300
      ]
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "const { session } = $input.first().json;\nconst systemPrompt = `Eres FerreBot, agente de ventas de Ferreter\u00eda El Constructor.\n\n=== CAT\u00c1LOGO ===\n[{\"id\":\"H001\",\"nombre\":\"Martillo Carpintero 16oz\",\"precio\":45000},{\"id\":\"H002\",\"nombre\":\"Destornillador Estrella #2\",\"precio\":12000},{\"id\":\"H003\",\"nombre\":\"Taladro Inal\u00e1mbrico 18V\",\"precio\":280000},{\"id\":\"H004\",\"nombre\":\"Nivel Aluminio 60cm\",\"precio\":35000},{\"id\":\"H005\",\"nombre\":\"Llave Inglesa 12 pulgadas\",\"precio\":28000},{\"id\":\"M001\",\"nombre\":\"Cemento Gris 50kg\",\"precio\":32000},{\"id\":\"M002\",\"nombre\":\"Arena de R\u00edo bulto 40kg\",\"precio\":8500},{\"id\":\"M003\",\"nombre\":\"Varilla Corrugada 3/8\",\"precio\":18000},{\"id\":\"M004\",\"nombre\":\"Ladrillo Tolete unidad\",\"precio\":800},{\"id\":\"E001\",\"nombre\":\"Cable THW #12 rollo 100m\",\"precio\":95000},{\"id\":\"E002\",\"nombre\":\"Toma Corriente Doble\",\"precio\":8500},{\"id\":\"E003\",\"nombre\":\"Interruptor Sencillo\",\"precio\":6500},{\"id\":\"P001\",\"nombre\":\"Tubo PVC 1/2 x 6m\",\"precio\":12000},{\"id\":\"P002\",\"nombre\":\"Codo PVC 1/2\",\"precio\":900},{\"id\":\"P003\",\"nombre\":\"Llave de Paso 1/2\",\"precio\":18500},{\"id\":\"F001\",\"nombre\":\"Pintura Viniltex Blanco 1/4 GL\",\"precio\":22000},{\"id\":\"F002\",\"nombre\":\"Brocha 3 pulgadas\",\"precio\":7500},{\"id\":\"F003\",\"nombre\":\"Rodillo Felpa 9 pulgadas\",\"precio\":14000}]\n\n=== POL\u00cdTICAS ===\n- Horario: Lun-S\u00e1b 7am-6pm\n- Domicilio GRATIS >= $150.000; menor costo $15.000\n- Pago: Efectivo, Transferencia, Nequi, Daviplata\n- Entrega: 2-4 horas\n\n=== CARRITO ACTUAL ===\n${JSON.stringify(session.cart)}\nSubtotal: $${(session.subtotal||0).toLocaleString('es-CO')} | Domicilio: $${(session.domicilio||0).toLocaleString('es-CO')} | TOTAL: $${(session.total||0).toLocaleString('es-CO')}\n\n=== SESI\u00d3N ===\nEstado: ${session.state} | Cliente: ${session.customerName||'?'} | Direcci\u00f3n: ${session.address||'?'} | Pago: ${session.paymentMethod||'?'} | Orden: ${session.orderId||'pendiente'}\n\n=== FLUJO ===\ninicio \u2192 catalogo \u2192 carrito \u2192 datos \u2192 pago \u2192 confirmacion \u2192 finalizado\n\n=== REGLAS ===\n1. Responde en espa\u00f1ol colombiano con emojis \ud83d\udd28\ud83e\ude9b\ud83d\udd27\u26a1\ud83c\udfd7\ufe0f\ud83d\uded2\n2. Agregar producto: [CART_ACTION:add|ID|CANTIDAD]\n3. Quitar producto: [CART_ACTION:remove|ID|0]\n4. Cambiar estado: [STATE:estado]\n5. Pedido listo para DB: [ORDER_READY]\n6. N\u00famero de orden: [ORDER_ID:FEC-${Math.floor(10000+Math.random()*89999)}]\n7. Nombre cliente: [CUSTOMER_NAME:nombre]\n8. Direcci\u00f3n: [ADDRESS:direcci\u00f3n]\n9. Pago: [PAYMENT:m\u00e9todo]\n10. Solo productos del cat\u00e1logo. S\u00e9 proactivo y sugiere complementos.`;\nconst historyMessages = (session.history||[]).slice(-10).map(h=>({role:h.role,content:h.content}));\nconst messages = [{role:'system',content:systemPrompt},...historyMessages];\nreturn [{ json: { messages, session, from: $input.first().json.from, wabaId: $input.first().json.wabaId } }];"
      },
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Preparar Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1280,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"gpt-4o\",\n  \"temperature\": 0.6,\n  \"max_tokens\": 600,\n  \"messages\": {{ JSON.stringify($json.messages) }}\n}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "5be5bc4c-dca7-4849-b8ae-e5dc240b49a9",
      "name": "OpenAI GPT-4o",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1500,
        300
      ],
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "const openAiResp = $input.first().json;\nconst prevNode = $('Preparar Prompt').first().json;\nconst session = prevNode.session;\nconst rawResponse = openAiResp.choices?.[0]?.message?.content || '';\nif (!rawResponse) { return [{ json: { reply: 'Lo siento, tuve un problema t\u00e9cnico. Intenta de nuevo \ud83d\ude4f', session, orderReady: false, from: prevNode.from, wabaId: prevNode.wabaId } }]; }\nlet reply = rawResponse;\nlet newState = session.state;\nlet orderId = session.orderId;\nlet customerName = session.customerName;\nlet address = session.address;\nlet paymentMethod = session.paymentMethod;\nlet orderReady = false;\nconst catalog = { H001:{nombre:'Martillo Carpintero 16oz',precio:45000}, H002:{nombre:'Destornillador Estrella #2',precio:12000}, H003:{nombre:'Taladro Inal\u00e1mbrico 18V',precio:280000}, H004:{nombre:'Nivel Aluminio 60cm',precio:35000}, H005:{nombre:'Llave Inglesa 12 pulgadas',precio:28000}, M001:{nombre:'Cemento Gris 50kg',precio:32000}, M002:{nombre:'Arena de R\u00edo bulto 40kg',precio:8500}, M003:{nombre:'Varilla Corrugada 3/8',precio:18000}, M004:{nombre:'Ladrillo Tolete unidad',precio:800}, E001:{nombre:'Cable THW #12 rollo 100m',precio:95000}, E002:{nombre:'Toma Corriente Doble',precio:8500}, E003:{nombre:'Interruptor Sencillo',precio:6500}, P001:{nombre:'Tubo PVC 1/2 x 6m',precio:12000}, P002:{nombre:'Codo PVC 1/2',precio:900}, P003:{nombre:'Llave de Paso 1/2',precio:18500}, F001:{nombre:'Pintura Viniltex Blanco 1/4 GL',precio:22000}, F002:{nombre:'Brocha 3 pulgadas',precio:7500}, F003:{nombre:'Rodillo Felpa 9 pulgadas',precio:14000} };\nconst cart = JSON.parse(JSON.stringify(session.cart || []));\nconst cartRegex = /\\[CART_ACTION:(add|remove)\\|([A-Z0-9]+)\\|(\\d+)\\]/g;\nlet m;\nwhile ((m = cartRegex.exec(rawResponse)) !== null) {\n  const [, action, productId, qtyStr] = m;\n  const qty = parseInt(qtyStr);\n  if (action === 'add' && catalog[productId]) {\n    const existing = cart.find(i => i.id === productId);\n    if (existing) { existing.qty += qty; } else { cart.push({ id: productId, nombre: catalog[productId].nombre, precio: catalog[productId].precio, qty }); }\n  } else if (action === 'remove') { const idx = cart.findIndex(i => i.id === productId); if (idx > -1) cart.splice(idx, 1); }\n  reply = reply.replace(m[0], '');\n}\nconst stateM = reply.match(/\\[STATE:(\\w+)\\]/); if (stateM) { newState = stateM[1]; reply = reply.replace(stateM[0], ''); }\nif (reply.includes('[ORDER_READY]')) { orderReady = true; reply = reply.replace('[ORDER_READY]', ''); }\nconst orderM = reply.match(/\\[ORDER_ID:([A-Z0-9-]+)\\]/); if (orderM) { orderId = orderM[1]; reply = reply.replace(orderM[0], ''); }\nconst nameM = reply.match(/\\[CUSTOMER_NAME:([^\\]]+)\\]/); if (nameM) { customerName = nameM[1].trim(); reply = reply.replace(nameM[0], ''); }\nconst addrM = reply.match(/\\[ADDRESS:([^\\]]+)\\]/); if (addrM) { address = addrM[1].trim(); reply = reply.replace(addrM[0], ''); }\nconst payM = reply.match(/\\[PAYMENT:([^\\]]+)\\]/); if (payM) { paymentMethod = payM[1].trim(); reply = reply.replace(payM[0], ''); }\nconst subtotal = cart.reduce((s, i) => s + i.precio * i.qty, 0);\nconst domicilio = subtotal > 0 && subtotal < 150000 ? 15000 : 0;\nconst total = subtotal + domicilio;\nconst updatedSession = { ...session, cart, state: newState, orderId, customerName, address, paymentMethod, subtotal, domicilio, total, history: [...(session.history||[]), { role: 'assistant', content: rawResponse, ts: new Date().toISOString() }] };\nreturn [{ json: { reply: reply.trim(), session: updatedSession, orderReady, from: prevNode.from, wabaId: prevNode.wabaId, order_id: orderId, customer_phone: prevNode.from, customer_name: customerName, cart: JSON.stringify(cart), subtotal, domicilio, total, state: newState, payment_method: paymentMethod, address } }];"
      },
      "id": "8dee73b8-0afc-4e0c-8572-034a57712b61",
      "name": "Procesar Respuesta IA",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1720,
        300
      ]
    },
    {
      "parameters": {
        "operation": "set",
        "key": "={{ 'ferrebot_session_' + $json.from }}",
        "value": "={{ JSON.stringify($json.session) }}",
        "keyExpiration": 86400,
        "options": {}
      },
      "id": "439959c4-a7a5-4e95-8888-dbdcee4e9752",
      "name": "Guardar Sesi\u00f3n Redis",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        1940,
        300
      ],
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-order",
              "leftValue": "={{ $json.orderReady }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "436d6cff-d3f5-4124-a9bf-c204693577e6",
      "name": "\u00bfPedido Confirmado?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2160,
        300
      ]
    },
    {
      "parameters": {
        "operation": "insert",
        "schema": {
          "__rl": true,
          "value": "public",
          "mode": "list"
        },
        "table": {
          "__rl": true,
          "value": "orders",
          "mode": "list"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "order_id": "={{ $json.order_id }}",
            "customer_phone": "={{ $json.customer_phone }}",
            "customer_name": "={{ $json.customer_name }}",
            "cart": "={{ $json.cart }}",
            "subtotal": "={{ $json.subtotal }}",
            "domicilio": "={{ $json.domicilio }}",
            "total": "={{ $json.total }}",
            "state": "={{ $json.state }}",
            "payment_method": "={{ $json.payment_method }}",
            "address": "={{ $json.address }}"
          },
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "b011a5fd-bfc7-4b45-bd06-16067f8539ca",
      "name": "Guardar Pedido PostgreSQL",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        2380,
        160
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.facebook.com/v19.0/{{ $('Parsear Mensaje').first().json.wabaId }}/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"messaging_product\": \"whatsapp\",\n  \"recipient_type\": \"individual\",\n  \"to\": \"{{ $('Procesar Respuesta IA').first().json.from }}\",\n  \"type\": \"text\",\n  \"text\": { \"preview_url\": false, \"body\": {{ JSON.stringify($('Procesar Respuesta IA').first().json.reply) }} }\n}",
        "options": {
          "timeout": 10000
        }
      },
      "id": "95c88a6f-aace-4ca1-a766-df63d3cf718b",
      "name": "Enviar Respuesta WhatsApp",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        2380,
        420
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "OK",
        "options": {
          "responseCode": 200
        }
      },
      "id": "52179986-77fc-46b6-b3a9-f47ab45f9bb2",
      "name": "Confirmar Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        2600,
        420
      ]
    }
  ],
  "connections": {
    "WhatsApp Webhook": {
      "main": [
        [
          {
            "node": "Parsear Mensaje",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parsear Mensaje": {
      "main": [
        [
          {
            "node": "\u00bfMensaje V\u00e1lido?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u00bfMensaje V\u00e1lido?": {
      "main": [
        [
          {
            "node": "Obtener Sesi\u00f3n Redis",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Responder 200 OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtener Sesi\u00f3n Redis": {
      "main": [
        [
          {
            "node": "Construir Sesi\u00f3n",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Construir Sesi\u00f3n": {
      "main": [
        [
          {
            "node": "Preparar Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Prompt": {
      "main": [
        [
          {
            "node": "OpenAI GPT-4o",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI GPT-4o": {
      "main": [
        [
          {
            "node": "Procesar Respuesta IA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Procesar Respuesta IA": {
      "main": [
        [
          {
            "node": "Guardar Sesi\u00f3n Redis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar Sesi\u00f3n Redis": {
      "main": [
        [
          {
            "node": "\u00bfPedido Confirmado?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u00bfPedido Confirmado?": {
      "main": [
        [
          {
            "node": "Guardar Pedido PostgreSQL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Enviar Respuesta WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar Pedido PostgreSQL": {
      "main": [
        [
          {
            "node": "Enviar Respuesta WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enviar Respuesta WhatsApp": {
      "main": [
        [
          {
            "node": "Confirmar Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true
  },
  "staticData": null,
  "meta": {
    "templateCredsSetupCompleted": false
  }
}