/* 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 m² 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)} />
m²
) : (
)}
);
}
/* 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 });