{
  "updatedAt": "2026-05-02T18:24:35.918Z",
  "createdAt": "2026-05-02T02:09:28.457Z",
  "id": "CjS3Gm0863S4j9Ij",
  "name": "Trato Hecho - Chat Agent",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "chat",
        "responseMode": "responseNode",
        "options": {
          "allowedOrigins": "*"
        }
      },
      "id": "95504852-22df-4116-8621-7d3828534d55",
      "name": "Webhook Chat",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -1808,
        448
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json;\nconst body = raw.body || raw;\n\nconst uuid = String(body.sessionId || body.uuid || '').trim()\n             || 'auto_' + Date.now() + '_' + Math.random().toString(36).substr(2,6);\n\nconst message = String(body.message || body.chatInput || '').trim();\n\nif (!message) throw new Error('message es requerido');\n\nlet bodyHistory = [];\ntry {\n  if (Array.isArray(body.history)) bodyHistory = body.history;\n} catch(e) { bodyHistory = []; }\n\nreturn [{ json: { uuid, message, bodyHistory } }];"
      },
      "id": "2ff5e22d-da17-4d23-aa49-29b7af8c560a",
      "name": "Extraer Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1600,
        448
      ]
    },
    {
      "parameters": {
        "jsCode": "const extract = $('Extraer Input').first().json;\nconst { uuid, message, bodyHistory } = extract;\n\n// \u2500\u2500 1. HISTORIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet history = [];\ntry {\n  const raw = $('GET Historial Supabase').first()?.json;\n  if (Array.isArray(raw) && raw.length > 0 && Array.isArray(raw[0].messages)) {\n    history = raw[0].messages;\n  } else if (raw && Array.isArray(raw.messages)) {\n    history = raw.messages;\n  }\n} catch(e) {}\nif (!Array.isArray(history) || history.length === 0) {\n  if (Array.isArray(bodyHistory) && bodyHistory.length > 0) history = bodyHistory;\n}\nhistory = history.filter(m => m && m.content);\n\n// \u2500\u2500 2. COTIZACI\u00d3N EXISTENTE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet quoteContext = '';\ntry {\n  const raw = $('GET Cotizaci\u00f3n Supabase').first()?.json;\n  const q = Array.isArray(raw) && raw.length > 0 ? raw[0]\n            : (raw && raw.numero ? raw : null);\n  if (q) {\n    quoteContext = '\\n\\nCOTIZACI\u00d3N EXISTENTE #' + q.numero\n      + ': ' + q.producto\n      + ', ' + q.m2 + ' m\u00b2'\n      + ', instalacion=' + (q.instalacion ? 'SI' : 'NO')\n      + ', total=$' + Number(q.total).toLocaleString('es-CL')\n      + '. Estado: ' + q.estado + '.';\n  }\n} catch(e) {}\n\n// \u2500\u2500 3. AN\u00c1LISIS DEL HISTORIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Estado seg\u00fan \u00faltimo mensaje del asistente\nconst lastAssistant = [...history].reverse().find(m => m.role === 'assistant');\nconst lastAsstMsg = lastAssistant ? lastAssistant.content.toLowerCase() : '';\n\nconst estadoPidiendoNombre      = /nombre completo|tu nombre|c\u00f3mo te llamas|cu\u00e1l es tu nombre/.test(lastAsstMsg);\nconst estadoPidiendoRut         = /\\brut\\b/.test(lastAsstMsg) && !/direcci/.test(lastAsstMsg);\nconst estadoPidiendoDireccion   = /direcci/.test(lastAsstMsg);\nconst estadoEnDatos             = estadoPidiendoNombre || estadoPidiendoRut || estadoPidiendoDireccion;\nconst estadoPidiendoInstalacion = /instalaci/i.test(lastAsstMsg) && /\\?\\s*$|si o no|responde si/i.test(lastAsstMsg);\n\n// Texto completo del historial + mensaje actual (para detecci\u00f3n de keywords)\nconst historialTexto = history.map(m => m.content).join(' ').toLowerCase() + ' ' + message.toLowerCase();\n\n// \u2500\u2500 Detecci\u00f3n de tipo de producto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst PRODUCTOS = {\n  'Luxury Emerald (40mm)':    { keywords: ['luxury', 'emerald', '40mm'], precio: 28500 },\n  'Soft Touch (30mm)':        { keywords: ['soft touch', '30mm', 'suave', 'residencial'], precio: 22900 },\n  'Pet-Friendly Turf (35mm)': { keywords: ['pet', 'friendly', '35mm', 'mascota', 'perro', 'gato'], precio: 26000 },\n};\n\nlet productoNombre = null;\nlet precioPorM2 = 0;\nlet tipoCancha = '';\n\n// Detectar si es deportivo y qu\u00e9 deporte espec\u00edfico\nconst esDeportivo = /deportivo|f\u00fatbol|futbol|tenis|p\u00e1del|padel|cancha/.test(historialTexto);\nif (esDeportivo) {\n  if (/f\u00fatbol 7|futbol 7/.test(historialTexto))         { tipoCancha = 'F\u00fatbol 7';  productoNombre = 'Pasto Deportivo F\u00fatbol 7'; }\n  else if (/f\u00fatbol 11|futbol 11/.test(historialTexto))  { tipoCancha = 'F\u00fatbol 11'; productoNombre = 'Pasto Deportivo F\u00fatbol 11'; }\n  else if (/f\u00fatbol|futbol/.test(historialTexto))        { tipoCancha = 'F\u00fatbol';    productoNombre = 'Pasto Deportivo F\u00fatbol'; }\n  else if (/tenis/.test(historialTexto))                { tipoCancha = 'Tenis';     productoNombre = 'Pasto Tenis & P\u00e1del'; }\n  else if (/p\u00e1del|padel/.test(historialTexto))          { tipoCancha = 'P\u00e1del';     productoNombre = 'Pasto Tenis & P\u00e1del'; }\n  // precioPorM2 = 0 para deportivos (precio a cotizar)\n} else {\n  for (const [nombre, info] of Object.entries(PRODUCTOS)) {\n    if (info.keywords.some(k => historialTexto.includes(k))) {\n      productoNombre = nombre;\n      precioPorM2 = info.precio;\n      break;\n    }\n  }\n}\n\n// \u2500\u2500 Detecci\u00f3n de m\u00b2 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Desactivada durante captura de nombre/RUT/direcci\u00f3n\nlet m2Final = 0;\nif (!estadoEnDatos) {\n  // Formato \"5x8\", \"5 por 8\", \"5 * 8\"\n  const regexDim = /(\\d+(?:[.,]\\d+)?)\\s*(?:x|\u00d7|por|\\*)\\s*(\\d+(?:[.,]\\d+)?)/i;\n  const matchDim = message.match(regexDim);\n  if (matchDim) {\n    m2Final = parseFloat(matchDim[1].replace(',', '.')) * parseFloat(matchDim[2].replace(',', '.'));\n  }\n  // N\u00famero solo o con unidad\n  if (!m2Final) {\n    const matchM2 = message.trim().match(/^(\\d+(?:[.,]\\d+)?)\\s*(?:m2|m\u00b2|mt2|mt|mts|metros?)?\\s*$/i);\n    if (matchM2) m2Final = parseFloat(matchM2[1].replace(',', '.'));\n  }\n}\n// Buscar m\u00b2 en historial si no se encontr\u00f3 en el mensaje actual\nif (!m2Final) {\n  for (const turn of [...history].reverse()) {\n    if (turn.role !== 'user') continue;\n    const dimM = turn.content.match(/(\\d+(?:[.,]\\d+)?)\\s*(?:x|\u00d7|por|\\*)\\s*(\\d+(?:[.,]\\d+)?)/i);\n    if (dimM) { m2Final = parseFloat(dimM[1].replace(',', '.')) * parseFloat(dimM[2].replace(',', '.')); break; }\n    const m2M = turn.content.trim().match(/^(\\d+(?:[.,]\\d+)?)\\s*(?:m2|m\u00b2|mt2|mt|mts|metros?)?\\s*$/i);\n    if (m2M) { m2Final = parseFloat(m2M[1].replace(',', '.')); break; }\n  }\n}\n\n// \u2500\u2500 Detecci\u00f3n de instalaci\u00f3n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet instalacionDefinida = null;\nfor (let i = history.length - 1; i >= 0; i--) {\n  const m = history[i];\n  if (m.role === 'assistant' && /instalaci/i.test(m.content) && /\\?\\s*$|si o no|responde si/i.test(m.content.toLowerCase())) {\n    if (i + 1 < history.length && history[i + 1].role === 'user') {\n      const resp = history[i + 1].content.toLowerCase().trim();\n      if (/^s[i\u00ed]\\b|con instalaci|quiero instalaci|necesito instalaci/.test(resp)) instalacionDefinida = true;\n      else if (/^no\\b|sin instalaci|no quiero|no necesito/.test(resp)) instalacionDefinida = false;\n    }\n    break;\n  }\n}\n// Verificar mensaje actual como respuesta a pregunta de instalaci\u00f3n\nif (instalacionDefinida === null && estadoPidiendoInstalacion) {\n  const resp = message.toLowerCase().trim();\n  if (/^s[i\u00ed]\\b|con instalaci|quiero instalaci|necesito instalaci/.test(resp)) instalacionDefinida = true;\n  else if (/^no\\b|sin instalaci|no quiero|no necesito/.test(resp)) instalacionDefinida = false;\n}\n\n// \u2500\u2500 Captura de datos del cliente desde historial \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet nombreCapturado = '';\nlet rutCapturado = '';\nlet direccionCapturada = '';\nfor (let i = 0; i < history.length - 1; i++) {\n  const aMsg = history[i];\n  const uMsg = history[i + 1];\n  if (aMsg.role !== 'assistant' || uMsg.role !== 'user') continue;\n  const aLow = aMsg.content.toLowerCase();\n  if (!nombreCapturado && /nombre completo|tu nombre|c\u00f3mo te llamas|cu\u00e1l es tu nombre/.test(aLow)) nombreCapturado = uMsg.content.trim();\n  if (!rutCapturado && /\\brut\\b/.test(aLow) && !/direcci/.test(aLow)) rutCapturado = uMsg.content.trim();\n  if (!direccionCapturada && /direcci/.test(aLow)) direccionCapturada = uMsg.content.trim();\n}\n\n// \u2500\u2500 C\u00e1lculo determin\u00edstico del total (server-side) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet m2Margen = 0, subtotalPasto = 0, subtotalInst = 0, totalFinal = 0;\nif (m2Final > 0) {\n  m2Margen      = Math.ceil(m2Final * 1.10);\n  subtotalPasto = m2Margen * precioPorM2;           // 0 para deportivos\n  subtotalInst  = (instalacionDefinida === true && precioPorM2 > 0) ? m2Margen * 4500 : 0;\n  totalFinal    = subtotalPasto + subtotalInst;\n}\n\n// \u2500\u2500 4. INSTRUCCI\u00d3N CR\u00cdTICA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet instruccionInmediata = '';\nconst msgLow = message.toLowerCase().trim();\n\n// Detectar si ya se inform\u00f3 el total o se pidi\u00f3 COTIZAR (para no repetir)\nconst yaSeInformoTotal = history.some(m =>\n  m.role === 'assistant' && /escribe cotizar|escribe \"cotizar\"/i.test(m.content)\n);\n// Detectar si ya se pregunt\u00f3 el deporte espec\u00edfico\nconst yaSePreguntoDeporte = history.some(m =>\n  m.role === 'assistant' && /f\u00fatbol 7|f\u00fatbol 11|tipo de cancha|qu\u00e9 tipo/i.test(m.content)\n);\n// Detectar si ya se preguntaron las dimensiones (deportivo)\nconst yaSePreguntoMedidas = history.some(m =>\n  m.role === 'assistant' && /dimensiones|largo.*ancho|medidas.*cancha/i.test(m.content)\n);\n\nif (estadoPidiendoDireccion && !direccionCapturada && nombreCapturado && rutCapturado && message.trim().length >= 5) {\n  // \u2192 Usuario dio la direcci\u00f3n: emitir [COTIZAR:]\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA \u2014 M\u00c1XIMA PRIORIDAD. Emite \u00daNICAMENTE esta l\u00ednea, sin ning\u00fan texto antes ni despu\u00e9s:\\n` +\n    `[COTIZAR: nombre=${nombreCapturado}, rut=${rutCapturado}, direccion=${message.trim()}, m2=${m2Final}, tipo=${productoNombre || 'No especificado'}, instalacion=${instalacionDefinida ? 'SI' : 'NO'}, total=${totalFinal}]`;\n\n} else if (estadoPidiendoRut && !rutCapturado) {\n  // \u2192 Usuario dio el RUT: pedir direcci\u00f3n\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente acaba de entregar su RUT. Responde EXACTAMENTE:\\n` +\n    `\"Gracias. \u00bfCu\u00e1l es la direcci\u00f3n de instalaci\u00f3n completa?\"`;\n\n} else if (estadoPidiendoNombre && !nombreCapturado) {\n  // \u2192 Usuario dio el nombre: pedir RUT\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente acaba de dar su nombre. Responde EXACTAMENTE:\\n` +\n    `\"Gracias. \u00bfCu\u00e1l es tu RUT? (formato: 12.345.678-9)\"`;\n\n} else if (/^cotizar$/.test(msgLow)) {\n  // \u2192 Cliente escribe COTIZAR: iniciar captura de datos\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente quiere cotizar. Responde EXACTAMENTE:\\n` +\n    `\"Perfecto. \u00bfCu\u00e1l es tu nombre completo?\"`;\n\n// \u2500\u2500 Flujo DEPORTIVO \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n} else if (esDeportivo && tipoCancha && m2Final > 0 && !yaSeInformoTotal) {\n  // \u2192 Tiene deporte + m\u00b2: mostrar resumen y pedir COTIZAR\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente quiere ${tipoCancha}. \u00c1rea confirmada: ${m2Final} m\u00b2 (con margen t\u00e9cnico: ${m2Margen} m\u00b2).\\n` +\n    `Responde con este resumen exacto:\\n` +\n    `\"Producto: ${productoNombre}\\n` +\n    `\u00c1rea: ${m2Final} m\u00b2 \u2192 con margen t\u00e9cnico: ${m2Margen} m\u00b2\\n` +\n    `Precio: a cotizar seg\u00fan especificaciones t\u00e9cnicas de la cancha.\\n` +\n    `Escribe COTIZAR para generar tu cotizaci\u00f3n formal.\"`;\n\n} else if (esDeportivo && tipoCancha && !m2Final && !yaSePreguntoMedidas) {\n  // \u2192 Tiene deporte pero no m\u00b2: pedir dimensiones\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente quiere ${tipoCancha}. Responde EXACTAMENTE:\\n` +\n    `\"\u00bfCu\u00e1les son las dimensiones de la cancha? (largo \u00d7 ancho en metros, ej: 50x40)\"`;\n\n} else if (esDeportivo && tipoCancha && !m2Final && yaSePreguntoMedidas) {\n  // \u2192 Ya se pregunt\u00f3 medidas, cliente est\u00e1 respondiendo de forma no num\u00e9rica\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: Est\u00e1s esperando las dimensiones de la cancha de ${tipoCancha} en metros. ` +\n    `Si el cliente no sabe, dile que puede estimar con largo \u00d7 ancho. No avances hasta tener las medidas.`;\n\n} else if (esDeportivo && !tipoCancha && !yaSePreguntoDeporte) {\n  // \u2192 Es deportivo pero no especific\u00f3 el deporte: preguntar\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente quiere cancha deportiva. Responde EXACTAMENTE:\\n` +\n    `\"\u00bfQu\u00e9 tipo de cancha necesitas? F\u00fatbol 7, F\u00fatbol 11, Tenis o P\u00e1del\"`;\n\n// \u2500\u2500 Flujo RESIDENCIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n} else if (productoNombre && m2Final > 0 && instalacionDefinida !== null && !yaSeInformoTotal) {\n  // \u2192 Tiene producto + m\u00b2 + instalaci\u00f3n: mostrar total y pedir COTIZAR\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: Muestra este resumen exacto y luego di \"Escribe COTIZAR para tu cotizaci\u00f3n formal.\":\\n` +\n    `Producto: ${productoNombre} \u2014 $${precioPorM2.toLocaleString('es-CL')}/m\u00b2\\n` +\n    `Superficie: ${m2Final} m\u00b2 \u2192 con margen t\u00e9cnico: ${m2Margen} m\u00b2\\n` +\n    `Subtotal pasto: ${m2Margen} \u00d7 $${precioPorM2.toLocaleString('es-CL')} = $${subtotalPasto.toLocaleString('es-CL')}\\n` +\n    (instalacionDefinida ? `Instalaci\u00f3n: ${m2Margen} \u00d7 $4.500 = $${subtotalInst.toLocaleString('es-CL')}\\n` : `Instalaci\u00f3n: no incluida\\n`) +\n    `TOTAL: $${totalFinal.toLocaleString('es-CL')}`;\n\n} else if (productoNombre && m2Final > 0 && instalacionDefinida === null && !estadoPidiendoInstalacion) {\n  // \u2192 Tiene producto + m\u00b2 pero no pregunt\u00f3 instalaci\u00f3n: mostrar subtotal y preguntar\n  const m2M = Math.ceil(m2Final * 1.10);\n  const subP = m2M * precioPorM2;\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: Muestra el desglose y pregunta por instalaci\u00f3n:\\n` +\n    `Producto: ${productoNombre} \u2014 $${precioPorM2.toLocaleString('es-CL')}/m\u00b2\\n` +\n    `Superficie: ${m2Final} m\u00b2 \u2192 con margen t\u00e9cnico: ${m2M} m\u00b2\\n` +\n    `Subtotal pasto: ${m2M} \u00d7 $${precioPorM2.toLocaleString('es-CL')} = $${subP.toLocaleString('es-CL')}\\n` +\n    `Luego pregunta EXACTAMENTE: \"\u00bfNecesita instalaci\u00f3n? (+$4.500/m\u00b2) \u2014 responde SI o NO\"`;\n}\n\n// \u2500\u2500 5. SYSTEM PROMPT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst systemPrompt = `Eres Queno, vendedor virtual de C\u00e9sped Sint\u00e9tico ARM en Melipilla, Chile.\nTu objetivo es vender c\u00e9sped sint\u00e9tico, tomar medidas, calcular precios y generar cotizaciones formales.\n${instruccionInmediata ? '\\n' + instruccionInmediata + '\\n\\nSI HAY UNA INSTRUCCI\u00d3N CR\u00cdTICA ARRIBA, S\u00cdGUELA AL PIE DE LA LETRA. No hagas nada m\u00e1s que lo que indica.\\n' : ''}${quoteContext}\n\nCAT\u00c1LOGO (precios CLP/m2, con IVA):\n- Luxury Emerald (40mm): $28.500/m2 \u2014 jard\u00edn o terraza premium\n- Soft Touch (30mm): $22.900/m2 \u2014 jard\u00edn residencial, suave al tacto\n- Pet-Friendly Turf (35mm): $26.000/m2 \u2014 jardines con mascotas, fibra antimicrobiana\n- Pasto Deportivo F\u00fatbol: precio a cotizar \u2014 canchas f\u00fatbol 11 y f\u00fatbol 7\n- Pasto Tenis & P\u00e1del: precio a cotizar \u2014 canchas tenis y p\u00e1del\n- Instalaci\u00f3n opcional: +$4.500/m2 (solo productos residenciales)\n\nFLUJO DE VENTA (solo si NO hay instrucci\u00f3n cr\u00edtica arriba):\n1. Sin producto \u2192 pregunta si es jard\u00edn/terraza o cancha deportiva\n2. Con producto residencial, sin m2 \u2192 pregunta cu\u00e1ntos m2 o dimensiones\n3. Con producto + m2 \u2192 muestra desglose y pregunta instalaci\u00f3n (SI o NO)\n4. Con instalaci\u00f3n definida \u2192 muestra total. Di: \"Escribe COTIZAR para cotizaci\u00f3n formal\"\n5. Producto deportivo sin deporte \u2192 pregunta: F\u00fatbol 7, F\u00fatbol 11, Tenis o P\u00e1del\n6. Deporte elegido, sin m2 \u2192 pregunta dimensiones (largo \u00d7 ancho en metros)\n7. Deporte + m2 \u2192 muestra resumen. Di: \"Escribe COTIZAR para cotizaci\u00f3n formal\"\n8. Cliente escribe COTIZAR \u2192 pide nombre completo\n9. Tras nombre \u2192 pide RUT (formato: 12.345.678-9)\n10. Tras RUT \u2192 pide direcci\u00f3n de instalaci\u00f3n completa\n11. Tras direcci\u00f3n \u2192 emite solo: [COTIZAR: nombre=X, rut=X, direccion=X, m2=X, tipo=X, instalacion=SI/NO, total=X]\n\nINTERPRETACI\u00d3N DE MEDIDAS:\n- N\u00famero solo (20) o con unidad (20m2) = m2 directamente\n- Dos n\u00fameros con x/por/* (50x40, 5 por 8) = largo \u00d7 ancho \u2192 calcula m2\n- EXCEPCI\u00d3N: en modo nombre/RUT/direcci\u00f3n, nunca interpretes texto como m2\n\nCUANDO EL CLIENTE NO SABE SUS MEDIDAS:\n- Explica brevemente c\u00f3mo medir y agrega al final: [CALCULAR]\n\nSI HAY COTIZACI\u00d3N EXISTENTE en el contexto:\n- Mu\u00e9strale los datos y ofrece proceder al pago: [PAGAR: numero=X, monto=X]\n\nREGLAS ESTRICTAS:\n- M\u00e1ximo 4 l\u00edneas por respuesta. Solo 1 pregunta por mensaje.\n- NUNCA saludar con \"Hola\" si ya hay historial.\n- NUNCA reiniciar el flujo ni volver a preguntar algo ya respondido.\n- NUNCA pedir datos ya entregados en el historial.\n- NUNCA calcular precios para productos deportivos (precio a cotizar).\n- NUNCA agregar texto antes o despu\u00e9s de [COTIZAR:] o [PAGAR:].\n- No interpretar RUT, nombre ni direcci\u00f3n como metros cuadrados.`;\n\n// \u2500\u2500 6. MENSAJES PARA CLAUDE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst messages = [\n  ...history.map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),\n  { role: 'user', content: message }\n];\n\n// \u2500\u2500 7. BODY PARA LA API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst claudeBody = JSON.stringify({\n  model: 'claude-sonnet-4-5',\n  max_tokens: 1024,\n  system: systemPrompt,\n  messages\n});\n\nreturn [{ json: { uuid, message, claudeBody, historyLength: history.length } }];\n"
      },
      "id": "742904d0-5ff8-436d-b70f-4a0ef3a56a1c",
      "name": "Construir Prompt Claude",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1024,
        448
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.claudeBody }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "7b976c3e-b321-43a7-9958-a4f0fd000a6a",
      "name": "Llamar Claude API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        -816,
        432
      ],
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        },
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const rawText = Array.isArray($json.content)\n  ? $json.content\n      .filter(b => b.type === 'text')\n      .map(b => b.text || '')\n      .join('\\n')\n  : String($json.content || '');\n\nconst cotizarRegex = /\\[COTIZAR:\\s*([\\s\\S]*?)\\]/i;\nconst pagarRegex = /\\[PAGAR:\\s*([\\s\\S]*?)\\]/i;\n\nconst cotizarMatch = rawText.match(cotizarRegex);\nconst pagarMatch = rawText.match(pagarRegex);\nconsole.log(\"muestra valor:\", rawText.match(cotizarRegex));\n\n\nfunction parseKeyValueBlock(blockText) {\n  const result = {};\n  const parts = blockText.split(/,\\s*(?=[a-zA-Z_]+\\s*=)/);\n\n  for (const part of parts) {\n    const idx = part.indexOf('=');\n    if (idx === -1) continue;\n    const key = part.slice(0, idx).trim().toLowerCase();\n    const value = part.slice(idx + 1).trim();\n    result[key] = value;\n  }\n\n  return result;\n}\n\nfunction limpiarNumero(str) {\n  return parseInt(String(str || '').replace(/[$.\\s]/g, '').replace(',', '.'), 10) || 0;\n}\n\nlet cleanText = rawText\n  .replace(cotizarRegex, '')\n  .replace(pagarRegex, '')\n  .trim();\n\nlet action = 'CHAT';\nlet actionData = null;\n\nif (cotizarMatch) {\n  const data = parseKeyValueBlock(cotizarMatch[1]);\n\n  action = 'COTIZAR';\n  actionData = {\n    nombre: data.nombre || '',\n    rut: data.rut || '',\n    direccion: data.direccion || '',\n    m2: parseFloat(data.m2 || '0') || 0,\n    tipo: data.tipo || '',\n    instalacion: String(data.instalacion || '').toUpperCase() === 'SI',\n    total: limpiarNumero(data.total),\n  };\n} else if (pagarMatch) {\n  const data = parseKeyValueBlock(pagarMatch[1]);\n\n  action = 'PAGAR';\n  actionData = {\n    numero: data.numero || '',\n    monto: limpiarNumero(data.monto),\n  };\n}\n\nreturn [\n  {\n    json: {\n      rawText,\n      cleanText,\n      action,\n      actionData,\n      debug: {\n        cotizarFound: !!cotizarMatch,\n        pagarFound: !!pagarMatch,\n        cotizarBlock: cotizarMatch ? cotizarMatch[1] : null,\n        pagarBlock: pagarMatch ? pagarMatch[1] : null,\n      }\n    }\n  }\n];"
      },
      "id": "e92d4f67-d377-4b34-899b-a0818086f295",
      "name": "Parsear Respuesta Claude",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -624,
        432
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $('Parsear Respuesta Claude').first().json.action }}",
                    "rightValue": "COTIZAR",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "c91ac9c7-7fb7-4c7b-89cb-48cafb69ec75"
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ $('Parsear Respuesta Claude').first().json.action }}",
                    "rightValue": "PAGAR",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "db93e55e-8522-4dd5-a01d-b8661b0c6ce8"
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "5e4b8335-a4bb-4ad8-94ca-33b344f23156",
      "name": "Switch Acci\u00f3n",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        -176,
        144
      ]
    },
    {
      "parameters": {
        "jsCode": "const parseData  = $('Parsear Respuesta Claude').first().json;\nconst actionData = parseData.actionData || {};\nconst uuid       = parseData.uuid || $('Extraer Input').first().json.uuid || 'sin-uuid';\nconst cleanText  = parseData.cleanText || '';\n\n// N\u00famero de cotizaci\u00f3n: COT-A\u00d1O-XXXXX (basado en timestamp)\nconst year   = new Date().getFullYear();\nconst seq    = String(Date.now()).slice(-5);\nconst numero = 'COT-' + year + '-' + seq;\n\nconst total      = Number(actionData.total) || 0;\nconst m2         = Number(actionData.m2) || 0;\nconst instalacion = (String(actionData.instalacion || '')).toUpperCase() === 'SI';\n\nconst quote = {\n  numero,\n  session_id:  uuid,\n  nombre:      actionData.nombre    || '',\n  rut:         actionData.rut       || '',\n  direccion:   actionData.direccion || '',\n  producto:    actionData.tipo      || '',\n  m2,\n  instalacion,\n  total,\n  estado: 'pendiente'\n};\n\nconst totalStr = total > 0\n  ? '$' + total.toLocaleString('es-CL')\n  : 'a cotizar';\n\nconst message = '\u2705 Cotizaci\u00f3n generada!\\n'\n  + '\ud83d\udccb N\u00b0 ' + numero + '\\n'\n  + '\ud83d\udc64 ' + quote.nombre + ' \u2014 ' + quote.rut + '\\n'\n  + '\ud83d\udccd ' + quote.direccion + '\\n'\n  + '\ud83c\udf3f ' + quote.producto + ' \u2014 ' + m2 + ' m\u00b2\\n'\n  + (instalacion ? '\ud83d\udd27 Con instalaci\u00f3n\\n' : '')\n  + '\ud83d\udcb0 Total: ' + totalStr + '\\n\\n'\n  + 'Escribe PAGAR para continuar con el pago.';\n\nreturn [{ json: { quote, message, uuid, cleanText } }];"
      },
      "id": "8ae42054-a7a7-4111-a2ce-bcd6aa9a2763",
      "name": "Crear Cotizaci\u00f3n",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        256,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u2500\u2500 Respuesta al frontend con cotizaci\u00f3n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst quoteData = $('Crear Cotizaci\u00f3n').first().json;\n\nreturn [{\n  json: {\n    message: quoteData.message,\n    quote:   quoteData.quote,\n    paymentLink: null\n  }\n}];"
      },
      "id": "d31376b6-4f76-4751-9ee0-7894432c3f9c",
      "name": "Respuesta Cotizaci\u00f3n",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        688,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.mercadopago.com/checkout/preferences",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\n  \"items\": [\n    {\n      \"id\": \"123456\",\n      \"title\": \"Producto de Prueba\",\n      \"description\": \"Descripci\u00f3n del Producto\",\n      \"category_id\": \"producto\",\n      \"quantity\": 1,\n      \"currency_id\": \"ARS\",\n      \"unit_price\": 3500\n    }\n  ],\n  \"payer\": {\n    \"name\": \"Soledad\",\n    \"surname\": \"Gomez\",\n    \"email\": \"solgomez@gmail.com\",\n    \"phone\": {\n      \"area_code\": \"11\",\n      \"number\": 116565984\n    }\n  },\n  \"identification\": {\n    \"type\": \"DNI\",\n    \"number\": \"564654654\"\n  },\n  \"external_reference\": \"prueba_123\"\n}",
        "options": {
          "timeout": 15000
        }
      },
      "id": "1801e4f5-7826-41bd-9571-208938a1830b",
      "name": "Llamar MercadoPago",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        336,
        416
      ],
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// --- Respuesta al frontend con link de pago ---------\nconst mpResp = $input.first().json;\nconst parseData = $('Parsear Respuesta Claude').first().json;\n\n// Buscamos el link, ya sea producci\u00f3n o sandbox\nconst paymentLink = mpResp.init_point || mpResp.sandbox_init_point || null;\n\n// Acceso seguro a los datos de la cotizaci\u00f3n (si no existen, ponemos vac\u00edo)\nconst numeroCotizacion = 1/**parseData.actionData?.numero*/ || \"N/A\";\nconst textoLimpio = parseData.cleanText || \"\";\n\nconst message = paymentLink \n  ? `${textoLimpio}\\n\\n\ud83c\udfab **Orden #${numeroCotizacion}**\\n\u2705 \u00a1Listo! Aqu\u00ed est\u00e1 tu link para pagar de forma segura con Mercado Pago:\\n${paymentLink}`\n  : `${textoLimpio}\\n\\n\u26a0\ufe0f Hubo un problema al generar el link de pago. Por favor, cont\u00e1ctanos directamente.`;\n\nreturn [{\n  json: {\n    message,\n    quote: parseData.existingQuote || null,\n    paymentLink\n  }\n}];"
      },
      "id": "cc34fb13-2dfe-4d4d-b43d-f73d44266862",
      "name": "Respuesta Pago",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        416
      ]
    },
    {
      "parameters": {
        "jsCode": "const parseData = $('Parsear Respuesta Claude').first().json;\nreturn [{\n  json: {\n    message: parseData.cleanText || parseData.rawText || '',\n    quote: parseData.existingQuote || null,\n    paymentLink: null\n  }\n}];"
      },
      "id": "d8191df4-5d15-4be3-9249-7f96bbb97fed",
      "name": "Respuesta Simple",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        128,
        192
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "3799f2c6-4022-42ca-a01e-c0d1cacbaef4",
      "name": "Responder Webhook (Cotizar)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1360,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "a757e730-1d85-4dd3-9d96-fa7cab12206b",
      "name": "Responder Webhook (Pagar)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1392,
        416
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "c86ac9d9-6f40-4e3d-8594-6a3f7f205ded",
      "name": "Responder Webhook (Simple)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        416,
        192
      ]
    },
    {
      "parameters": {
        "chatId": "8605918152",
        "text": "=\ud83e\uddfe *Nueva Cotizaci\u00f3n Generada*  \ud83d\udccb N\u00famero: `{{ $('Crear Cotizaci\u00f3n').first().json.quote.numero }}` \ud83c\udf3f Producto: {{ $('Crear Cotizaci\u00f3n').first().json.quote.tipo }} \ud83d\udcd0 M\u00b2: {{ $('Crear Cotizaci\u00f3n').first().json.quote.m2 }} m\u00b2 \ud83d\udd27 Instalaci\u00f3n: {{ $('Crear Cotizaci\u00f3n').first().json.quote.instalacion ? 'S\u00ed' : 'No' }} \ud83d\udcb0 Total: ${{ Number($('Crear Cotizaci\u00f3n').first().json.quote.total).toLocaleString('es-CL') }} CLP \ud83d\udcc5 Fecha: {{ $('Crear Cotizaci\u00f3n').first().json.quote.fecha }} \ud83c\udd94 UUID cliente: `{{ $('Crear Cotizaci\u00f3n').first().json.uuid }}`",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        928,
        0
      ],
      "id": "b6b932b5-e2e1-4e6d-afd6-ee24617a9069",
      "name": "Telegram - MsgeCotiza",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "sendTo": "aromero.madrid@gmail.com",
        "subject": "Nueva Cotizacion de Queno",
        "message": "<div style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333;\">   <h2 style=\"color: #2e7d32;\">Resumen de tu Cotizaci\u00f3n</h2>   <p>Hola, adjuntamos los detalles de la cotizaci\u00f3n solicitada para tu proyecto:</p>      <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 20px;\">     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">N\u00famero:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.numero }}</td>     </tr>     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">Fecha:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.fecha }}</td>     </tr>     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">Superficie:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.m2 }} m\u00b2</td>     </tr>     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">Tipo de Pasto:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.tipo }}</td>     </tr>     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">Instalaci\u00f3n:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.instalacion ? 'Incluida' : 'No incluida' }}</td>     </tr>     <tr style=\"font-size: 1.2em; color: #2e7d32;\">       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold;\">TOTAL:</td>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold;\">${{ $json.quote.total.toLocaleString('es-CL') }} CLP</td>     </tr>   </table>    <p>Si deseas proceder con la compra, puedes hacerlo directamente a trav\u00e9s del link enviado por chat.</p>   <hr>   <p style=\"font-size: 0.9em; color: #777;\">C\u00e9sped Sint\u00e9tico SpA - Melipilla, Chile.</p> </div>",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1136,
        0
      ],
      "id": "ad075c19-1962-487a-9e36-52fa6e233936",
      "name": "MsjeCotiza",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "8605918152",
        "text": "=\ud83d\udcb3 *\u00a1Nueva Venta / Pago Iniciado!*  \ud83d\udccb Cotizaci\u00f3n: `{{ $json.actionData.numero }}` \ud83d\udcb0 Monto: ${{ Number($json.actionData.monto).toLocaleString('es-CL') }} CLP \ud83d\udd17 Link de pago: {{ $json.paymentLink \n\n 'No disponible' }} \ud83d\udcc5 Fecha: {{ new Date().toLocaleDateString('es-CL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        880,
        416
      ],
      "id": "05e84850-fc6b-4b1b-903c-9b52548fe386",
      "name": "Send a text message1",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "sendTo": "aromero.madrid@gmail.com",
        "subject": "Nueva Venta",
        "message": "<div style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; border: 1px solid #eee; padding: 20px;\">   <div style=\"text-align: center; background-color: #2e7d32; padding: 10px;\">     <h1 style=\"color: white; margin: 0;\">\u00a1Gracias por tu compra!</h1>   </div>      <p style=\"margin-top: 20px;\">Hola,</p>   <p>Hemos recibido correctamente el pago/confirmaci\u00f3n de tu pedido. A continuaci\u00f3n, te detallamos el resumen de tu compra para <strong>Melipilla</strong>:</p>      <div style=\"background-color: #f4f4f4; padding: 15px; border-radius: 5px; margin: 20px 0;\">     <p style=\"margin: 5px 0;\"><strong>Orden:</strong> {{ $json.quote.numero }}</p>     <p style=\"margin: 5px 0;\"><strong>Producto:</strong> {{ $json.quote.tipo }}</p>     <p style=\"margin: 5px 0;\"><strong>Superficie:</strong> {{ $json.quote.m2 }} m\u00b2</p>     <p style=\"margin: 5px 0;\"><strong>Instalaci\u00f3n:</strong> {{ $json.quote.instalacion ? '\u2705 Incluida' : '\u274c No incluida' }}</p>     <p style=\"margin: 5px 0; font-size: 1.2em; color: #2e7d32;\"><strong>Total Pagado: ${{ $json.quote.total.toLocaleString('es-CL') }} CLP</strong></p>   </div>    <h3 style=\"color: #2e7d32;\">\u00bfQu\u00e9 sigue ahora?</h3>   <ol>     <li>Nuestro equipo t\u00e9cnico revisar\u00e1 los detalles de tu proyecto.</li>     <li>Te contactaremos en las pr\u00f3ximas 24 horas h\u00e1biles para coordinar la entrega o la fecha de instalaci\u00f3n.</li>   </ol>    <p>Si tienes alguna duda, puedes responder a este correo o escribirnos directamente por WhatsApp.</p>      <hr style=\"border: 0; border-top: 1px solid #eee; margin: 30px 0;\">   <p style=\"font-size: 0.8em; color: #999; text-align: center;\">     C\u00e9sped Sint\u00e9tico SpA - Especialistas en \u00e1reas verdes para Melipilla y alrededores.   </p> </div>",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1120,
        416
      ],
      "id": "389a61ce-d59f-4e7e-8d1f-663e4e9b4d15",
      "name": "MsjeVenta1",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// \u2500\u2500 Respuesta cuando no hay cotizaci\u00f3n previa con monto \u2500\u2500\nconst data = $input.first().json;\n\nreturn [{\n  json: {\n    message: 'Revisar o generar cotizaci\u00f3n previa',\n    quote: data.existingQuote || null,\n    paymentLink: null\n  }\n}];"
      },
      "id": "d0fac113-7c3e-4299-8b1b-bf92b3a4c63e",
      "name": "Respuesta Sin Cotizaci\u00f3n1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        272,
        736
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "e80152a9-8670-4dbe-9572-7f214fdf5028",
      "name": "Responder Webhook (Sin Cotizaci\u00f3n)1",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        576,
        736
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "15d6dade-9d8e-4586-a118-3fa82746e800",
              "leftValue": "={{ $json.actionData.monto }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        32,
        432
      ],
      "id": "fd4800ff-78c5-4b01-ab51-6c30834bc2a2",
      "name": "If"
    },
    {
      "parameters": {
        "jsCode": "const extract = $('Extraer Input').first()?.json || {};\nconst parse   = $('Parsear Respuesta Claude').first()?.json || {};\n\nconst uuid         = extract.uuid || 'anonimo';\nconst userMsg      = extract.message || '';\n// rawText mantiene etiquetas [COTIZAR:] para que el estado no se rompa\nconst assistantMsg = parse.rawText || parse.cleanText || parse.fullResponse || '';\n\nlet history = [];\ntry {\n  const raw = $('GET Historial Supabase').first()?.json;\n  if (Array.isArray(raw) && raw.length > 0 && Array.isArray(raw[0].messages)) {\n    history = raw[0].messages;\n  } else if (raw && Array.isArray(raw.messages)) {\n    history = raw.messages;\n  }\n} catch(e) {}\n\nif (!Array.isArray(history) || history.length === 0) {\n  try {\n    const bh = extract.bodyHistory;\n    if (Array.isArray(bh)) history = bh;\n  } catch(e) {}\n}\n\nif (!Array.isArray(history)) history = [];\nhistory = history.filter(m => m && m.content);\n\nhistory.push({ role: 'user',      content: userMsg      });\nhistory.push({ role: 'assistant', content: assistantMsg });\nhistory = history.slice(-40);\n\nreturn [{ json: { uuid, history, message: userMsg, assistantMsg } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -432,
        432
      ],
      "id": "741ea0da-7694-420e-afd6-255415c84fcb",
      "name": "Preparar Historial"
    },
    {
      "parameters": {
        "url": "={{ 'https://enpzxntzphvezopbxbtx.supabase.co/rest/v1/historial?session_id=eq.' + $json.uuid + '&select=messages' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "<redacted-credential>"
            },
            {
              "name": "Authorization",
              "value": "<redacted-credential>"
            }
          ]
        },
        "options": {}
      },
      "id": "89ef7229-9790-46a7-b398-ffe362246ae9",
      "name": "GET Historial Supabase",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -1408,
        448
      ],
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "={{ 'https://enpzxntzphvezopbxbtx.supabase.co/rest/v1/cotizaciones?session_id=eq.' + $('Extraer Input').first().json.uuid + '&order=created_at.desc&limit=1&select=*' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "<redacted-credential>"
            },
            {
              "name": "Authorization",
              "value": "<redacted-credential>"
            }
          ]
        },
        "options": {}
      },
      "id": "97a5b356-2417-471c-836d-4ee2d1469e40",
      "name": "GET Cotizaci\u00f3n Supabase",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -1232,
        448
      ],
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://enpzxntzphvezopbxbtx.supabase.co/rest/v1/historial",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "<redacted-credential>"
            },
            {
              "name": "Authorization",
              "value": "<redacted-credential>"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "resolution=merge-duplicates"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ session_id: $json.uuid, messages: $json.history, updated_at: new Date().toISOString() }) }}",
        "options": {}
      },
      "id": "6eaa3201-8ab4-4cbb-b730-c344233e93bb",
      "name": "Guardar Historial Supabase",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -192,
        400
      ],
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://enpzxntzphvezopbxbtx.supabase.co/rest/v1/cotizaciones",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "<redacted-credential>"
            },
            {
              "name": "Authorization",
              "value": "<redacted-credential>"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "return=representation"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.quote) }}",
        "options": {}
      },
      "id": "c3ed2543-9ddb-4afe-8caa-00fc43597399",
      "name": "Guardar Cotizaci\u00f3n Supabase",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        480,
        0
      ],
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    }
  ],
  "connections": {
    "Webhook Chat": {
      "main": [
        [
          {
            "node": "Extraer Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extraer Input": {
      "main": [
        [
          {
            "node": "GET Historial Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET Historial Supabase": {
      "main": [
        [
          {
            "node": "GET Cotizaci\u00f3n Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET Cotizaci\u00f3n Supabase": {
      "main": [
        [
          {
            "node": "Construir Prompt Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Construir Prompt Claude": {
      "main": [
        [
          {
            "node": "Llamar Claude API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Llamar Claude API": {
      "main": [
        [
          {
            "node": "Parsear Respuesta Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parsear Respuesta Claude": {
      "main": [
        [
          {
            "node": "Preparar Historial",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Historial": {
      "main": [
        [
          {
            "node": "Guardar Historial Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar Historial Supabase": {
      "main": [
        [
          {
            "node": "Switch Acci\u00f3n",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch Acci\u00f3n": {
      "main": [
        [
          {
            "node": "Crear Cotizaci\u00f3n",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respuesta Simple",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear Cotizaci\u00f3n": {
      "main": [
        [
          {
            "node": "Guardar Cotizaci\u00f3n Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar Cotizaci\u00f3n Supabase": {
      "main": [
        [
          {
            "node": "Respuesta Cotizaci\u00f3n",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respuesta Cotizaci\u00f3n": {
      "main": [
        [
          {
            "node": "Telegram - MsgeCotiza",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram - MsgeCotiza": {
      "main": [
        [
          {
            "node": "MsjeCotiza",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MsjeCotiza": {
      "main": [
        [
          {
            "node": "Responder Webhook (Cotizar)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "Llamar MercadoPago",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respuesta Sin Cotizaci\u00f3n1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Llamar MercadoPago": {
      "main": [
        [
          {
            "node": "Respuesta Pago",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respuesta Pago": {
      "main": [
        [
          {
            "node": "Send a text message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a text message1": {
      "main": [
        [
          {
            "node": "MsjeVenta1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MsjeVenta1": {
      "main": [
        [
          {
            "node": "Responder Webhook (Pagar)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respuesta Sin Cotizaci\u00f3n1": {
      "main": [
        [
          {
            "node": "Responder Webhook (Sin Cotizaci\u00f3n)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respuesta Simple": {
      "main": [
        [
          {
            "node": "Responder Webhook (Simple)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false
  },
  "staticData": {},
  "meta": null,
  "versionId": "24bb9529-c7f0-4d8c-9918-2e52748b5b6a",
  "activeVersionId": "24bb9529-c7f0-4d8c-9918-2e52748b5b6a",
  "versionCounter": 176,
  "triggerCount": 1,
  "shared": [
    {
      "updatedAt": "2026-05-02T02:09:28.473Z",
      "createdAt": "2026-05-02T02:09:28.473Z",
      "role": "workflow:owner",
      "workflowId": "CjS3Gm0863S4j9Ij",
      "projectId": "WFAsewOsXFDwGJWF",
      "project": {
        "updatedAt": "2026-03-28T13:04:48.870Z",
        "createdAt": "2026-03-28T13:01:08.818Z",
        "id": "WFAsewOsXFDwGJWF",
        "name": "Gonzalo ghon.cbr@gmail.com <ghon.cbr@gmail.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "creatorId": "b4851c5e-a89a-4984-af61-213ba884aaeb"
      }
    }
  ],
  "tags": [
    {
      "updatedAt": "2026-03-28T13:23:08.567Z",
      "createdAt": "2026-03-28T13:23:08.567Z",
      "id": "JL63ldyzA1UQlelo",
      "name": "trato-hecho"
    },
    {
      "updatedAt": "2026-03-28T13:23:08.570Z",
      "createdAt": "2026-03-28T13:23:08.570Z",
      "id": "nnzNct4HPQf6Xwta",
      "name": "chat-agent"
    }
  ],
  "activeVersion": {
    "updatedAt": "2026-05-02T18:24:35.925Z",
    "createdAt": "2026-05-02T18:24:35.925Z",
    "versionId": "24bb9529-c7f0-4d8c-9918-2e52748b5b6a",
    "workflowId": "CjS3Gm0863S4j9Ij",
    "nodes": [
      {
        "parameters": {
          "httpMethod": "POST",
          "path": "chat",
          "responseMode": "responseNode",
          "options": {
            "allowedOrigins": "*"
          }
        },
        "id": "95504852-22df-4116-8621-7d3828534d55",
        "name": "Webhook Chat",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          -1808,
          448
        ],
        "webhookId": "7932d703-57b1-48cd-a334-4f11edcc1725"
      },
      {
        "parameters": {
          "jsCode": "const raw = $input.first().json;\nconst body = raw.body || raw;\n\nconst uuid = String(body.sessionId || body.uuid || '').trim()\n             || 'auto_' + Date.now() + '_' + Math.random().toString(36).substr(2,6);\n\nconst message = String(body.message || body.chatInput || '').trim();\n\nif (!message) throw new Error('message es requerido');\n\nlet bodyHistory = [];\ntry {\n  if (Array.isArray(body.history)) bodyHistory = body.history;\n} catch(e) { bodyHistory = []; }\n\nreturn [{ json: { uuid, message, bodyHistory } }];"
        },
        "id": "2ff5e22d-da17-4d23-aa49-29b7af8c560a",
        "name": "Extraer Input",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -1600,
          448
        ]
      },
      {
        "parameters": {
          "jsCode": "const extract = $('Extraer Input').first().json;\nconst { uuid, message, bodyHistory } = extract;\n\n// \u2500\u2500 1. HISTORIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet history = [];\ntry {\n  const raw = $('GET Historial Supabase').first()?.json;\n  if (Array.isArray(raw) && raw.length > 0 && Array.isArray(raw[0].messages)) {\n    history = raw[0].messages;\n  } else if (raw && Array.isArray(raw.messages)) {\n    history = raw.messages;\n  }\n} catch(e) {}\nif (!Array.isArray(history) || history.length === 0) {\n  if (Array.isArray(bodyHistory) && bodyHistory.length > 0) history = bodyHistory;\n}\nhistory = history.filter(m => m && m.content);\n\n// \u2500\u2500 2. COTIZACI\u00d3N EXISTENTE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet quoteContext = '';\ntry {\n  const raw = $('GET Cotizaci\u00f3n Supabase').first()?.json;\n  const q = Array.isArray(raw) && raw.length > 0 ? raw[0]\n            : (raw && raw.numero ? raw : null);\n  if (q) {\n    quoteContext = '\\n\\nCOTIZACI\u00d3N EXISTENTE #' + q.numero\n      + ': ' + q.producto\n      + ', ' + q.m2 + ' m\u00b2'\n      + ', instalacion=' + (q.instalacion ? 'SI' : 'NO')\n      + ', total=$' + Number(q.total).toLocaleString('es-CL')\n      + '. Estado: ' + q.estado + '.';\n  }\n} catch(e) {}\n\n// \u2500\u2500 3. AN\u00c1LISIS DEL HISTORIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Estado seg\u00fan \u00faltimo mensaje del asistente\nconst lastAssistant = [...history].reverse().find(m => m.role === 'assistant');\nconst lastAsstMsg = lastAssistant ? lastAssistant.content.toLowerCase() : '';\n\nconst estadoPidiendoNombre      = /nombre completo|tu nombre|c\u00f3mo te llamas|cu\u00e1l es tu nombre/.test(lastAsstMsg);\nconst estadoPidiendoRut         = /\\brut\\b/.test(lastAsstMsg) && !/direcci/.test(lastAsstMsg);\nconst estadoPidiendoDireccion   = /direcci/.test(lastAsstMsg);\nconst estadoEnDatos             = estadoPidiendoNombre || estadoPidiendoRut || estadoPidiendoDireccion;\nconst estadoPidiendoInstalacion = /instalaci/i.test(lastAsstMsg) && /\\?\\s*$|si o no|responde si/i.test(lastAsstMsg);\n\n// Texto completo del historial + mensaje actual (para detecci\u00f3n de keywords)\nconst historialTexto = history.map(m => m.content).join(' ').toLowerCase() + ' ' + message.toLowerCase();\n\n// \u2500\u2500 Detecci\u00f3n de tipo de producto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst PRODUCTOS = {\n  'Luxury Emerald (40mm)':    { keywords: ['luxury', 'emerald', '40mm'], precio: 28500 },\n  'Soft Touch (30mm)':        { keywords: ['soft touch', '30mm', 'suave', 'residencial'], precio: 22900 },\n  'Pet-Friendly Turf (35mm)': { keywords: ['pet', 'friendly', '35mm', 'mascota', 'perro', 'gato'], precio: 26000 },\n};\n\nlet productoNombre = null;\nlet precioPorM2 = 0;\nlet tipoCancha = '';\n\n// Detectar si es deportivo y qu\u00e9 deporte espec\u00edfico\nconst esDeportivo = /deportivo|f\u00fatbol|futbol|tenis|p\u00e1del|padel|cancha/.test(historialTexto);\nif (esDeportivo) {\n  if (/f\u00fatbol 7|futbol 7/.test(historialTexto))         { tipoCancha = 'F\u00fatbol 7';  productoNombre = 'Pasto Deportivo F\u00fatbol 7'; }\n  else if (/f\u00fatbol 11|futbol 11/.test(historialTexto))  { tipoCancha = 'F\u00fatbol 11'; productoNombre = 'Pasto Deportivo F\u00fatbol 11'; }\n  else if (/f\u00fatbol|futbol/.test(historialTexto))        { tipoCancha = 'F\u00fatbol';    productoNombre = 'Pasto Deportivo F\u00fatbol'; }\n  else if (/tenis/.test(historialTexto))                { tipoCancha = 'Tenis';     productoNombre = 'Pasto Tenis & P\u00e1del'; }\n  else if (/p\u00e1del|padel/.test(historialTexto))          { tipoCancha = 'P\u00e1del';     productoNombre = 'Pasto Tenis & P\u00e1del'; }\n  // precioPorM2 = 0 para deportivos (precio a cotizar)\n} else {\n  for (const [nombre, info] of Object.entries(PRODUCTOS)) {\n    if (info.keywords.some(k => historialTexto.includes(k))) {\n      productoNombre = nombre;\n      precioPorM2 = info.precio;\n      break;\n    }\n  }\n}\n\n// \u2500\u2500 Detecci\u00f3n de m\u00b2 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Desactivada durante captura de nombre/RUT/direcci\u00f3n\nlet m2Final = 0;\nif (!estadoEnDatos) {\n  // Formato \"5x8\", \"5 por 8\", \"5 * 8\"\n  const regexDim = /(\\d+(?:[.,]\\d+)?)\\s*(?:x|\u00d7|por|\\*)\\s*(\\d+(?:[.,]\\d+)?)/i;\n  const matchDim = message.match(regexDim);\n  if (matchDim) {\n    m2Final = parseFloat(matchDim[1].replace(',', '.')) * parseFloat(matchDim[2].replace(',', '.'));\n  }\n  // N\u00famero solo o con unidad\n  if (!m2Final) {\n    const matchM2 = message.trim().match(/^(\\d+(?:[.,]\\d+)?)\\s*(?:m2|m\u00b2|mt2|mt|mts|metros?)?\\s*$/i);\n    if (matchM2) m2Final = parseFloat(matchM2[1].replace(',', '.'));\n  }\n}\n// Buscar m\u00b2 en historial si no se encontr\u00f3 en el mensaje actual\nif (!m2Final) {\n  for (const turn of [...history].reverse()) {\n    if (turn.role !== 'user') continue;\n    const dimM = turn.content.match(/(\\d+(?:[.,]\\d+)?)\\s*(?:x|\u00d7|por|\\*)\\s*(\\d+(?:[.,]\\d+)?)/i);\n    if (dimM) { m2Final = parseFloat(dimM[1].replace(',', '.')) * parseFloat(dimM[2].replace(',', '.')); break; }\n    const m2M = turn.content.trim().match(/^(\\d+(?:[.,]\\d+)?)\\s*(?:m2|m\u00b2|mt2|mt|mts|metros?)?\\s*$/i);\n    if (m2M) { m2Final = parseFloat(m2M[1].replace(',', '.')); break; }\n  }\n}\n\n// \u2500\u2500 Detecci\u00f3n de instalaci\u00f3n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet instalacionDefinida = null;\nfor (let i = history.length - 1; i >= 0; i--) {\n  const m = history[i];\n  if (m.role === 'assistant' && /instalaci/i.test(m.content) && /\\?\\s*$|si o no|responde si/i.test(m.content.toLowerCase())) {\n    if (i + 1 < history.length && history[i + 1].role === 'user') {\n      const resp = history[i + 1].content.toLowerCase().trim();\n      if (/^s[i\u00ed]\\b|con instalaci|quiero instalaci|necesito instalaci/.test(resp)) instalacionDefinida = true;\n      else if (/^no\\b|sin instalaci|no quiero|no necesito/.test(resp)) instalacionDefinida = false;\n    }\n    break;\n  }\n}\n// Verificar mensaje actual como respuesta a pregunta de instalaci\u00f3n\nif (instalacionDefinida === null && estadoPidiendoInstalacion) {\n  const resp = message.toLowerCase().trim();\n  if (/^s[i\u00ed]\\b|con instalaci|quiero instalaci|necesito instalaci/.test(resp)) instalacionDefinida = true;\n  else if (/^no\\b|sin instalaci|no quiero|no necesito/.test(resp)) instalacionDefinida = false;\n}\n\n// \u2500\u2500 Captura de datos del cliente desde historial \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet nombreCapturado = '';\nlet rutCapturado = '';\nlet direccionCapturada = '';\nfor (let i = 0; i < history.length - 1; i++) {\n  const aMsg = history[i];\n  const uMsg = history[i + 1];\n  if (aMsg.role !== 'assistant' || uMsg.role !== 'user') continue;\n  const aLow = aMsg.content.toLowerCase();\n  if (!nombreCapturado && /nombre completo|tu nombre|c\u00f3mo te llamas|cu\u00e1l es tu nombre/.test(aLow)) nombreCapturado = uMsg.content.trim();\n  if (!rutCapturado && /\\brut\\b/.test(aLow) && !/direcci/.test(aLow)) rutCapturado = uMsg.content.trim();\n  if (!direccionCapturada && /direcci/.test(aLow)) direccionCapturada = uMsg.content.trim();\n}\n\n// \u2500\u2500 C\u00e1lculo determin\u00edstico del total (server-side) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet m2Margen = 0, subtotalPasto = 0, subtotalInst = 0, totalFinal = 0;\nif (m2Final > 0) {\n  m2Margen      = Math.ceil(m2Final * 1.10);\n  subtotalPasto = m2Margen * precioPorM2;           // 0 para deportivos\n  subtotalInst  = (instalacionDefinida === true && precioPorM2 > 0) ? m2Margen * 4500 : 0;\n  totalFinal    = subtotalPasto + subtotalInst;\n}\n\n// \u2500\u2500 4. INSTRUCCI\u00d3N CR\u00cdTICA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet instruccionInmediata = '';\nconst msgLow = message.toLowerCase().trim();\n\n// Detectar si ya se inform\u00f3 el total o se pidi\u00f3 COTIZAR (para no repetir)\nconst yaSeInformoTotal = history.some(m =>\n  m.role === 'assistant' && /escribe cotizar|escribe \"cotizar\"/i.test(m.content)\n);\n// Detectar si ya se pregunt\u00f3 el deporte espec\u00edfico\nconst yaSePreguntoDeporte = history.some(m =>\n  m.role === 'assistant' && /f\u00fatbol 7|f\u00fatbol 11|tipo de cancha|qu\u00e9 tipo/i.test(m.content)\n);\n// Detectar si ya se preguntaron las dimensiones (deportivo)\nconst yaSePreguntoMedidas = history.some(m =>\n  m.role === 'assistant' && /dimensiones|largo.*ancho|medidas.*cancha/i.test(m.content)\n);\n\nif (estadoPidiendoDireccion && !direccionCapturada && nombreCapturado && rutCapturado && message.trim().length >= 5) {\n  // \u2192 Usuario dio la direcci\u00f3n: emitir [COTIZAR:]\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA \u2014 M\u00c1XIMA PRIORIDAD. Emite \u00daNICAMENTE esta l\u00ednea, sin ning\u00fan texto antes ni despu\u00e9s:\\n` +\n    `[COTIZAR: nombre=${nombreCapturado}, rut=${rutCapturado}, direccion=${message.trim()}, m2=${m2Final}, tipo=${productoNombre || 'No especificado'}, instalacion=${instalacionDefinida ? 'SI' : 'NO'}, total=${totalFinal}]`;\n\n} else if (estadoPidiendoRut && !rutCapturado) {\n  // \u2192 Usuario dio el RUT: pedir direcci\u00f3n\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente acaba de entregar su RUT. Responde EXACTAMENTE:\\n` +\n    `\"Gracias. \u00bfCu\u00e1l es la direcci\u00f3n de instalaci\u00f3n completa?\"`;\n\n} else if (estadoPidiendoNombre && !nombreCapturado) {\n  // \u2192 Usuario dio el nombre: pedir RUT\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente acaba de dar su nombre. Responde EXACTAMENTE:\\n` +\n    `\"Gracias. \u00bfCu\u00e1l es tu RUT? (formato: 12.345.678-9)\"`;\n\n} else if (/^cotizar$/.test(msgLow)) {\n  // \u2192 Cliente escribe COTIZAR: iniciar captura de datos\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente quiere cotizar. Responde EXACTAMENTE:\\n` +\n    `\"Perfecto. \u00bfCu\u00e1l es tu nombre completo?\"`;\n\n// \u2500\u2500 Flujo DEPORTIVO \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n} else if (esDeportivo && tipoCancha && m2Final > 0 && !yaSeInformoTotal) {\n  // \u2192 Tiene deporte + m\u00b2: mostrar resumen y pedir COTIZAR\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente quiere ${tipoCancha}. \u00c1rea confirmada: ${m2Final} m\u00b2 (con margen t\u00e9cnico: ${m2Margen} m\u00b2).\\n` +\n    `Responde con este resumen exacto:\\n` +\n    `\"Producto: ${productoNombre}\\n` +\n    `\u00c1rea: ${m2Final} m\u00b2 \u2192 con margen t\u00e9cnico: ${m2Margen} m\u00b2\\n` +\n    `Precio: a cotizar seg\u00fan especificaciones t\u00e9cnicas de la cancha.\\n` +\n    `Escribe COTIZAR para generar tu cotizaci\u00f3n formal.\"`;\n\n} else if (esDeportivo && tipoCancha && !m2Final && !yaSePreguntoMedidas) {\n  // \u2192 Tiene deporte pero no m\u00b2: pedir dimensiones\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente quiere ${tipoCancha}. Responde EXACTAMENTE:\\n` +\n    `\"\u00bfCu\u00e1les son las dimensiones de la cancha? (largo \u00d7 ancho en metros, ej: 50x40)\"`;\n\n} else if (esDeportivo && tipoCancha && !m2Final && yaSePreguntoMedidas) {\n  // \u2192 Ya se pregunt\u00f3 medidas, cliente est\u00e1 respondiendo de forma no num\u00e9rica\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: Est\u00e1s esperando las dimensiones de la cancha de ${tipoCancha} en metros. ` +\n    `Si el cliente no sabe, dile que puede estimar con largo \u00d7 ancho. No avances hasta tener las medidas.`;\n\n} else if (esDeportivo && !tipoCancha && !yaSePreguntoDeporte) {\n  // \u2192 Es deportivo pero no especific\u00f3 el deporte: preguntar\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: El cliente quiere cancha deportiva. Responde EXACTAMENTE:\\n` +\n    `\"\u00bfQu\u00e9 tipo de cancha necesitas? F\u00fatbol 7, F\u00fatbol 11, Tenis o P\u00e1del\"`;\n\n// \u2500\u2500 Flujo RESIDENCIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n} else if (productoNombre && m2Final > 0 && instalacionDefinida !== null && !yaSeInformoTotal) {\n  // \u2192 Tiene producto + m\u00b2 + instalaci\u00f3n: mostrar total y pedir COTIZAR\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: Muestra este resumen exacto y luego di \"Escribe COTIZAR para tu cotizaci\u00f3n formal.\":\\n` +\n    `Producto: ${productoNombre} \u2014 $${precioPorM2.toLocaleString('es-CL')}/m\u00b2\\n` +\n    `Superficie: ${m2Final} m\u00b2 \u2192 con margen t\u00e9cnico: ${m2Margen} m\u00b2\\n` +\n    `Subtotal pasto: ${m2Margen} \u00d7 $${precioPorM2.toLocaleString('es-CL')} = $${subtotalPasto.toLocaleString('es-CL')}\\n` +\n    (instalacionDefinida ? `Instalaci\u00f3n: ${m2Margen} \u00d7 $4.500 = $${subtotalInst.toLocaleString('es-CL')}\\n` : `Instalaci\u00f3n: no incluida\\n`) +\n    `TOTAL: $${totalFinal.toLocaleString('es-CL')}`;\n\n} else if (productoNombre && m2Final > 0 && instalacionDefinida === null && !estadoPidiendoInstalacion) {\n  // \u2192 Tiene producto + m\u00b2 pero no pregunt\u00f3 instalaci\u00f3n: mostrar subtotal y preguntar\n  const m2M = Math.ceil(m2Final * 1.10);\n  const subP = m2M * precioPorM2;\n  instruccionInmediata =\n    `INSTRUCCI\u00d3N CR\u00cdTICA: Muestra el desglose y pregunta por instalaci\u00f3n:\\n` +\n    `Producto: ${productoNombre} \u2014 $${precioPorM2.toLocaleString('es-CL')}/m\u00b2\\n` +\n    `Superficie: ${m2Final} m\u00b2 \u2192 con margen t\u00e9cnico: ${m2M} m\u00b2\\n` +\n    `Subtotal pasto: ${m2M} \u00d7 $${precioPorM2.toLocaleString('es-CL')} = $${subP.toLocaleString('es-CL')}\\n` +\n    `Luego pregunta EXACTAMENTE: \"\u00bfNecesita instalaci\u00f3n? (+$4.500/m\u00b2) \u2014 responde SI o NO\"`;\n}\n\n// \u2500\u2500 5. SYSTEM PROMPT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst systemPrompt = `Eres Queno, vendedor virtual de C\u00e9sped Sint\u00e9tico ARM en Melipilla, Chile.\nTu objetivo es vender c\u00e9sped sint\u00e9tico, tomar medidas, calcular precios y generar cotizaciones formales.\n${instruccionInmediata ? '\\n' + instruccionInmediata + '\\n\\nSI HAY UNA INSTRUCCI\u00d3N CR\u00cdTICA ARRIBA, S\u00cdGUELA AL PIE DE LA LETRA. No hagas nada m\u00e1s que lo que indica.\\n' : ''}${quoteContext}\n\nCAT\u00c1LOGO (precios CLP/m2, con IVA):\n- Luxury Emerald (40mm): $28.500/m2 \u2014 jard\u00edn o terraza premium\n- Soft Touch (30mm): $22.900/m2 \u2014 jard\u00edn residencial, suave al tacto\n- Pet-Friendly Turf (35mm): $26.000/m2 \u2014 jardines con mascotas, fibra antimicrobiana\n- Pasto Deportivo F\u00fatbol: precio a cotizar \u2014 canchas f\u00fatbol 11 y f\u00fatbol 7\n- Pasto Tenis & P\u00e1del: precio a cotizar \u2014 canchas tenis y p\u00e1del\n- Instalaci\u00f3n opcional: +$4.500/m2 (solo productos residenciales)\n\nFLUJO DE VENTA (solo si NO hay instrucci\u00f3n cr\u00edtica arriba):\n1. Sin producto \u2192 pregunta si es jard\u00edn/terraza o cancha deportiva\n2. Con producto residencial, sin m2 \u2192 pregunta cu\u00e1ntos m2 o dimensiones\n3. Con producto + m2 \u2192 muestra desglose y pregunta instalaci\u00f3n (SI o NO)\n4. Con instalaci\u00f3n definida \u2192 muestra total. Di: \"Escribe COTIZAR para cotizaci\u00f3n formal\"\n5. Producto deportivo sin deporte \u2192 pregunta: F\u00fatbol 7, F\u00fatbol 11, Tenis o P\u00e1del\n6. Deporte elegido, sin m2 \u2192 pregunta dimensiones (largo \u00d7 ancho en metros)\n7. Deporte + m2 \u2192 muestra resumen. Di: \"Escribe COTIZAR para cotizaci\u00f3n formal\"\n8. Cliente escribe COTIZAR \u2192 pide nombre completo\n9. Tras nombre \u2192 pide RUT (formato: 12.345.678-9)\n10. Tras RUT \u2192 pide direcci\u00f3n de instalaci\u00f3n completa\n11. Tras direcci\u00f3n \u2192 emite solo: [COTIZAR: nombre=X, rut=X, direccion=X, m2=X, tipo=X, instalacion=SI/NO, total=X]\n\nINTERPRETACI\u00d3N DE MEDIDAS:\n- N\u00famero solo (20) o con unidad (20m2) = m2 directamente\n- Dos n\u00fameros con x/por/* (50x40, 5 por 8) = largo \u00d7 ancho \u2192 calcula m2\n- EXCEPCI\u00d3N: en modo nombre/RUT/direcci\u00f3n, nunca interpretes texto como m2\n\nCUANDO EL CLIENTE NO SABE SUS MEDIDAS:\n- Explica brevemente c\u00f3mo medir y agrega al final: [CALCULAR]\n\nSI HAY COTIZACI\u00d3N EXISTENTE en el contexto:\n- Mu\u00e9strale los datos y ofrece proceder al pago: [PAGAR: numero=X, monto=X]\n\nREGLAS ESTRICTAS:\n- M\u00e1ximo 4 l\u00edneas por respuesta. Solo 1 pregunta por mensaje.\n- NUNCA saludar con \"Hola\" si ya hay historial.\n- NUNCA reiniciar el flujo ni volver a preguntar algo ya respondido.\n- NUNCA pedir datos ya entregados en el historial.\n- NUNCA calcular precios para productos deportivos (precio a cotizar).\n- NUNCA agregar texto antes o despu\u00e9s de [COTIZAR:] o [PAGAR:].\n- No interpretar RUT, nombre ni direcci\u00f3n como metros cuadrados.`;\n\n// \u2500\u2500 6. MENSAJES PARA CLAUDE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst messages = [\n  ...history.map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),\n  { role: 'user', content: message }\n];\n\n// \u2500\u2500 7. BODY PARA LA API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst claudeBody = JSON.stringify({\n  model: 'claude-sonnet-4-5',\n  max_tokens: 1024,\n  system: systemPrompt,\n  messages\n});\n\nreturn [{ json: { uuid, message, claudeBody, historyLength: history.length } }];\n"
        },
        "id": "742904d0-5ff8-436d-b70f-4a0ef3a56a1c",
        "name": "Construir Prompt Claude",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -1024,
          448
        ]
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.anthropic.com/v1/messages",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "anthropic-version",
                "value": "2023-06-01"
              },
              {
                "name": "content-type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ $json.claudeBody }}",
          "options": {
            "timeout": 30000
          }
        },
        "id": "7b976c3e-b321-43a7-9958-a4f0fd000a6a",
        "name": "Llamar Claude API",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4,
        "position": [
          -816,
          432
        ],
        "credentials": {
          "httpBearerAuth": {
            "id": "TeM2S1g4Qzk06fx0",
            "name": "Bearer Auth account"
          },
          "httpHeaderAuth": {
            "id": "GoQq4Na0MeD7nLo1",
            "name": "Claude - Trato Hecho"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "const rawText = Array.isArray($json.content)\n  ? $json.content\n      .filter(b => b.type === 'text')\n      .map(b => b.text || '')\n      .join('\\n')\n  : String($json.content || '');\n\nconst cotizarRegex = /\\[COTIZAR:\\s*([\\s\\S]*?)\\]/i;\nconst pagarRegex = /\\[PAGAR:\\s*([\\s\\S]*?)\\]/i;\n\nconst cotizarMatch = rawText.match(cotizarRegex);\nconst pagarMatch = rawText.match(pagarRegex);\nconsole.log(\"muestra valor:\", rawText.match(cotizarRegex));\n\n\nfunction parseKeyValueBlock(blockText) {\n  const result = {};\n  const parts = blockText.split(/,\\s*(?=[a-zA-Z_]+\\s*=)/);\n\n  for (const part of parts) {\n    const idx = part.indexOf('=');\n    if (idx === -1) continue;\n    const key = part.slice(0, idx).trim().toLowerCase();\n    const value = part.slice(idx + 1).trim();\n    result[key] = value;\n  }\n\n  return result;\n}\n\nfunction limpiarNumero(str) {\n  return parseInt(String(str || '').replace(/[$.\\s]/g, '').replace(',', '.'), 10) || 0;\n}\n\nlet cleanText = rawText\n  .replace(cotizarRegex, '')\n  .replace(pagarRegex, '')\n  .trim();\n\nlet action = 'CHAT';\nlet actionData = null;\n\nif (cotizarMatch) {\n  const data = parseKeyValueBlock(cotizarMatch[1]);\n\n  action = 'COTIZAR';\n  actionData = {\n    nombre: data.nombre || '',\n    rut: data.rut || '',\n    direccion: data.direccion || '',\n    m2: parseFloat(data.m2 || '0') || 0,\n    tipo: data.tipo || '',\n    instalacion: String(data.instalacion || '').toUpperCase() === 'SI',\n    total: limpiarNumero(data.total),\n  };\n} else if (pagarMatch) {\n  const data = parseKeyValueBlock(pagarMatch[1]);\n\n  action = 'PAGAR';\n  actionData = {\n    numero: data.numero || '',\n    monto: limpiarNumero(data.monto),\n  };\n}\n\nreturn [\n  {\n    json: {\n      rawText,\n      cleanText,\n      action,\n      actionData,\n      debug: {\n        cotizarFound: !!cotizarMatch,\n        pagarFound: !!pagarMatch,\n        cotizarBlock: cotizarMatch ? cotizarMatch[1] : null,\n        pagarBlock: pagarMatch ? pagarMatch[1] : null,\n      }\n    }\n  }\n];"
        },
        "id": "e92d4f67-d377-4b34-899b-a0818086f295",
        "name": "Parsear Respuesta Claude",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -624,
          432
        ]
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 1
                  },
                  "conditions": [
                    {
                      "leftValue": "={{ $('Parsear Respuesta Claude').first().json.action }}",
                      "rightValue": "COTIZAR",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      },
                      "id": "c91ac9c7-7fb7-4c7b-89cb-48cafb69ec75"
                    }
                  ],
                  "combinator": "and"
                }
              },
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 1
                  },
                  "conditions": [
                    {
                      "leftValue": "={{ $('Parsear Respuesta Claude').first().json.action }}",
                      "rightValue": "PAGAR",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      },
                      "id": "db93e55e-8522-4dd5-a01d-b8661b0c6ce8"
                    }
                  ],
                  "combinator": "and"
                }
              }
            ]
          },
          "options": {
            "fallbackOutput": "extra"
          }
        },
        "id": "5e4b8335-a4bb-4ad8-94ca-33b344f23156",
        "name": "Switch Acci\u00f3n",
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3,
        "position": [
          -176,
          144
        ]
      },
      {
        "parameters": {
          "jsCode": "const parseData  = $('Parsear Respuesta Claude').first().json;\nconst actionData = parseData.actionData || {};\nconst uuid       = parseData.uuid || $('Extraer Input').first().json.uuid || 'sin-uuid';\nconst cleanText  = parseData.cleanText || '';\n\n// N\u00famero de cotizaci\u00f3n: COT-A\u00d1O-XXXXX (basado en timestamp)\nconst year   = new Date().getFullYear();\nconst seq    = String(Date.now()).slice(-5);\nconst numero = 'COT-' + year + '-' + seq;\n\nconst total      = Number(actionData.total) || 0;\nconst m2         = Number(actionData.m2) || 0;\nconst instalacion = (String(actionData.instalacion || '')).toUpperCase() === 'SI';\n\nconst quote = {\n  numero,\n  session_id:  uuid,\n  nombre:      actionData.nombre    || '',\n  rut:         actionData.rut       || '',\n  direccion:   actionData.direccion || '',\n  producto:    actionData.tipo      || '',\n  m2,\n  instalacion,\n  total,\n  estado: 'pendiente'\n};\n\nconst totalStr = total > 0\n  ? '$' + total.toLocaleString('es-CL')\n  : 'a cotizar';\n\nconst message = '\u2705 Cotizaci\u00f3n generada!\\n'\n  + '\ud83d\udccb N\u00b0 ' + numero + '\\n'\n  + '\ud83d\udc64 ' + quote.nombre + ' \u2014 ' + quote.rut + '\\n'\n  + '\ud83d\udccd ' + quote.direccion + '\\n'\n  + '\ud83c\udf3f ' + quote.producto + ' \u2014 ' + m2 + ' m\u00b2\\n'\n  + (instalacion ? '\ud83d\udd27 Con instalaci\u00f3n\\n' : '')\n  + '\ud83d\udcb0 Total: ' + totalStr + '\\n\\n'\n  + 'Escribe PAGAR para continuar con el pago.';\n\nreturn [{ json: { quote, message, uuid, cleanText } }];"
        },
        "id": "8ae42054-a7a7-4111-a2ce-bcd6aa9a2763",
        "name": "Crear Cotizaci\u00f3n",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          256,
          0
        ]
      },
      {
        "parameters": {
          "jsCode": "// \u2500\u2500 Respuesta al frontend con cotizaci\u00f3n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst quoteData = $('Crear Cotizaci\u00f3n').first().json;\n\nreturn [{\n  json: {\n    message: quoteData.message,\n    quote:   quoteData.quote,\n    paymentLink: null\n  }\n}];"
        },
        "id": "d31376b6-4f76-4751-9ee0-7894432c3f9c",
        "name": "Respuesta Cotizaci\u00f3n",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          688,
          0
        ]
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.mercadopago.com/checkout/preferences",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpBearerAuth",
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "{\n  \"items\": [\n    {\n      \"id\": \"123456\",\n      \"title\": \"Producto de Prueba\",\n      \"description\": \"Descripci\u00f3n del Producto\",\n      \"category_id\": \"producto\",\n      \"quantity\": 1,\n      \"currency_id\": \"ARS\",\n      \"unit_price\": 3500\n    }\n  ],\n  \"payer\": {\n    \"name\": \"Soledad\",\n    \"surname\": \"Gomez\",\n    \"email\": \"solgomez@gmail.com\",\n    \"phone\": {\n      \"area_code\": \"11\",\n      \"number\": 116565984\n    }\n  },\n  \"identification\": {\n    \"type\": \"DNI\",\n    \"number\": \"564654654\"\n  },\n  \"external_reference\": \"prueba_123\"\n}",
          "options": {
            "timeout": 15000
          }
        },
        "id": "1801e4f5-7826-41bd-9571-208938a1830b",
        "name": "Llamar MercadoPago",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4,
        "position": [
          336,
          416
        ],
        "credentials": {
          "httpBearerAuth": {
            "id": "TeM2S1g4Qzk06fx0",
            "name": "Bearer Auth account"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// --- Respuesta al frontend con link de pago ---------\nconst mpResp = $input.first().json;\nconst parseData = $('Parsear Respuesta Claude').first().json;\n\n// Buscamos el link, ya sea producci\u00f3n o sandbox\nconst paymentLink = mpResp.init_point || mpResp.sandbox_init_point || null;\n\n// Acceso seguro a los datos de la cotizaci\u00f3n (si no existen, ponemos vac\u00edo)\nconst numeroCotizacion = 1/**parseData.actionData?.numero*/ || \"N/A\";\nconst textoLimpio = parseData.cleanText || \"\";\n\nconst message = paymentLink \n  ? `${textoLimpio}\\n\\n\ud83c\udfab **Orden #${numeroCotizacion}**\\n\u2705 \u00a1Listo! Aqu\u00ed est\u00e1 tu link para pagar de forma segura con Mercado Pago:\\n${paymentLink}`\n  : `${textoLimpio}\\n\\n\u26a0\ufe0f Hubo un problema al generar el link de pago. Por favor, cont\u00e1ctanos directamente.`;\n\nreturn [{\n  json: {\n    message,\n    quote: parseData.existingQuote || null,\n    paymentLink\n  }\n}];"
        },
        "id": "cc34fb13-2dfe-4d4d-b43d-f73d44266862",
        "name": "Respuesta Pago",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          640,
          416
        ]
      },
      {
        "parameters": {
          "jsCode": "const parseData = $('Parsear Respuesta Claude').first().json;\nreturn [{\n  json: {\n    message: parseData.cleanText || parseData.rawText || '',\n    quote: parseData.existingQuote || null,\n    paymentLink: null\n  }\n}];"
        },
        "id": "d8191df4-5d15-4be3-9249-7f96bbb97fed",
        "name": "Respuesta Simple",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          128,
          192
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify($json) }}",
          "options": {
            "responseHeaders": {
              "entries": [
                {
                  "name": "Access-Control-Allow-Origin",
                  "value": "*"
                },
                {
                  "name": "Content-Type",
                  "value": "application/json"
                }
              ]
            }
          }
        },
        "id": "3799f2c6-4022-42ca-a01e-c0d1cacbaef4",
        "name": "Responder Webhook (Cotizar)",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1,
        "position": [
          1360,
          0
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify($json) }}",
          "options": {
            "responseHeaders": {
              "entries": [
                {
                  "name": "Access-Control-Allow-Origin",
                  "value": "*"
                },
                {
                  "name": "Content-Type",
                  "value": "application/json"
                }
              ]
            }
          }
        },
        "id": "a757e730-1d85-4dd3-9d96-fa7cab12206b",
        "name": "Responder Webhook (Pagar)",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1,
        "position": [
          1392,
          416
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify($json) }}",
          "options": {
            "responseHeaders": {
              "entries": [
                {
                  "name": "Access-Control-Allow-Origin",
                  "value": "*"
                },
                {
                  "name": "Content-Type",
                  "value": "application/json"
                }
              ]
            }
          }
        },
        "id": "c86ac9d9-6f40-4e3d-8594-6a3f7f205ded",
        "name": "Responder Webhook (Simple)",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1,
        "position": [
          416,
          192
        ]
      },
      {
        "parameters": {
          "chatId": "8605918152",
          "text": "=\ud83e\uddfe *Nueva Cotizaci\u00f3n Generada*  \ud83d\udccb N\u00famero: `{{ $('Crear Cotizaci\u00f3n').first().json.quote.numero }}` \ud83c\udf3f Producto: {{ $('Crear Cotizaci\u00f3n').first().json.quote.tipo }} \ud83d\udcd0 M\u00b2: {{ $('Crear Cotizaci\u00f3n').first().json.quote.m2 }} m\u00b2 \ud83d\udd27 Instalaci\u00f3n: {{ $('Crear Cotizaci\u00f3n').first().json.quote.instalacion ? 'S\u00ed' : 'No' }} \ud83d\udcb0 Total: ${{ Number($('Crear Cotizaci\u00f3n').first().json.quote.total).toLocaleString('es-CL') }} CLP \ud83d\udcc5 Fecha: {{ $('Crear Cotizaci\u00f3n').first().json.quote.fecha }} \ud83c\udd94 UUID cliente: `{{ $('Crear Cotizaci\u00f3n').first().json.uuid }}`",
          "additionalFields": {
            "parse_mode": "Markdown"
          }
        },
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          928,
          0
        ],
        "id": "b6b932b5-e2e1-4e6d-afd6-ee24617a9069",
        "name": "Telegram - MsgeCotiza",
        "webhookId": "c8d162e4-16be-491a-b8a4-16358efa73e7",
        "credentials": {
          "telegramApi": {
            "id": "telegram-trato-hecho-001",
            "name": "Telegram Bot - Trato Hecho"
          }
        }
      },
      {
        "parameters": {
          "sendTo": "aromero.madrid@gmail.com",
          "subject": "Nueva Cotizacion de Queno",
          "message": "<div style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333;\">   <h2 style=\"color: #2e7d32;\">Resumen de tu Cotizaci\u00f3n</h2>   <p>Hola, adjuntamos los detalles de la cotizaci\u00f3n solicitada para tu proyecto:</p>      <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 20px;\">     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">N\u00famero:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.numero }}</td>     </tr>     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">Fecha:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.fecha }}</td>     </tr>     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">Superficie:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.m2 }} m\u00b2</td>     </tr>     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">Tipo de Pasto:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.tipo }}</td>     </tr>     <tr>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; background-color: #f9f9f9;\">Instalaci\u00f3n:</td>       <td style=\"padding: 8px; border: 1px solid #ddd;\">{{ $json.quote.instalacion ? 'Incluida' : 'No incluida' }}</td>     </tr>     <tr style=\"font-size: 1.2em; color: #2e7d32;\">       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold;\">TOTAL:</td>       <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold;\">${{ $json.quote.total.toLocaleString('es-CL') }} CLP</td>     </tr>   </table>    <p>Si deseas proceder con la compra, puedes hacerlo directamente a trav\u00e9s del link enviado por chat.</p>   <hr>   <p style=\"font-size: 0.9em; color: #777;\">C\u00e9sped Sint\u00e9tico SpA - Melipilla, Chile.</p> </div>",
          "options": {}
        },
        "type": "n8n-nodes-base.gmail",
        "typeVersion": 2.2,
        "position": [
          1136,
          0
        ],
        "id": "ad075c19-1962-487a-9e36-52fa6e233936",
        "name": "MsjeCotiza",
        "webhookId": "20c551e2-7ae2-4ea4-b3f8-ae0037e13bca",
        "credentials": {
          "gmailOAuth2": {
            "id": "F7GcQZm8AEno5j25",
            "name": "Gmail account"
          }
        }
      },
      {
        "parameters": {
          "chatId": "8605918152",
          "text": "=\ud83d\udcb3 *\u00a1Nueva Venta / Pago Iniciado!*  \ud83d\udccb Cotizaci\u00f3n: `{{ $json.actionData.numero }}` \ud83d\udcb0 Monto: ${{ Number($json.actionData.monto).toLocaleString('es-CL') }} CLP \ud83d\udd17 Link de pago: {{ $json.paymentLink \n\n 'No disponible' }} \ud83d\udcc5 Fecha: {{ new Date().toLocaleDateString('es-CL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) }}",
          "additionalFields": {
            "parse_mode": "Markdown"
          }
        },
        "type": "n8n-nodes-base.telegram",
        "typeVersion": 1.2,
        "position": [
          880,
          416
        ],
        "id": "05e84850-fc6b-4b1b-903c-9b52548fe386",
        "name": "Send a text message1",
        "webhookId": "4cc1cf23-3935-442d-8289-c1298b1a739f",
        "credentials": {
          "telegramApi": {
            "id": "telegram-trato-hecho-001",
            "name": "Telegram Bot - Trato Hecho"
          }
        }
      },
      {
        "parameters": {
          "sendTo": "aromero.madrid@gmail.com",
          "subject": "Nueva Venta",
          "message": "<div style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; border: 1px solid #eee; padding: 20px;\">   <div style=\"text-align: center; background-color: #2e7d32; padding: 10px;\">     <h1 style=\"color: white; margin: 0;\">\u00a1Gracias por tu compra!</h1>   </div>      <p style=\"margin-top: 20px;\">Hola,</p>   <p>Hemos recibido correctamente el pago/confirmaci\u00f3n de tu pedido. A continuaci\u00f3n, te detallamos el resumen de tu compra para <strong>Melipilla</strong>:</p>      <div style=\"background-color: #f4f4f4; padding: 15px; border-radius: 5px; margin: 20px 0;\">     <p style=\"margin: 5px 0;\"><strong>Orden:</strong> {{ $json.quote.numero }}</p>     <p style=\"margin: 5px 0;\"><strong>Producto:</strong> {{ $json.quote.tipo }}</p>     <p style=\"margin: 5px 0;\"><strong>Superficie:</strong> {{ $json.quote.m2 }} m\u00b2</p>     <p style=\"margin: 5px 0;\"><strong>Instalaci\u00f3n:</strong> {{ $json.quote.instalacion ? '\u2705 Incluida' : '\u274c No incluida' }}</p>     <p style=\"margin: 5px 0; font-size: 1.2em; color: #2e7d32;\"><strong>Total Pagado: ${{ $json.quote.total.toLocaleString('es-CL') }} CLP</strong></p>   </div>    <h3 style=\"color: #2e7d32;\">\u00bfQu\u00e9 sigue ahora?</h3>   <ol>     <li>Nuestro equipo t\u00e9cnico revisar\u00e1 los detalles de tu proyecto.</li>     <li>Te contactaremos en las pr\u00f3ximas 24 horas h\u00e1biles para coordinar la entrega o la fecha de instalaci\u00f3n.</li>   </ol>    <p>Si tienes alguna duda, puedes responder a este correo o escribirnos directamente por WhatsApp.</p>      <hr style=\"border: 0; border-top: 1px solid #eee; margin: 30px 0;\">   <p style=\"font-size: 0.8em; color: #999; text-align: center;\">     C\u00e9sped Sint\u00e9tico SpA - Especialistas en \u00e1reas verdes para Melipilla y alrededores.   </p> </div>",
          "options": {}
        },
        "type": "n8n-nodes-base.gmail",
        "typeVersion": 2.2,
        "position": [
          1120,
          416
        ],
        "id": "389a61ce-d59f-4e7e-8d1f-663e4e9b4d15",
        "name": "MsjeVenta1",
        "webhookId": "3723d494-a673-47ee-905f-e9a955c19e6b",
        "credentials": {
          "gmailOAuth2": {
            "id": "F7GcQZm8AEno5j25",
            "name": "Gmail account"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// \u2500\u2500 Respuesta cuando no hay cotizaci\u00f3n previa con monto \u2500\u2500\nconst data = $input.first().json;\n\nreturn [{\n  json: {\n    message: 'Revisar o generar cotizaci\u00f3n previa',\n    quote: data.existingQuote || null,\n    paymentLink: null\n  }\n}];"
        },
        "id": "d0fac113-7c3e-4299-8b1b-bf92b3a4c63e",
        "name": "Respuesta Sin Cotizaci\u00f3n1",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          272,
          736
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify($json) }}",
          "options": {
            "responseHeaders": {
              "entries": [
                {
                  "name": "Access-Control-Allow-Origin",
                  "value": "*"
                },
                {
                  "name": "Content-Type",
                  "value": "application/json"
                }
              ]
            }
          }
        },
        "id": "e80152a9-8670-4dbe-9572-7f214fdf5028",
        "name": "Responder Webhook (Sin Cotizaci\u00f3n)1",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1,
        "position": [
          576,
          736
        ]
      },
      {
        "parameters": {
          "conditions": {
            "options": {
              "caseSensitive": true,
              "leftValue": "",
              "typeValidation": "strict",
              "version": 3
            },
            "conditions": [
              {
                "id": "15d6dade-9d8e-4586-a118-3fa82746e800",
                "leftValue": "={{ $json.actionData.monto }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ],
            "combinator": "and"
          },
          "options": {}
        },
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.3,
        "position": [
          32,
          432
        ],
        "id": "fd4800ff-78c5-4b01-ab51-6c30834bc2a2",
        "name": "If"
      },
      {
        "parameters": {
          "jsCode": "const extract = $('Extraer Input').first()?.json || {};\nconst parse   = $('Parsear Respuesta Claude').first()?.json || {};\n\nconst uuid         = extract.uuid || 'anonimo';\nconst userMsg      = extract.message || '';\n// rawText mantiene etiquetas [COTIZAR:] para que el estado no se rompa\nconst assistantMsg = parse.rawText || parse.cleanText || parse.fullResponse || '';\n\nlet history = [];\ntry {\n  const raw = $('GET Historial Supabase').first()?.json;\n  if (Array.isArray(raw) && raw.length > 0 && Array.isArray(raw[0].messages)) {\n    history = raw[0].messages;\n  } else if (raw && Array.isArray(raw.messages)) {\n    history = raw.messages;\n  }\n} catch(e) {}\n\nif (!Array.isArray(history) || history.length === 0) {\n  try {\n    const bh = extract.bodyHistory;\n    if (Array.isArray(bh)) history = bh;\n  } catch(e) {}\n}\n\nif (!Array.isArray(history)) history = [];\nhistory = history.filter(m => m && m.content);\n\nhistory.push({ role: 'user',      content: userMsg      });\nhistory.push({ role: 'assistant', content: assistantMsg });\nhistory = history.slice(-40);\n\nreturn [{ json: { uuid, history, message: userMsg, assistantMsg } }];"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -432,
          432
        ],
        "id": "741ea0da-7694-420e-afd6-255415c84fcb",
        "name": "Preparar Historial"
      },
      {
        "parameters": {
          "url": "={{ 'https://enpzxntzphvezopbxbtx.supabase.co/rest/v1/historial?session_id=eq.' + $json.uuid + '&select=messages' }}",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "apikey",
                "value": "<redacted-credential>"
              },
              {
                "name": "Authorization",
                "value": "<redacted-credential>"
              }
            ]
          },
          "options": {}
        },
        "id": "89ef7229-9790-46a7-b398-ffe362246ae9",
        "name": "GET Historial Supabase",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -1408,
          448
        ],
        "alwaysOutputData": true,
        "onError": "continueRegularOutput"
      },
      {
        "parameters": {
          "url": "={{ 'https://enpzxntzphvezopbxbtx.supabase.co/rest/v1/cotizaciones?session_id=eq.' + $('Extraer Input').first().json.uuid + '&order=created_at.desc&limit=1&select=*' }}",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "apikey",
                "value": "<redacted-credential>"
              },
              {
                "name": "Authorization",
                "value": "<redacted-credential>"
              }
            ]
          },
          "options": {}
        },
        "id": "97a5b356-2417-471c-836d-4ee2d1469e40",
        "name": "GET Cotizaci\u00f3n Supabase",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -1232,
          448
        ],
        "alwaysOutputData": true,
        "onError": "continueRegularOutput"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://enpzxntzphvezopbxbtx.supabase.co/rest/v1/historial",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "apikey",
                "value": "<redacted-credential>"
              },
              {
                "name": "Authorization",
                "value": "<redacted-credential>"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              },
              {
                "name": "Prefer",
                "value": "resolution=merge-duplicates"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ session_id: $json.uuid, messages: $json.history, updated_at: new Date().toISOString() }) }}",
          "options": {}
        },
        "id": "6eaa3201-8ab4-4cbb-b730-c344233e93bb",
        "name": "Guardar Historial Supabase",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -192,
          400
        ],
        "alwaysOutputData": true,
        "onError": "continueRegularOutput"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://enpzxntzphvezopbxbtx.supabase.co/rest/v1/cotizaciones",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "apikey",
                "value": "<redacted-credential>"
              },
              {
                "name": "Authorization",
                "value": "<redacted-credential>"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              },
              {
                "name": "Prefer",
                "value": "return=representation"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify($json.quote) }}",
          "options": {}
        },
        "id": "c3ed2543-9ddb-4afe-8caa-00fc43597399",
        "name": "Guardar Cotizaci\u00f3n Supabase",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          480,
          0
        ],
        "alwaysOutputData": true,
        "onError": "continueRegularOutput"
      }
    ],
    "connections": {
      "Webhook Chat": {
        "main": [
          [
            {
              "node": "Extraer Input",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Extraer Input": {
        "main": [
          [
            {
              "node": "GET Historial Supabase",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "GET Historial Supabase": {
        "main": [
          [
            {
              "node": "GET Cotizaci\u00f3n Supabase",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "GET Cotizaci\u00f3n Supabase": {
        "main": [
          [
            {
              "node": "Construir Prompt Claude",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Construir Prompt Claude": {
        "main": [
          [
            {
              "node": "Llamar Claude API",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Llamar Claude API": {
        "main": [
          [
            {
              "node": "Parsear Respuesta Claude",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Parsear Respuesta Claude": {
        "main": [
          [
            {
              "node": "Preparar Historial",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Preparar Historial": {
        "main": [
          [
            {
              "node": "Guardar Historial Supabase",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Guardar Historial Supabase": {
        "main": [
          [
            {
              "node": "Switch Acci\u00f3n",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Switch Acci\u00f3n": {
        "main": [
          [
            {
              "node": "Crear Cotizaci\u00f3n",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "If",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Respuesta Simple",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Crear Cotizaci\u00f3n": {
        "main": [
          [
            {
              "node": "Guardar Cotizaci\u00f3n Supabase",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Guardar Cotizaci\u00f3n Supabase": {
        "main": [
          [
            {
              "node": "Respuesta Cotizaci\u00f3n",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respuesta Cotizaci\u00f3n": {
        "main": [
          [
            {
              "node": "Telegram - MsgeCotiza",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Telegram - MsgeCotiza": {
        "main": [
          [
            {
              "node": "MsjeCotiza",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "MsjeCotiza": {
        "main": [
          [
            {
              "node": "Responder Webhook (Cotizar)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "If": {
        "main": [
          [
            {
              "node": "Llamar MercadoPago",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Respuesta Sin Cotizaci\u00f3n1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Llamar MercadoPago": {
        "main": [
          [
            {
              "node": "Respuesta Pago",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respuesta Pago": {
        "main": [
          [
            {
              "node": "Send a text message1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Send a text message1": {
        "main": [
          [
            {
              "node": "MsjeVenta1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "MsjeVenta1": {
        "main": [
          [
            {
              "node": "Responder Webhook (Pagar)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respuesta Sin Cotizaci\u00f3n1": {
        "main": [
          [
            {
              "node": "Responder Webhook (Sin Cotizaci\u00f3n)1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respuesta Simple": {
        "main": [
          [
            {
              "node": "Responder Webhook (Simple)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "authors": "Gonzalo ghon.cbr@gmail.com",
    "name": null,
    "description": null,
    "autosaved": false,
    "workflowPublishHistory": [
      {
        "createdAt": "2026-05-02T18:24:39.152Z",
        "id": 78,
        "workflowId": "CjS3Gm0863S4j9Ij",
        "versionId": "24bb9529-c7f0-4d8c-9918-2e52748b5b6a",
        "event": "activated",
        "userId": "b4851c5e-a89a-4984-af61-213ba884aaeb"
      }
    ]
  }
}