"""
create_agent.py — Crea todos los workflows del agente Richard (Fibromuebles) en n8n.

Uso:
  cd projects/fibromuebles-whatsapp-agent/workflows
  python3 create_agent.py

Requiere:
  - n8n corriendo en http://localhost:5678
  - API_KEY de n8n (ver Settings → API Keys en n8n UI)

Qué crea:
  1. tool-get-prices
  2. tool-calculate-budget
  3. tool-qualify-lead
  4. tool-escalate-to-edd
  5. tool-analyze-image
  6. tool-transcribe-audio
  7. tool-generate-render
  8. tool-generate-budget
  9. whatsapp-agent-fibromuebles (SANDBOX) — agente principal con todos los tools

Después de ejecutar:
  - Completar credenciales en n8n UI (Anthropic, OpenAI para Whisper)
  - Reemplazar TU_NUMERO_ID, TU_TOKEN_AQUI, NUMERO_EDD_AQUI en tool-escalate-to-edd
  - Probar con: POST http://localhost:5678/webhook/fibromuebles-whatsapp-sandbox
"""

import json
import urllib.request
import urllib.error
import sys
import os

# ─────────────────────────────────────────
# CONFIGURACIÓN — Ajustar según el entorno
# ─────────────────────────────────────────
API_KEY = os.environ.get("N8N_API_KEY", "TU_N8N_API_KEY_AQUI")
BASE    = os.environ.get("N8N_BASE_URL", "http://localhost:5678") + "/api/v1"
CSV_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQZhfoC1sQzhDWnkMfeM0xFoceHFOl42fmqi1azku6xjnyxGA9fxMBodtErsARicX8HOMHPtIWeQ20p/pub?output=csv"


def api_post(path, data):
    body = json.dumps(data).encode()
    req = urllib.request.Request(
        BASE + path, data=body,
        headers={"X-N8N-API-KEY": API_KEY, "Content-Type": "application/json"}
    )
    try:
        with urllib.request.urlopen(req) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        print(f"  ERROR {e.code}: {e.read().decode()}", file=sys.stderr)
        raise


def create(wf):
    r = api_post("/workflows", wf)
    wf_id = r["id"]
    print(f"  Creado: {r['name']} → ID: {wf_id}")
    return wf_id


# ─────────────────────────────────────────
# CÓDIGO JS DE LOS CODE NODES
# Cargados desde archivos locales para facilitar mantenimiento.
# ─────────────────────────────────────────

