/* global React, Icon, Button, Badge, useState, useEffect, useRef */ /* Recomendador de baldes: cubre los litros con el menor sobrante razonable */ function recommendCans(liters, presList) { const sizes = [...presList].sort((a, b) => b.liters - a.liters); let rem = liters; const picks = []; for (const s of sizes) { const n = Math.floor(rem / s.liters + 1e-9); if (n > 0) { picks.push({ pres: s, count: n }); rem -= n * s.liters; } } if (rem > 0.01) { const cover = [...presList].filter((s) => s.liters >= rem - 1e-9).sort((a, b) => a.liters - b.liters)[0] || sizes[sizes.length - 1]; const ex = picks.find((p) => p.pres.id === cover.id); if (ex) ex.count++; else picks.push({ pres: cover, count: 1 }); rem -= cover.liters; } const totalLiters = picks.reduce((s, p) => s + p.pres.liters * p.count, 0); return { picks, totalLiters }; } const ENVIRONMENTS = [ { id: "interior", label: "Pared interior", coatHint: 2 }, { id: "cocina", label: "Cocina / Baño", coatHint: 2 }, { id: "exterior", label: "Frente / Exterior", coatHint: 2 }, { id: "techo", label: "Techo / Terraza", coatHint: 3 }, { id: "madera", label: "Madera / Metal", coatHint: 2 }, ]; function Typing() { return
; } function BotFlow({ product, presId, color, onConfirm, onClose }) { const NOGO = window.NOGO; const [msgs, setMsgs] = useState([]); const [step, setStep] = useState("intro"); // intro|ctx|area|dims|coats|result const [typing, setTyping] = useState(false); const [ctx, setCtx] = useState(null); const [area, setArea] = useState(""); const [coats, setCoats] = useState(product.manos); const [dims, setDims] = useState({ peri: "", alto: "" }); const [extras, setExtras] = useState([]); const feedRef = useRef(null); const push = (m) => setMsgs((prev) => [...prev, { id: Date.now() + Math.random(), ...m }]); // auto-scroll useEffect(() => { if (feedRef.current) feedRef.current.scrollTop = feedRef.current.scrollHeight; }, [msgs, typing, step]); // mensaje del bot con "typing" previo const botSay = (content, delay = 650) => { setTyping(true); return new Promise((res) => setTimeout(() => { setTyping(false); push({ from: "bot", content }); res(); }, delay)); }; // arranque useEffect(() => { let alive = true; (async () => { await botSay(<>¡Hola! Soy Nilo, tu asesor de pintura 🎨 Sumaste {product.name}. En 3 pasos calculo cuánta pintura necesitás para no comprar de más ni de menos., 500); if (!alive) return; await botSay(<>¿Dónde vas a pintar?, 700); if (alive) setStep("ctx"); })(); return () => { alive = false; }; }, []); const chooseEnv = async (env) => { push({ from: "user", content: env.label }); setCtx(env); setCoats(env.coatHint); setStep("intro"); await botSay(<>Perfecto. ¿Cuántos vas a pintar? Si no sabés, te ayudo a calcularlo con las medidas., 700); setStep("area"); }; const submitArea = async (m2) => { push({ from: "user", content: `${m2} m²` }); setStep("intro"); await botSay(<>Genial. Para esta superficie suelo recomendar {ctx.coatHint} manos. ¿Confirmás o ajustás?, 650); setStep("coats"); }; const computeFromDims = () => { const peri = parseFloat(dims.peri), alto = parseFloat(dims.alto); if (!peri || !alto) return; const m2 = Math.round(peri * alto); setArea(String(m2)); submitArea(m2); }; const confirmCoats = async (c) => { setCoats(c); push({ from: "user", content: `${c} ${c === 1 ? "mano" : "manos"}` }); setStep("intro"); const m2 = parseFloat(area); const litersNeeded = (m2 * c) / product.rendimiento; const rec = recommendCans(litersNeeded, NOGO.presentations.filter((p) => product.pres.includes(p.id))); await botSay(<>Listo, hice el cálculo 👇, 800); push({ from: "bot", kind: "calc", litersNeeded, rec, m2, coats: c }); await botSay(<>Para que el trabajo quede impecable, sumá estos complementos. ¿Te agrego alguno?, 900); setStep("result"); // guardo recomendación para el confirm window.__lastRec = rec; }; const toggleExtra = (id) => setExtras((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]); const finish = () => { const rec = window.__lastRec; const items = rec.picks.map((p) => ({ productId: product.id, name: product.name, presId: p.pres.id, presLabel: `${p.pres.label} ${p.pres.unit}`, qty: p.count, price: NOGO.priceFor(product, p.pres.id), color, })); const accItems = extras.map((id) => { const a = NOGO.accessories.find((x) => x.id === id); return { productId: a.id, name: a.name, presId: "u", presLabel: "Unidad", qty: 1, price: a.price, color: null }; }); onConfirm([...items, ...accItems]); }; const recoExtras = ["rodillo", "cinta", ctx && ctx.id === "interior" ? "enduido" : "lija"].filter(Boolean); return (
Nilo · Asesor Nogopaint
Calculando con datos reales del producto
{msgs.map((m) => { if (m.kind === "calc") return ; return
{m.content}
; })} {typing && } {/* zona interactiva inline segun step */} {!typing && step === "ctx" && (
{ENVIRONMENTS.map((e) => ( ))}
)} {!typing && step === "coats" && (
{[1, 2, 3].map((c) => ( ))}
)} {!typing && step === "result" && (
{recoExtras.map((id) => { const a = NOGO.accessories.find((x) => x.id === id); const on = extras.includes(id); return ( ); })}
)}
{/* footer: input de m² o CTA final */} {step === "area" && ( )} {step === "result" && (
)}
); } /* Input de m² con calculadora por medidas */ function AreaInput({ dims, setDims, area, setArea, onSubmit, onCalc }) { const [mode, setMode] = useState("direct"); // direct | calc return (
{mode === "direct" ? (
setArea(e.target.value)} />
) : (
setDims({ ...dims, peri: e.target.value })} /> setDims({ ...dims, alto: e.target.value })} />
)}
); } /* Tarjeta de resultado del cálculo */ function CalcCard({ litersNeeded, rec, m2, coats, product }) { const NOGO = window.NOGO; const totalPrice = rec.picks.reduce((s, p) => s + NOGO.priceFor(product, p.pres.id) * p.count, 0); const leftover = (rec.totalLiters - litersNeeded); return (
Necesitás aproximadamente
{litersNeeded.toFixed(1)} litros {m2} m² · {coats} {coats === 1 ? "mano" : "manos"}
Te recomendamos comprar
{rec.picks.map((p) => (
{p.count} × balde de {p.pres.label} {p.pres.unit} {NOGO.fmt(NOGO.priceFor(product, p.pres.id) * p.count)}
))}
Total pintura · {rec.totalLiters} L {NOGO.fmt(totalPrice)}
{leftover > 0.05 && (
Te sobra ~{leftover.toFixed(1)} L para retoques. Rinde {product.rendimiento} m²/L por mano.
)}
); } Object.assign(window, { BotFlow, recommendCans, CalcCard });