def load_js(filename):
    """Carga el código JS desde la carpeta code-nodes/"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    filepath = os.path.join(script_dir, "code-nodes", filename)
    with open(filepath, "r", encoding="utf-8") as f:
        return f.read()


# Código inline compacto para tools que se ejecutan dentro del workflow principal
GET_PRICES_JS = (
    'const data = $input.first().json.data || "";\n'
    'let PRECIOS = {"3":4096,"5":5415,"9":7252,"12":9358,"15":11492,"18":13805,"22":18246,"25":20984,'
    '"3b":6500,"5b":9159,"12b":15147,"15b":16824,"18b":18914,"18c":25440,'
    '"p1x1":656,"p1x2":1312,"p1x3":1968,"p1x4":2624,"p2x2":2624};\n'
    'let HERRAJES = {"cazoleta":3000,"telescopica":18000,"n4":500,"rueda":3500,"bisagra":3000};\n'
    'data.split("\\n").forEach(line => {\n'
    '  const clean = line.replace(/"/g,"").trim();\n'
    '  if (!clean) return;\n'
    '  const sep = clean.includes(";") ? ";" : ",";\n'
    '  const parts = clean.split(sep);\n'
    '  if (parts.length >= 2) {\n'
    '    const k = parts[0].trim();\n'
    '    const raw = parts[1].trim().replace("$","").split(".").join("").replace(",",".");\n'
    '    const v = parseFloat(raw);\n'
    '    if (!isNaN(v) && k) {\n'
    '      if (PRECIOS[k] !== undefined) PRECIOS[k] = v;\n'
    '      else if (HERRAJES[k] !== undefined) HERRAJES[k] = v;\n'
    '      else PRECIOS[k] = v;\n'
    '    }\n'
    '  }\n'
    '});\n'
    'return [{json: {precios: PRECIOS, herrajes: HERRAJES}}];\n'
)

CALC_BUDGET_JS = (
    'let params = {};\n'
    'try { params = JSON.parse($("When Called by Tool").first().json.query || "{}"); } catch(e) {}\n'
    'const csvData = $input.first().json.data || "";\n'
    'let PRECIOS = {"3":4096,"5":5415,"9":7252,"12":9358,"15":11492,"18":13805,"22":18246,"25":20984,'
    '"3b":6500,"5b":9159,"12b":15147,"15b":16824,"18b":18914,"18c":25440,'
    '"p1x1":656,"p1x2":1312,"p1x3":1968,"p1x4":2624,"p2x2":2624};\n'
    'let HERRAJES = {"cazoleta":3000,"telescopica":18000,"n4":500,"rueda":3500,"bisagra":3000};\n'
    'csvData.split("\\n").forEach(line => {\n'
    '  const clean = line.replace(/"/g,"").trim(); if (!clean) return;\n'
    '  const sep = clean.includes(";") ? ";" : ","; const parts = clean.split(sep);\n'
    '  if (parts.length >= 2) {\n'
    '    const k = parts[0].trim(); const raw = parts[1].trim().replace("$","").split(".").join("").replace(",",".");\n'
    '    const v = parseFloat(raw); if (!isNaN(v) && k) { if (PRECIOS[k]!==undefined) PRECIOS[k]=v; else if (HERRAJES[k]!==undefined) HERRAJES[k]=v; }\n'
    '  }\n'
    '});\n'
    'const tag=params.tag||""; const cant=parseFloat(params.cant)||0;\n'
    'const a=parseFloat(String(params.a||"0").replace(",",".")); const b=parseFloat(String(params.b||"0").replace(",","."));\n'
    'const mat=params.mat||""; const canto=(params.canto||"").toLowerCase(); const pint=params.pint?parseInt(params.pint):0;\n'
    'const COEF_GENERAL=4805; const COEF_TROZADO=COEF_GENERAL/70000; const VALOR_TAPACANTO=18; const VALOR_PINTURA=25000;\n'
    'const COEFS={"#Corte":1.6,"#Estándar":2,"#Medida-MDF":2.6,"#Medida-Blanco":2.8,"#Medida-Color":3.3,"#Pino":2.1,"#Sobrante":1.0};\n'
    'const rnd=v=>Math.round(v/1000)*1000; const fmt=n=>"$"+rnd(n).toLocaleString("es-AR");\n'
    'let precio=0;\n'
    'if (cant>0 && tag) {\n'
    '  if (tag==="#Pino") { const largo=parseFloat(String(params.largo||"0").replace(",",".")); precio=(largo/100)*cant*(PRECIOS[mat]||0)*(COEFS[tag]||1); }\n'
    '  else { const sup=(a/100)*(b/100)*cant; const costoMat=sup*(PRECIOS[mat]||0);\n'
    '    let ml=0; if (canto) { const mX=(canto.match(/x/g)||[]).length; const mY=(canto.match(/y/g)||[]).length; ml=(a*mX*cant)+(b*mY*cant); }\n'
    '    const vTap=(ml/100)*VALOR_TAPACANTO*100; const vPint=pint?sup*pint*VALOR_PINTURA:0; const troz=cant*((a+b)*2)*COEF_TROZADO;\n'
    '    precio=(costoMat*(COEFS[tag]||1))+vTap+vPint+troz; }\n'
    '}\n'
    'const min=rnd(precio*0.85); const max=rnd(precio*1.15);\n'
    'return [{json:{precio_estimado:Math.round(precio),precio_formateado:fmt(precio),rango:fmt(min)+" a "+fmt(max),descripcion:cant+"x "+tag+" "+a+"x"+b+"cm "+mat,tag,cant,a,b,mat}}];\n'
)

QUALIFY_LEAD_JS = (
    'let params = {};\n'
    'try { params = JSON.parse($json.query || "{}"); } catch(e) {}\n'
    'const phone=params.phone||""; const name=params.name||"Sin nombre";\n'
    'const location=(params.location||"").toLowerCase(); const urgency=(params.urgency||"").toLowerCase();\n'
    'const hasDim=params.has_dimensions===true||params.has_dimensions==="true";\n'
    'let budget=0;\n'
    'if (typeof params.budget==="number") budget=params.budget;\n'
    'else { const c=String(params.budget||"0").replace(/\\$/g,"").split(".").join("").replace(",",".").replace(/k$/i,"000"); budget=parseFloat(c)||0; }\n'
    'let score=0; const reasons=[];\n'
    'if (location.includes("bah")&&(location.includes("blanca")||location.includes("bb"))) { score+=3; reasons.push("Bahia Blanca +3"); }\n'
    'else if (location&&!location.includes("no s")) { score+=1; reasons.push("Zona indicada +1"); }\n'
    'const hot=["esta semana","semana","urgente","dias","2 semanas","dos semanas"];\n'
    'const warm=["este mes","mes","30 dias","proximo mes"];\n'
    'if (hot.some(k=>urgency.includes(k))) { score+=3; reasons.push("Urgente +3"); }\n'
    'else if (warm.some(k=>urgency.includes(k))) { score+=1; reasons.push("Este mes +1"); }\n'
    'if (budget>=200000) { score+=2; reasons.push("Budget alto +2"); }\n'
    'else if (budget>=50000) { score+=1; reasons.push("Budget medio +1"); }\n'
    'if (hasDim) { score+=2; reasons.push("Tiene medidas +2"); }\n'
    'const status=score>=6?"hot":score>=3?"warm":"cold";\n'
    'const rec=score>=6?"Lead caliente — escalar a Edd ahora":score>=3?"Continuar calificando":"Lead frio, info general";\n'
    'return [{json:{score,qualified:score>=6,status,recommendation:rec,reasons,'
    'lead_data:{phone,name,score,product_interest:params.product_interest||"",'
    'budget,location:params.location||"",urgency:params.urgency||"",status}}}];\n'
)

ESCALATE_JS = (
    'let params = {};\n'
    'try { params = JSON.parse($json.query || "{}"); } catch(e) {}\n'
    'const name=params.name||"Cliente"; const phone=params.phone||"";\n'
    'const reason=params.reason||"Sin motivo"; const summary=params.summary||"Sin resumen";\n'
    'const msg="🔔 *Lead Fibromuebles — Escalamiento*\\n\\n👤 *Cliente:* "+name+"\\n📱 *Teléfono:* "+phone+'
    '"\\n⚡ *Motivo:* "+reason+"\\n\\n📋 *Resumen:*\\n"+summary+"\\n\\n_Enviado por Richard (agente automático)_";\n'
    'return [{json:{message:msg,phone,name,escalated:true}}];\n'
)

# Código para analyze_image — llama a Claude Vision API
ANALYZE_IMAGE_JS = (
    'let params = {};\n'
    'try { params = JSON.parse($json.query || "{}"); } catch(e) {}\n'
    'const mediaType = params.media_type || "image/jpeg";\n'
    'const context = params.context || "";\n'
    'const imageData = $binary?.data;\n'
    'if (!imageData) return [{json:{error:"Sin imagen",descripcion:"No se recibió imagen"}}];\n'
    'const base64Image = imageData.toString("base64");\n'
    'const anthropicKey = $env.ANTHROPIC_API_KEY || "";\n'
    'const response = await fetch("https://api.anthropic.com/v1/messages", {\n'
    '  method: "POST",\n'
    '  headers: {"Content-Type":"application/json","x-api-key":anthropicKey,"anthropic-version":"2023-06-01"},\n'
    '  body: JSON.stringify({\n'
    '    model: "claude-sonnet-4-6", max_tokens: 1024,\n'
    '    system: "Sos un asistente de Fibromuebles, fábrica de muebles en MDF y melamina en Bahía Blanca, Argentina. Analizás imágenes de muebles para ayudar a cotizar. Respondé siempre en español argentino.",\n'
    '    messages: [{\n'
    '      role: "user",\n'
    '      content: [\n'
    '        {type:"image",source:{type:"base64",media_type:mediaType,data:base64Image}},\n'
    '        {type:"text",text:"Analizá esta imagen" + (context ? " (contexto: \\\"" + context + "\\\")" : "") + ". Respondé en JSON: {descripcion, tipo_mueble, dimensiones_est, materiales_sug, estilo, colores, observaciones, prompt_render}"}\n'
    '      ]\n'
    '    }]\n'
    '  })\n'
    '});\n'
    'if (!response.ok) return [{json:{error:"Error Claude API: "+response.status}}];\n'
    'const result = await response.json();\n'
    'const rawText = result.content?.[0]?.text || "{}";\n'
    'let parsed = {};\n'
    'try { const m = rawText.match(/\\{[\\s\\S]*\\}/); if (m) parsed = JSON.parse(m[0]); } catch(e) { parsed = {descripcion:rawText}; }\n'
    'return [{json:{...parsed,raw:rawText}}];\n'
)

# Código para transcribe_audio — llama a Whisper API
TRANSCRIBE_AUDIO_JS = (
    'let params = {};\n'
    'try { params = JSON.parse($json.query || "{}"); } catch(e) {}\n'
    'const mediaType = params.media_type || "audio/ogg";\n'
    'const phone = params.phone || "desconocido";\n'
    'const audioBinary = $binary?.data;\n'
    'if (!audioBinary) return [{json:{error:"Sin audio",texto:"[No se pudo procesar el audio]"}}];\n'
    'const extMap = {"audio/ogg":"ogg","audio/mpeg":"mp3","audio/mp4":"m4a","audio/webm":"webm","audio/wav":"wav","audio/amr":"amr"};\n'
    'const ext = extMap[mediaType] || "ogg";\n'
    'const filename = "audio_" + phone + "_" + Date.now() + "." + ext;\n'
    'const formData = new FormData();\n'
    'const blob = new Blob([audioBinary], {type: mediaType});\n'
    'formData.append("file", blob, filename);\n'
    'formData.append("model", "whisper-1");\n'
    'formData.append("language", "es");\n'
    'formData.append("response_format", "verbose_json");\n'
    'const openaiKey = $env.OPENAI_API_KEY || "";\n'
    'const response = await fetch("https://api.openai.com/v1/audio/transcriptions", {\n'
    '  method: "POST",\n'
    '  headers: {"Authorization": "Bearer " + openaiKey},\n'
    '  body: formData\n'
    '});\n'
    'if (!response.ok) return [{json:{error:"Error Whisper API: "+response.status,texto:"[Error al transcribir]"}}];\n'
    'const result = await response.json();\n'
    'return [{json:{texto:result.text||"[Sin transcripción]",duracion_est:result.duration?Math.round(result.duration):null,idioma:result.language||"es"}}];\n'
)

# Código para generate_render — usa Pollinations.ai (sin API key)
GENERATE_RENDER_JS = (
    'let params = {};\n'
    'try { params = JSON.parse($json.query || "{}"); } catch(e) {}\n'
    'const tipoMueble = params.tipo_mueble || "furniture piece";\n'
    'const material = params.material || "white melamine";\n'
    'const color = params.color || "white";\n'
    'const dimensiones = params.dimensiones || "";\n'
    'const estilo = params.estilo || "modern";\n'
    'const descripcion = params.descripcion || "";\n'
    'const ambiente = params.ambiente || "living room";\n'
    'const estiloMap = {"moderno":"modern","minimalista":"minimalist","rústico":"rustic","industrial":"industrial","clásico":"classic","contemporáneo":"contemporary","escandinavo":"scandinavian"};\n'
    'const ambienteMap = {"dormitorio":"bedroom","living":"living room","cocina":"kitchen","oficina":"home office","comedor":"dining room","baño":"bathroom"};\n'
    'const materialMap = {"melamina blanca":"white melamine","melamina blanco":"white melamine","melamina color":"colored melamine","mdf crudo":"raw MDF wood","melamina gris":"gray melamine","melamina negra":"black melamine"};\n'
    'const estiloEn = estiloMap[estilo.toLowerCase()] || estilo;\n'
    'const ambienteEn = ambienteMap[ambiente.toLowerCase()] || ambiente;\n'
    'const materialEn = materialMap[material.toLowerCase()] || material;\n'
    'const parts = [estiloEn+" "+tipoMueble,"made of "+materialEn,color+" color",descripcion,dimensiones?"approximately "+dimensiones:"","placed in a "+ambienteEn,"photorealistic interior design render","professional furniture photography","high quality, detailed"].filter(Boolean);\n'
    'const prompt = parts.join(", ");\n'
    'const encodedPrompt = encodeURIComponent(prompt);\n'
    'const seed = Math.floor(Math.random() * 99999);\n'
    'const imageUrl = "https://image.pollinations.ai/prompt/" + encodedPrompt + "?width=1024&height=768&model=flux&nologo=true&seed=" + seed;\n'
    'return [{json:{imagen_url:imageUrl,prompt_usado:prompt,modelo:"flux (pollinations.ai)",seed}}];\n'
)

# Código para generate_budget — salida interna/private_only, no WhatsApp con precio
GENERATE_BUDGET_JS = (
    'let params = {};\n'
    'try { params = JSON.parse($json.query || "{}"); } catch(e) {}\n'
    'const cliente = params.cliente || "Cliente";\n'
    'const mueble = params.mueble || "Mueble a medida";\n'
    'const items = params.items || [];\n'
    'const total = items.reduce((s,i)=>s+(parseFloat(i.precio_total||i.subtotal_estimado)||0),0);\n'
    'const fmt = n => "$" + Math.round(n).toLocaleString("es-AR");\n'
    'const detalle = items.map(i => "- " + (i.descripcion||"Pieza") + ": " + fmt(i.precio_total||i.subtotal_estimado||0)).join("\\n");\n'
    'const nota = "PRESUPUESTO INTERNO FIBROMUEBLES\\n\\nCliente: " + cliente + "\\nPedido: " + mueble + "\\n\\nPiezas:\\n" + (detalle || "- Sin desglose") + "\\n\\nTotal interno estimado: " + fmt(total) + "\\npricing_visibility: private_only";\n'
    'return [{json:{pricing_visibility:"private_only",nota_privada:nota,total_interno_estimado:Math.round(total),mensaje_cliente_sin_precio:"Dale, ya le deje todo a Edelmar para que te pase el valor final por aca."}}];\n'
)

SYSTEM_PROMPT = (
    "Sos Richard, el asistente comercial de Fibromuebles. "
    "Fábrica de muebles a medida en MDF y melamina ubicada en Bahía Blanca, Argentina (Corrientes 289, www.fibromuebles.com.ar).\n\n"
    "## Tu personalidad\n"
    "- Hablás en castellano argentino coloquial. Usás \"che\", \"dale\", \"te cuento\", \"perfecto\".\n"
    "- Sos directo y concreto. Nada de \"¡Excelente pregunta!\" o frases serviles.\n"
    "- Mensajes cortos: máximo 4 líneas. Si hay mucho, dividilo en partes.\n"
    "- Si te preguntan si sos IA, desviás el tema o decís que sos del equipo de Fibromuebles.\n\n"
    "## Tu objetivo\n"
    "Entender qué necesita el cliente, deducir cortes, calcular sólo de forma interna y crear nota privada para Edd.\n"
    "Recolectá: qué mueble, medidas aprox, material, estilo, para cuándo, presupuesto, de dónde es.\n\n"
    "## Cómo manejar imágenes\n"
    "Si el input empieza con \"[imagen]\" o tiene media_type=image:\n"
    "1. Llamar a analyze_image con el media_url y media_type.\n"
    "2. Basarte en el análisis para preguntar lo que falta.\n"
    "3. Confirmale: \"Vi que mandaste una foto de [descripción]. ¿Eso es lo que querés?\"\n\n"
    "## Cómo manejar audios\n"
    "Si el input empieza con \"[audio]\" o tiene media_type=audio:\n"
    "1. Llamar a transcribe_audio con el media_url y media_type.\n"
    "2. Respondé basándote en la transcripción, sin mencionar que transcribiste.\n\n"
    "## Cuándo generar render\n"
    "Cuando tengas tipo de mueble + dimensiones + material. Preguntá: \"¿Querés que te muestre cómo quedaría?\"\n"
    "Llamar a generate_render con todos los datos disponibles.\n\n"
    "## Cuándo generar presupuesto formal\n"
    "Cuando hayas calculado con calculate_budget, llamar a generate_budget para el mensaje completo.\n\n"
    "## Herramientas\n"
    "- get_prices: precios actualizados (sin parámetros)\n"
    "- calculate_budget: precio por pieza. JSON: {{tag, cant, a, b, mat}}. "
    "Tags: #Medida-Blanco, #Medida-MDF, #Medida-Color, #Corte, #Estándar, #Pino, #Sobrante, #Herraje. "
    "Mat: 18b=melamina blanco 18mm, 18=MDF 18mm, 15b=blanco 15mm, 18c=color 18mm.\n"
    "- qualify_lead: JSON: {{phone, name, budget, location, urgency, product_interest, has_dimensions}}\n"
    "- escalate_to_edd: JSON: {{phone, name, reason, summary}}\n"
    "- analyze_image: JSON: {{media_url, media_type, context}}\n"
    "- transcribe_audio: JSON: {{media_url, media_type, phone}}\n"
    "- generate_render: JSON: {{tipo_mueble, material, color, dimensiones, estilo, descripcion, ambiente}}\n"
    "- generate_budget: JSON: {{cliente, mueble, items:[{{descripcion,cantidad,precio_total}}], render_url, notas, tiempo_entrega}}\n\n"
    "NUNCA des precio exacto sin llamar a calculate_budget. Precios siempre en ARS con ±15% de margen.\n\n"
    "## Cuándo escalar — llamar a escalate_to_edd si:\n"
    "- qualify_lead retorna score >= 6 (lead caliente)\n"
    "- El cliente quiere confirmar o cerrar un pedido\n"
    "- Reclamo o problema con pedido anterior\n"
    "- Cliente fuera de Bahía Blanca pregunta por flete/envío\n"
    "- Pedido complejo (cocina completa, local comercial, múltiples ambientes)\n"
    "- El cliente pide hablar con una persona\n"
    "Al escalar: \"Dale, te paso con Edd. Ya le mando tus datos y te contacta en el día. ¿Está bien?\"\n\n"
    "## Info del negocio\n"
    "- Materiales: MDF (3,5,9,12,15,18,22,25mm), Melamina blanco (3,5,12,15,18mm), Melamina color (18mm)\n"
    "- Entrega: 7-15 días hábiles. Equipo de 2 personas, no sobreprometás plazos.\n"
    "- Flete extra si el cliente es de fuera de Bahía Blanca. Siempre avisarlo.\n"
)


# ─────────────────────────────────────────
# 1. tool-get-prices
# ─────────────────────────────────────────
print("\n[1/9] Creando tool-get-prices...")
id_get_prices = create({
    "name": "fibro-tool-get-prices",
    "nodes": [
        {"id": "n1", "name": "When Called by Tool", "type": "n8n-nodes-base.executeWorkflowTrigger",
         "typeVersion": 1, "position": [240, 300], "parameters": {}},
        {"id": "n2", "name": "Fetch CSV", "type": "n8n-nodes-base.httpRequest",
         "typeVersion": 4.1, "position": [460, 300],
         "parameters": {"url": CSV_URL, "options": {}}},
        {"id": "n3", "name": "Parse Prices", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [680, 300],
         "parameters": {"jsCode": GET_PRICES_JS}}
    ],
    "connections": {
        "When Called by Tool": {"main": [[{"node": "Fetch CSV", "type": "main", "index": 0}]]},
        "Fetch CSV": {"main": [[{"node": "Parse Prices", "type": "main", "index": 0}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# 2. tool-calculate-budget
# ─────────────────────────────────────────
print("\n[2/9] Creando tool-calculate-budget...")
id_calc_budget = create({
    "name": "fibro-tool-calculate-budget",
    "nodes": [
        {"id": "n1", "name": "When Called by Tool", "type": "n8n-nodes-base.executeWorkflowTrigger",
         "typeVersion": 1, "position": [240, 300], "parameters": {}},
        {"id": "n2", "name": "Fetch CSV", "type": "n8n-nodes-base.httpRequest",
         "typeVersion": 4.1, "position": [460, 300],
         "parameters": {"url": CSV_URL, "options": {}}},
        {"id": "n3", "name": "Calculate Budget", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [680, 300],
         "parameters": {"jsCode": CALC_BUDGET_JS}}
    ],
    "connections": {
        "When Called by Tool": {"main": [[{"node": "Fetch CSV", "type": "main", "index": 0}]]},
        "Fetch CSV": {"main": [[{"node": "Calculate Budget", "type": "main", "index": 0}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# 3. tool-qualify-lead
# ─────────────────────────────────────────
print("\n[3/9] Creando tool-qualify-lead...")
id_qualify = create({
    "name": "fibro-tool-qualify-lead",
    "nodes": [
        {"id": "n1", "name": "When Called by Tool", "type": "n8n-nodes-base.executeWorkflowTrigger",
         "typeVersion": 1, "position": [240, 300], "parameters": {}},
        {"id": "n2", "name": "Qualify Lead", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [460, 300],
         "parameters": {"jsCode": QUALIFY_LEAD_JS}}
    ],
    "connections": {
        "When Called by Tool": {"main": [[{"node": "Qualify Lead", "type": "main", "index": 0}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# 4. tool-escalate-to-edd
# ─────────────────────────────────────────
print("\n[4/9] Creando tool-escalate-to-edd...")
id_escalate = create({
    "name": "fibro-tool-escalate-to-edd",
    "nodes": [
        {"id": "n1", "name": "When Called by Tool", "type": "n8n-nodes-base.executeWorkflowTrigger",
         "typeVersion": 1, "position": [240, 300], "parameters": {}},
        {"id": "n2", "name": "Build Message", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [460, 300],
         "parameters": {"jsCode": ESCALATE_JS}},
        # SANDBOX: simula el envío sin llamar a Meta API real
        {"id": "n3", "name": "Log Escalamiento", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [680, 300],
         "parameters": {"jsCode": (
             "const data = $input.first().json;\n"
             "return [{json:{...data, sandbox_note:'[SANDBOX] Escalamiento simulado — no se envió WhatsApp real', enviado:false}}];"
         )}}
    ],
    "connections": {
        "When Called by Tool": {"main": [[{"node": "Build Message", "type": "main", "index": 0}]]},
        "Build Message": {"main": [[{"node": "Log Escalamiento", "type": "main", "index": 0}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# 5. tool-analyze-image
# ─────────────────────────────────────────
print("\n[5/9] Creando tool-analyze-image...")
id_analyze_image = create({
    "name": "fibro-tool-analyze-image",
    "nodes": [
        {"id": "n1", "name": "When Called by Tool", "type": "n8n-nodes-base.executeWorkflowTrigger",
         "typeVersion": 1, "position": [240, 300], "parameters": {}},
        {"id": "n2", "name": "Download Image", "type": "n8n-nodes-base.httpRequest",
         "typeVersion": 4.1, "position": [460, 300],
         "parameters": {
             "url": "={{ (() => { const q = $json.query; if (typeof q === 'string') { try { const p = JSON.parse(q || '{}'); return p.media_url || p.audio_url || p.url || q; } catch (e) { const m = q.match(/https?:\\/\\/[^\\s\\\"'<>]+/); return m ? m[0] : q; } } return q?.media_url || q?.audio_url || q?.url || $json.media_url || $json.audio_url || $json.url; })() }}",
             "options": {"response": {"response": {"responseFormat": "file"}}}
         }},
        {"id": "n3", "name": "Analyze with Claude Vision", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [680, 300],
         "parameters": {"jsCode": ANALYZE_IMAGE_JS}}
    ],
    "connections": {
        "When Called by Tool": {"main": [[{"node": "Download Image", "type": "main", "index": 0}]]},
        "Download Image": {"main": [[{"node": "Analyze with Claude Vision", "type": "main", "index": 0}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# 6. tool-transcribe-audio
# ─────────────────────────────────────────
print("\n[6/9] Creando tool-transcribe-audio...")
id_transcribe = create({
    "name": "fibro-tool-transcribe-audio",
    "nodes": [
        {"id": "n1", "name": "When Called by Tool", "type": "n8n-nodes-base.executeWorkflowTrigger",
         "typeVersion": 1, "position": [240, 300], "parameters": {}},
        {"id": "n2", "name": "Download Audio", "type": "n8n-nodes-base.httpRequest",
         "typeVersion": 4.1, "position": [460, 300],
         "parameters": {
             "url": "={{ (() => { const q = $json.query; if (typeof q === 'string') { try { const p = JSON.parse(q || '{}'); return p.media_url || p.audio_url || p.url || q; } catch (e) { const m = q.match(/https?:\\/\\/[^\\s\\\"'<>]+/); return m ? m[0] : q; } } return q?.media_url || q?.audio_url || q?.url || $json.media_url || $json.audio_url || $json.url; })() }}",
             "options": {"response": {"response": {"responseFormat": "file"}}}
         }},
        {"id": "n3", "name": "Transcribe with Whisper", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [680, 300],
         "parameters": {"jsCode": TRANSCRIBE_AUDIO_JS}}
    ],
    "connections": {
        "When Called by Tool": {"main": [[{"node": "Download Audio", "type": "main", "index": 0}]]},
        "Download Audio": {"main": [[{"node": "Transcribe with Whisper", "type": "main", "index": 0}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# 7. tool-generate-render
# ─────────────────────────────────────────
print("\n[7/9] Creando tool-generate-render...")
id_render = create({
    "name": "fibro-tool-generate-render",
    "nodes": [
        {"id": "n1", "name": "When Called by Tool", "type": "n8n-nodes-base.executeWorkflowTrigger",
         "typeVersion": 1, "position": [240, 300], "parameters": {}},
        {"id": "n2", "name": "Generate Render URL", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [460, 300],
         "parameters": {"jsCode": GENERATE_RENDER_JS}}
    ],
    "connections": {
        "When Called by Tool": {"main": [[{"node": "Generate Render URL", "type": "main", "index": 0}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# 8. tool-generate-budget
# ─────────────────────────────────────────
print("\n[8/9] Creando tool-generate-budget...")
id_budget = create({
    "name": "fibro-tool-generate-budget",
    "nodes": [
        {"id": "n1", "name": "When Called by Tool", "type": "n8n-nodes-base.executeWorkflowTrigger",
         "typeVersion": 1, "position": [240, 300], "parameters": {}},
        {"id": "n2", "name": "Format Budget", "type": "n8n-nodes-base.code",
         "typeVersion": 2, "position": [460, 300],
         "parameters": {"jsCode": GENERATE_BUDGET_JS}}
    ],
    "connections": {
        "When Called by Tool": {"main": [[{"node": "Format Budget", "type": "main", "index": 0}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# 9. whatsapp-agent-fibromuebles (SANDBOX)
# ─────────────────────────────────────────
print("\n[9/9] Creando whatsapp-agent-fibromuebles (SANDBOX)...")
id_sandbox = create({
    "name": "whatsapp-agent-fibromuebles (SANDBOX)",
    "nodes": [
        # Webhook trigger
        {"id": "s1", "name": "Webhook SANDBOX", "type": "n8n-nodes-base.webhook",
         "typeVersion": 1, "position": [240, 300],
         "parameters": {"httpMethod": "POST", "path": "fibromuebles-whatsapp-sandbox",
                        "responseMode": "lastNode", "options": {}}},

        # AI Agent (Richard)
        {"id": "s2", "name": "Richard - AI Agent", "type": "@n8n/n8n-nodes-langchain.agent",
         "retryOnFail": True, "maxTries": 3, "waitBetweenTries": 20000,
         "typeVersion": 1, "position": [680, 300],
         "parameters": {
             "promptType": "define",
             "text": "={{ $json.input }}",
             "options": {"systemMessage": SYSTEM_PROMPT}
         }},

        # Google Gemini 2.0 Flash (gratuito via AI Studio)
        {"id": "s3", "name": "Gemini Flash", "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
         "typeVersion": 1, "position": [560, 500],
         "parameters": {"modelName": "gemini-2.0-flash", "options": {}}},

        # Memory por sesión
        {"id": "s4", "name": "Window Buffer Memory", "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
         "typeVersion": 1, "position": [700, 500],
         "parameters": {"sessionKey": "={{ $('Webhook SANDBOX').first().json.sessionId || $('Webhook SANDBOX').first().json.phone }}",
                        "contextWindowLength": 12}},

        # Tool: get_prices
        {"id": "s5", "name": "get_prices", "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
         "typeVersion": 1.2, "position": [840, 500],
         "parameters": {
             "name": "get_prices",
             "description": "Obtiene los precios actualizados de todos los materiales desde Google Sheets. Llamar sin parámetros cuando necesitás precios.",
             "workflowId": {"__rl": True, "value": id_get_prices, "mode": "id"},
             "fields": {"values": []}
         }},

        # Tool: calculate_budget
        {"id": "s6", "name": "calculate_budget", "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
         "typeVersion": 1.2, "position": [980, 500],
         "parameters": {
             "name": "calculate_budget",
             "description": "Calcula precio estimado de una pieza de mueble. Enviar JSON: {tag, cant, a(ancho cm), b(alto cm), mat}. Tags: #Medida-Blanco, #Medida-MDF, #Medida-Color, #Corte, #Estándar, #Pino, #Sobrante, #Herraje. Mat: 18b=melamina blanco 18mm, 18=MDF 18mm.",
             "workflowId": {"__rl": True, "value": id_calc_budget, "mode": "id"},
             "fields": {"values": []}
         }},

        # Tool: qualify_lead
        {"id": "s7", "name": "qualify_lead", "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
         "typeVersion": 1.2, "position": [1120, 500],
         "parameters": {
             "name": "qualify_lead",
             "description": "Califica un lead de Fibromuebles (score 0-10). JSON: {phone, name, budget(ARS), location, urgency, product_interest, has_dimensions(bool)}. Llamar cuando hayas recolectado suficiente info.",
             "workflowId": {"__rl": True, "value": id_qualify, "mode": "id"},
             "fields": {"values": []}
         }},

        # Tool: escalate_to_edd
        {"id": "s8", "name": "escalate_to_edd", "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
         "typeVersion": 1.2, "position": [1260, 500],
         "parameters": {
             "name": "escalate_to_edd",
             "description": "Escala la conversación a Edd notificándolo por WhatsApp. JSON: {phone, name, reason, summary}. Usar solo cuando sea necesario escalar.",
             "workflowId": {"__rl": True, "value": id_escalate, "mode": "id"},
             "fields": {"values": []}
         }},

        # Tool: analyze_image
        {"id": "s9", "name": "analyze_image", "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
         "typeVersion": 1.2, "position": [840, 620],
         "parameters": {
             "name": "analyze_image",
             "description": "Analiza una imagen enviada por el cliente (foto de referencia, habitación, mueble que le gusta). JSON: {media_url, media_type, context}. Usar cuando el cliente manda una imagen.",
             "workflowId": {"__rl": True, "value": id_analyze_image, "mode": "id"},
             "fields": {"values": []}
         }},

        # Tool: transcribe_audio
        {"id": "s10", "name": "transcribe_audio", "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
         "typeVersion": 1.2, "position": [980, 620],
         "parameters": {
             "name": "transcribe_audio",
             "description": "Transcribe un mensaje de voz (audio) enviado por el cliente via WhatsApp. JSON: {media_url, media_type, phone}. Usar cuando el cliente manda un audio.",
             "workflowId": {"__rl": True, "value": id_transcribe, "mode": "id"},
             "fields": {"values": []}
         }},

        # Tool: generate_render
        {"id": "s11", "name": "generate_render", "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
         "typeVersion": 1.2, "position": [1120, 620],
         "parameters": {
             "name": "generate_render",
             "description": "Genera un render visual del mueble con IA. JSON: {tipo_mueble, material, color, dimensiones, estilo, descripcion, ambiente}. Usar cuando el cliente quiere ver cómo quedaría el mueble.",
             "workflowId": {"__rl": True, "value": id_render, "mode": "id"},
             "fields": {"values": []}
         }},

        # Tool: generate_budget
        {"id": "s12", "name": "generate_budget", "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
         "typeVersion": 1.2, "position": [1260, 620],
         "parameters": {
             "name": "generate_budget",
             "description": "Genera el mensaje de presupuesto formateado para enviar por WhatsApp. JSON: {cliente, mueble, items:[{descripcion,cantidad,precio_total}], render_url(opt), notas(opt), tiempo_entrega(opt)}. Usar al final para entregar el presupuesto completo.",
             "workflowId": {"__rl": True, "value": id_budget, "mode": "id"},
             "fields": {"values": []}
         }},

        # Respond to webhook
        {"id": "s13", "name": "Respond to Webhook", "type": "n8n-nodes-base.respondToWebhook",
         "typeVersion": 1, "position": [920, 300],
         "parameters": {
             "respondWith": "json",
             "responseBody": "={{ JSON.stringify({output: $json.output}) }}"
         }}
    ],
    "connections": {
        "Webhook SANDBOX": {"main": [[{"node": "Richard - AI Agent", "type": "main", "index": 0}]]},
        "Richard - AI Agent": {"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]},
        "Gemini Flash": {"ai_languageModel": [[{"node": "Richard - AI Agent", "type": "ai_languageModel", "index": 0}]]},
        "Window Buffer Memory": {"ai_memory": [[{"node": "Richard - AI Agent", "type": "ai_memory", "index": 0}]]},
        "get_prices": {"ai_tool": [[{"node": "Richard - AI Agent", "type": "ai_tool", "index": 0}]]},
        "calculate_budget": {"ai_tool": [[{"node": "Richard - AI Agent", "type": "ai_tool", "index": 1}]]},
        "qualify_lead": {"ai_tool": [[{"node": "Richard - AI Agent", "type": "ai_tool", "index": 2}]]},
        "escalate_to_edd": {"ai_tool": [[{"node": "Richard - AI Agent", "type": "ai_tool", "index": 3}]]},
        "analyze_image": {"ai_tool": [[{"node": "Richard - AI Agent", "type": "ai_tool", "index": 4}]]},
        "transcribe_audio": {"ai_tool": [[{"node": "Richard - AI Agent", "type": "ai_tool", "index": 5}]]},
        "generate_render": {"ai_tool": [[{"node": "Richard - AI Agent", "type": "ai_tool", "index": 6}]]},
        "generate_budget": {"ai_tool": [[{"node": "Richard - AI Agent", "type": "ai_tool", "index": 7}]]}
    },
    "settings": {"executionOrder": "v1"}
})

# ─────────────────────────────────────────
# RESUMEN
# ─────────────────────────────────────────
print("\n" + "=" * 60)
print("WORKFLOWS CREADOS — Actualizá CLAUDE.md con estos IDs:")
print(f"  fibro-tool-get-prices:                    {id_get_prices}")
print(f"  fibro-tool-calculate-budget:              {id_calc_budget}")
print(f"  fibro-tool-qualify-lead:                  {id_qualify}")
print(f"  fibro-tool-escalate-to-edd:               {id_escalate}")
print(f"  fibro-tool-analyze-image:                 {id_analyze_image}")
print(f"  fibro-tool-transcribe-audio:              {id_transcribe}")
print(f"  fibro-tool-generate-render:               {id_render}")
print(f"  fibro-tool-generate-budget:               {id_budget}")
print(f"  whatsapp-agent-fibromuebles (SANDBOX):    {id_sandbox}")
print("=" * 60)
print("\nSANDBOX endpoint:")
print("  POST http://localhost:5678/webhook/fibromuebles-whatsapp-sandbox")
print('  Body: {"input": "Hola, quiero un placard", "sessionId": "test-001", "phone": "5491100000000"}')
print("\nPENDIENTE (completar en n8n UI):")
print("  1. Credencial Anthropic API en 'Claude Sonnet 4.6' node del agente SANDBOX")
print("  2. fibro-tool-escalate-to-edd: reemplazar TU_NUMERO_ID, TU_TOKEN_AQUI, NUMERO_EDD_AQUI")
print("  3. fibro-tool-transcribe-audio: variable de entorno OPENAI_API_KEY en n8n Settings")
print("  4. fibro-tool-analyze-image: variable de entorno ANTHROPIC_API_KEY en n8n Settings")
