/* global React, Icon */
/* =========================================================================
   FÁBRICA BREAK · Monitor de producción real con estética tycoon
   NO es un juego: cada orden representa una producción real, con tiempos
   reales (minutos/horas/días). El motor usa timestamps reales, así que el
   proceso avanza aunque la app esté cerrada.

   Motor orientado a objetos: recetas, máquinas e insumos son DATOS ("meta"),
   editables por el usuario desde la pantalla de Configuración y persistidos
   en localStorage. Las clases del motor son genéricas.
   ========================================================================= */

const FX_STORAGE_KEY = "break_factory_v2";
const FX_META_KEY = "break_factory_meta_v2";
const FX_MINUTE = 60 * 1000;

// ---------- Definiciones de fábrica por defecto (datos, no lógica) ----------
const FX_INSUMO_DEFS = [
  { id: "harina", name: "Harina 000", emoji: "🌾", unit: "kg", start: 30, pack: 25, max: 75 },
  { id: "agua", name: "Agua filtrada", emoji: "💧", unit: "L", start: 40, pack: 20, max: 80 },
  { id: "levadura", name: "Levadura fresca", emoji: "🧫", unit: "g", start: 300, pack: 500, max: 1000 },
  { id: "sal", name: "Sal marina", emoji: "🧂", unit: "g", start: 800, pack: 1000, max: 3000 },
  { id: "aceite", name: "Aceite de oliva", emoji: "🫒", unit: "ml", start: 1500, pack: 1000, max: 5000 },
  { id: "romero", name: "Romero fresco", emoji: "🌿", unit: "g", start: 120, pack: 100, max: 400 },
  { id: "cebolla", name: "Cebolla", emoji: "🧅", unit: "kg", start: 5, pack: 5, max: 15 },
  { id: "tomate-cherry", name: "Tomate cherry", emoji: "🍅", unit: "kg", start: 3, pack: 3, max: 10 },
  { id: "fiambre", name: "Fiambre", emoji: "🥓", unit: "kg", start: 5, pack: 5, max: 15 },
  { id: "envases", name: "Envases", emoji: "🛍️", unit: "un", start: 100, pack: 100, max: 400 },
];

// price = inversión en R$ · unitCap = espacio en unidades (se muestra como 64/100)
// group: las máquinas con el mismo grupo son intercambiables → al llegar la
// etapa, el usuario elige a cuál mandar la masa (ej. "Hornos", "Frío").
const FX_MACHINE_DEFS = [
  { id: "mesa", name: "Mesa de trabajo", emoji: "🪵", capacity: 2, price: 0, unitCap: 0, group: "", desc: "Masa, toppings y armado a mano" },
  { id: "heladera-cond", name: "Heladera condimentadora", emoji: "🧊", capacity: 3, price: 10450, unitCap: 100, group: "Frío", desc: "Fermentación en frío 48 hs" },
  { id: "heladera-casa", name: "Heladera de casa", emoji: "🚪", capacity: 1, price: 0, unitCap: 50, group: "Frío", desc: "Backup · entran 30-50 focaccias" },
  { id: "cortadora", name: "Cortadora de fiambre", emoji: "🔪", capacity: 1, price: 1600, unitCap: 0, group: "", desc: "Feteado de fiambre para el armado" },
  { id: "horno4", name: "Horno 4 bandejas", emoji: "🔥", capacity: 1, price: 2600, unitCap: 32, group: "Hornos", desc: "32 sandwiches cada 30 min (4 bandejas × 8)" },
  { id: "horno-casa", name: "Horno convencional de casa", emoji: "♨️", capacity: 1, price: 0, unitCap: 24, group: "Hornos", desc: "Backup · 16-24 cada 30 min" },
  { id: "empaque", name: "Zona de envasado", emoji: "📦", capacity: 1, price: 0, unitCap: 0, group: "", desc: "Enfriado y envasado final" },
];

const FX_LEGACY_GROUPS = { "heladera-cond": "Frío", "heladera-casa": "Frío", horno4: "Hornos", "horno-casa": "Hornos" };

// tiempos en minutos reales · tanda de focaccia = 1 hornada (32 un)
// Los pasos marcados (estimado) son los que quedaron con "?" — ajustalos en Configuración.
const FX_RECIPE_DEFS = [
  {
    productId: "focaccia", name: "Focaccia", emoji: "🫓", output: 32,
    steps: [
      { id: "masa", name: "Masa", machineId: "mesa", minutes: 90, inputs: { harina: 10, agua: 7, levadura: 100, sal: 200, aceite: 300 } },
      { id: "fermentado", name: "Fermentación en frío", machineId: "heladera-cond", minutes: 48 * 60 },
      { id: "toppings", name: "Toppings y aceite", machineId: "mesa", minutes: 30, inputs: { aceite: 400, romero: 30, sal: 50, cebolla: 1.5, "tomate-cherry": 1.5 } },
      { id: "horneado", name: "Horneado", machineId: "horno4", minutes: 30 },
      { id: "fiambre", name: "Cortar fiambre (estimado)", machineId: "cortadora", minutes: 20, inputs: { fiambre: 2.5 } },
      { id: "armado", name: "Armar cada uno (estimado)", machineId: "mesa", minutes: 45 },
      { id: "envasado", name: "Envasar (estimado)", machineId: "empaque", minutes: 30, inputs: { envases: 32 } },
    ],
  },
  {
    productId: "baguette", name: "Baguette", emoji: "🥖", output: 8,
    steps: [
      { id: "amasado", name: "Amasado", machineId: "mesa", minutes: 20, inputs: { harina: 2.5, agua: 1.6, levadura: 15, sal: 50 } },
      { id: "fermentado", name: "Fermentación en frío", machineId: "heladera-cond", minutes: 48 * 60 },
      { id: "formado", name: "Formado", machineId: "mesa", minutes: 30 },
      { id: "horneado", name: "Horneado al vapor", machineId: "horno4", minutes: 25 },
      { id: "envasado", name: "Enfriado y envasado", machineId: "empaque", minutes: 30 },
    ],
  },
];

function fxDefaultMeta() {
  return JSON.parse(JSON.stringify({
    insumos: FX_INSUMO_DEFS,
    machines: FX_MACHINE_DEFS,
    recipes: FX_RECIPE_DEFS,
  }));
}

function loadFactoryMeta() {
  try {
    const raw = localStorage.getItem(FX_META_KEY);
    if (!raw) return fxDefaultMeta();
    const parsed = JSON.parse(raw);
    if (!parsed || !Array.isArray(parsed.machines) || !Array.isArray(parsed.insumos) || !Array.isArray(parsed.recipes)) {
      return fxDefaultMeta();
    }
    // migración: configs guardadas antes de los grupos de máquinas
    parsed.machines = parsed.machines.map((m) =>
      m.group !== undefined ? m : { ...m, group: FX_LEGACY_GROUPS[m.id] || "" }
    );
    return parsed;
  } catch {
    return fxDefaultMeta();
  }
}

function saveFactoryMeta(meta) {
  try { localStorage.setItem(FX_META_KEY, JSON.stringify(meta)); } catch {}
}

// ---------- Clases del motor ----------
class FxInsumo {
  constructor(def, stock) {
    this.def = def;
    this.id = def.id;
    this.stock = typeof stock === "number" ? stock : def.start;
  }
  get pct() { return Math.max(0, Math.min(1, this.stock / this.def.max)); }
  get low() { return this.pct < 0.2; }
  restock() { this.stock = Math.min(this.def.max, this.stock + this.def.pack); }
  consume(qty) { this.stock = Math.max(0, this.stock - qty); }
  has(qty) { return this.stock >= qty - 1e-9; }
}

class FxMachine {
  constructor(def) {
    this.def = def;
    this.id = def.id;
    this.capacity = def.capacity;
  }
}

class FxStep {
  constructor(def) {
    this.def = def;
    this.id = def.id;
    this.name = def.name;
    this.machineId = def.machineId;
    this.minutes = def.minutes;
    this.inputs = def.inputs || {};
  }
  get durationMs() { return this.minutes * FX_MINUTE; }
}

class FxRecipe {
  constructor(def) {
    this.def = def;
    this.productId = def.productId;
    this.name = def.name;
    this.emoji = def.emoji;
    this.output = def.output;
    this.steps = def.steps.map((s) => new FxStep(s));
  }
  get totalMinutes() { return this.steps.reduce((sum, s) => sum + s.minutes, 0); }
  totalInputs() {
    const totals = {};
    for (const step of this.steps) {
      for (const key of Object.keys(step.inputs)) {
        totals[key] = (totals[key] || 0) + step.inputs[key];
      }
    }
    return totals;
  }
}

class FxJob {
  constructor(id, recipe, batches, saved) {
    this.id = id;
    this.recipe = recipe;
    this.batches = batches;
    this.stepIndex = saved ? Math.min(saved.stepIndex, recipe.steps.length - 1) : 0;
    this.state = saved ? saved.state : "queued"; // queued | waiting-choice | waiting-machine | waiting-insumos | running | done
    this.stepStartedAt = saved ? saved.stepStartedAt : null;   // timestamp real de inicio del paso
    this.readyAt = saved ? saved.readyAt : null;               // cuándo terminó el paso anterior
    this.createdAt = saved ? saved.createdAt : Date.now();
    this.assignedMachineId = saved ? saved.assignedMachineId || null : null; // elección del usuario para este paso
    this.runningMachineId = saved
      ? saved.runningMachineId || (saved.state === "running" && this.step ? this.step.machineId : null)
      : null;
    this.waitingMachineId = saved ? saved.waitingMachineId || null : null;
    this.blocked = null;
  }
  get step() { return this.recipe.steps[this.stepIndex]; }
  get outputQty() { return this.recipe.output * this.batches; }
  get stepEndsAt() {
    return this.state === "running" && this.stepStartedAt ? this.stepStartedAt + this.step.durationMs : null;
  }
  progressAt(now) {
    if (this.state !== "running" || !this.stepStartedAt) return 0;
    return Math.max(0, Math.min(1, (now - this.stepStartedAt) / this.step.durationMs));
  }
  // ETA estimada: fin del paso actual + duración de los pasos restantes (sin contar colas)
  etaAt(now) {
    let t = this.state === "running" ? this.stepEndsAt : Math.max(now, this.readyAt || now);
    for (let i = this.stepIndex + (this.state === "running" ? 1 : 0); i < this.recipe.steps.length; i++) {
      t += this.recipe.steps[i].durationMs;
    }
    return t;
  }
  toJSON() {
    return {
      id: this.id, recipeId: this.recipe.productId, batches: this.batches,
      stepIndex: this.stepIndex, state: this.state,
      stepStartedAt: this.stepStartedAt, readyAt: this.readyAt, createdAt: this.createdAt,
      assignedMachineId: this.assignedMachineId, runningMachineId: this.runningMachineId,
      waitingMachineId: this.waitingMachineId,
    };
  }
}

class FactoryEngine {
  constructor(saved, hooks, meta) {
    saved = saved || {};
    meta = meta || loadFactoryMeta();
    this.hooks = hooks || {};
    this.insumos = meta.insumos.map((def) => new FxInsumo(def, saved.insumos && saved.insumos[def.id]));
    this.machines = meta.machines.map((def) => new FxMachine(def));
    this.recipes = meta.recipes.map((def) => new FxRecipe(def));
    this.produced = saved.produced || {};
    this.jobSeq = saved.jobSeq || 1;
    this.log = Array.isArray(saved.log) ? saved.log : [];
    this.pendingPops = [];
    this.jobs = Array.isArray(saved.jobs)
      ? saved.jobs
          .map((j) => {
            const recipe = this.recipes.find((r) => r.productId === j.recipeId);
            return recipe && recipe.steps.length ? new FxJob(j.id, recipe, j.batches, j) : null;
          })
          .filter((j) => j && j.state !== "done")
      : [];
    if (!this.log.length) this.addLog("🏭 Monitor de producción Break iniciado.", "info");
  }

  insumo(id) { return this.insumos.find((i) => i.id === id); }
  machine(id) { return this.machines.find((m) => m.id === id); }
  recipe(id) { return this.recipes.find((r) => r.productId === id); }

  // máquinas candidatas para un paso: la del paso + todas las de su mismo grupo
  candidatesFor(step) {
    const base = this.machine(step.machineId);
    if (!base) return [];
    const group = (base.def.group || "").trim();
    if (!group) return [base];
    const list = this.machines.filter((m) => (m.def.group || "").trim() === group);
    return list.length ? list : [base];
  }

  // el usuario elige a qué máquina mandar el paso actual de la orden
  assignMachine(jobId, machineId) {
    const job = this.jobs.find((j) => j.id === jobId);
    const machine = this.machine(machineId);
    if (!job || !machine || job.state === "done") return;
    job.assignedMachineId = machineId;
    if (job.state === "waiting-choice" || job.state === "waiting-machine") {
      job.state = "queued";
      job.waitingMachineId = null;
    }
    this.addLog(`📥 Orden #${job.id} → ${machine.def.emoji} ${machine.def.name}.`, "info");
  }

  addLog(msg, tone, at) {
    const d = at ? new Date(at) : new Date();
    const t = d.toLocaleString("es-AR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
    this.log = [{ t, msg, tone: tone || "info" }, ...this.log].slice(0, 40);
  }

  missingFor(step, batches) {
    const missing = [];
    for (const key of Object.keys(step.inputs)) {
      const need = step.inputs[key] * batches;
      const insumo = this.insumo(key);
      if (!insumo || !insumo.has(need)) missing.push(insumo ? insumo.def.name : key);
    }
    return missing;
  }

  consumeFor(step, batches) {
    for (const key of Object.keys(step.inputs)) {
      const insumo = this.insumo(key);
      if (insumo) insumo.consume(step.inputs[key] * batches);
    }
  }

  hasInsumosFor(recipe, batches) {
    const totals = recipe.totalInputs();
    return Object.keys(totals).every((key) => {
      const insumo = this.insumo(key);
      return insumo && insumo.has(totals[key] * batches);
    });
  }

  startJob(recipeId, batches) {
    const recipe = this.recipe(recipeId);
    if (!recipe) return;
    const job = new FxJob(this.jobSeq++, recipe, batches);
    this.jobs.push(job);
    this.addLog(`${recipe.emoji} Orden #${job.id}: ${batches} tanda${batches > 1 ? "s" : ""} de ${recipe.name} (${job.outputQty} un).`, "info");
  }

  cancelJob(jobId) {
    const job = this.jobs.find((j) => j.id === jobId);
    if (!job) return;
    this.jobs = this.jobs.filter((j) => j.id !== jobId);
    this.addLog(`✖️ Orden #${job.id} (${job.recipe.name}) cancelada.`, "warn");
  }

  // Solo para testear: completa únicamente el paso actual de la orden
  // (ej. terminar ya la masa) y la deja lista para el paso siguiente.
  finishStepNow(jobId) {
    const job = this.jobs.find((j) => j.id === jobId);
    if (!job || job.state === "done") return;
    const step = job.step;
    if (!step) return;
    if (job.state !== "running") this.consumeFor(step, job.batches);
    const now = Date.now();
    job.stepIndex += 1;
    job.stepStartedAt = null;
    job.readyAt = now;
    job.assignedMachineId = null;
    job.runningMachineId = null;
    job.waitingMachineId = null;
    this.addLog(`⏭️ Orden #${job.id}: paso "${step.name}" adelantado (modo test).`, "warn");
    if (job.stepIndex >= job.recipe.steps.length) {
      this.completeJob(job, now);
      this.jobs = this.jobs.filter((j) => j.state !== "done");
    } else {
      job.state = "queued";
      job.blocked = null;
    }
  }

  // Solo para testear: completa la orden al instante, consumiendo los insumos
  // de los pasos que faltaban (sin esperar tiempos reales ni máquinas).
  finishJobNow(jobId) {
    const job = this.jobs.find((j) => j.id === jobId);
    if (!job || job.state === "done") return;
    for (let i = job.stepIndex; i < job.recipe.steps.length; i++) {
      const alreadyConsumed = i === job.stepIndex && job.state === "running";
      if (!alreadyConsumed) this.consumeFor(job.recipe.steps[i], job.batches);
    }
    job.stepIndex = job.recipe.steps.length;
    this.addLog(`⏩ Orden #${job.id} adelantada (modo test).`, "warn");
    this.completeJob(job, Date.now());
    this.jobs = this.jobs.filter((j) => j.state !== "done");
  }

  finishAllNow() {
    for (const job of [...this.jobs]) this.finishJobNow(job.id);
  }

  restockInsumo(id) {
    const insumo = this.insumo(id);
    if (!insumo || insumo.stock >= insumo.def.max) return;
    insumo.restock();
    this.addLog(`${insumo.def.emoji} Reposición: +${insumo.def.pack} ${insumo.def.unit} de ${insumo.def.name}.`, "info");
  }

  machineLoad() {
    const load = {};
    for (const m of this.machines) load[m.id] = 0;
    for (const job of this.jobs) {
      if (job.state === "running" && job.runningMachineId) load[job.runningMachineId] += 1;
    }
    return load;
  }

  waitingByMachine() {
    const waiting = {};
    for (const job of this.jobs) {
      if (job.state === "waiting-machine" && job.waitingMachineId) {
        waiting[job.waitingMachineId] = (waiting[job.waitingMachineId] || 0) + 1;
      }
    }
    return waiting;
  }

  completeJob(job, at) {
    const qty = job.outputQty;
    this.produced[job.recipe.productId] = (this.produced[job.recipe.productId] || 0) + qty;
    this.addLog(`✅ Orden #${job.id} terminada: ${qty} ${job.recipe.name} listas.`, "ok", at);
    this.pendingPops.push({ id: `${Date.now()}-${job.id}`, text: `${job.recipe.emoji} +${qty} ${job.recipe.name} listas` });
    if (this.hooks.onProduced) this.hooks.onProduced(job.recipe.name, qty);
    job.state = "done";
  }

  // Avanza la simulación hasta `now` usando tiempo real. Itera para encadenar
  // pasos que terminaron mientras la app estuvo cerrada.
  tick(now) {
    let changed = true;
    let guard = 0;
    while (changed && guard++ < 60) {
      changed = false;
      const load = this.machineLoad();
      for (const job of this.jobs) {
        if (job.state === "done") continue;

        if (job.state === "running") {
          const endAt = job.stepEndsAt;
          if (now >= endAt) {
            if (job.runningMachineId) load[job.runningMachineId] -= 1;
            job.stepIndex += 1;
            job.stepStartedAt = null;
            job.readyAt = endAt;
            job.assignedMachineId = null;
            job.runningMachineId = null;
            job.waitingMachineId = null;
            if (job.stepIndex >= job.recipe.steps.length) {
              this.completeJob(job, endAt);
            } else {
              job.state = "queued";
            }
            changed = true;
          }
          continue;
        }

        // queued / waiting → intentar arrancar el paso actual
        const step = job.step;
        if (!step) continue;
        const missing = this.missingFor(step, job.batches);
        if (missing.length) {
          if (job.state !== "waiting-insumos") {
            job.state = "waiting-insumos";
            job.blocked = missing.join(", ");
            this.addLog(`🚫 Orden #${job.id} frenada: faltan ${job.blocked}.`, "warn");
          }
          continue;
        }
        // si hay más de una máquina posible (mismo grupo), espera la elección del usuario
        const candidates = this.candidatesFor(step);
        if (!candidates.length) continue;
        let machine;
        if (candidates.length > 1) {
          machine = job.assignedMachineId ? candidates.find((m) => m.id === job.assignedMachineId) : null;
          if (!machine) {
            if (job.state !== "waiting-choice") {
              job.state = "waiting-choice";
              job.blocked = candidates.map((m) => m.def.name).join(" / ");
              this.addLog(`🤔 Orden #${job.id}: elegí dónde va "${step.name}" (${job.blocked}).`, "warn");
            }
            continue;
          }
        } else {
          machine = candidates[0];
        }
        if (load[machine.id] >= machine.capacity) {
          if (job.state !== "waiting-machine" || job.waitingMachineId !== machine.id) {
            job.state = "waiting-machine";
            job.waitingMachineId = machine.id;
            job.blocked = machine.def.name;
            this.addLog(`⏳ Orden #${job.id} espera ${machine.def.emoji} ${machine.def.name} (cuello de botella).`, "warn");
          }
          continue;
        }
        this.consumeFor(step, job.batches);
        // si venimos de un paso que terminó en el pasado (app cerrada), el
        // siguiente paso arranca en ese momento, no ahora
        job.stepStartedAt = job.readyAt && job.readyAt < now ? job.readyAt : now;
        job.state = "running";
        job.runningMachineId = machine.id;
        job.waitingMachineId = null;
        job.blocked = null;
        load[machine.id] += 1;
        changed = true;
      }
    }
    this.jobs = this.jobs.filter((j) => j.state !== "done");
  }

  serialize() {
    const insumos = {};
    for (const i of this.insumos) insumos[i.id] = i.stock;
    return {
      produced: this.produced, jobSeq: this.jobSeq, insumos,
      jobs: this.jobs.map((j) => j.toJSON()), log: this.log,
    };
  }
}

function loadFactoryState() {
  try {
    const raw = localStorage.getItem(FX_STORAGE_KEY);
    return raw ? JSON.parse(raw) : null;
  } catch {
    return null;
  }
}

function saveFactoryState(engine) {
  try { localStorage.setItem(FX_STORAGE_KEY, JSON.stringify(engine.serialize())); } catch {}
}

function saveFactoryRawState(state) {
  try { localStorage.setItem(FX_STORAGE_KEY, JSON.stringify(state)); } catch {}
}

function factoryInsumoSnapshot() {
  const meta = loadFactoryMeta();
  const saved = loadFactoryState() || {};
  const savedStock = saved.insumos || {};
  return meta.insumos.map((def) => ({
    ...def,
    stock: typeof savedStock[def.id] === "number" ? savedStock[def.id] : (Number(def.start) || 0),
  }));
}

function applyFactoryInsumoDeltas(deltas) {
  const cleanDeltas = Array.isArray(deltas)
    ? deltas
        .map((delta) => ({ id: String(delta.id || "").trim(), qty: Number(delta.qty) || 0 }))
        .filter((delta) => delta.id && delta.qty !== 0)
    : [];
  if (!cleanDeltas.length) return factoryInsumoSnapshot();

  const meta = loadFactoryMeta();
  const saved = loadFactoryState() || {};
  const insumos = {};
  for (const def of meta.insumos) {
    insumos[def.id] = typeof saved.insumos?.[def.id] === "number" ? saved.insumos[def.id] : (Number(def.start) || 0);
  }
  for (const id of Object.keys(saved.insumos || {})) {
    if (insumos[id] === undefined) insumos[id] = saved.insumos[id];
  }
  for (const delta of cleanDeltas) {
    insumos[delta.id] = Math.max(0, (Number(insumos[delta.id]) || 0) + delta.qty);
  }
  saveFactoryRawState({
    produced: saved.produced || {},
    jobSeq: saved.jobSeq || 1,
    jobs: Array.isArray(saved.jobs) ? saved.jobs : [],
    log: Array.isArray(saved.log) ? saved.log : [],
    ...saved,
    insumos,
  });
  return factoryInsumoSnapshot();
}

// ---------- Helpers de formato ----------
function fxDur(minutes) {
  if (minutes < 60) return `${Math.round(minutes)} min`;
  const h = minutes / 60;
  if (h < 48) {
    const whole = Math.floor(h);
    const rem = Math.round(minutes - whole * 60);
    return rem > 0 ? `${whole}h ${rem}m` : `${whole} h`;
  }
  const d = Math.floor(h / 24);
  const remH = Math.round(h - d * 24);
  return remH > 0 ? `${d}d ${remH}h` : `${d} d`;
}

function fxRemaining(ms) {
  return fxDur(Math.max(0, ms) / FX_MINUTE);
}

function fxEta(ts) {
  return new Date(ts).toLocaleString("es-AR", { weekday: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
}

function fxQty(value) {
  return Number.isInteger(value) ? String(value) : value.toFixed(1);
}

function fxNewId(prefix) {
  return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;
}

const FX_STATE_LABEL = {
  queued: { text: "En cola", tone: "wait" },
  "waiting-choice": { text: "Elegir equipo", tone: "choice" },
  "waiting-machine": { text: "Cuello de botella", tone: "warn" },
  "waiting-insumos": { text: "Faltan insumos", tone: "danger" },
  running: { text: "En proceso", tone: "run" },
};

// ---------- Componentes: piso de fábrica ----------
function FxRecipeCard({ recipe, engine, onStart }) {
  const [batches, setBatches] = React.useState(1);
  const totals = recipe.totalInputs();
  const enough = engine.hasInsumosFor(recipe, batches);
  return (
    <div className="fx-card fx-recipe">
      <div className="fx-recipe-head">
        <span className="fx-recipe-emoji">{recipe.emoji}</span>
        <div className="fx-recipe-title">
          <strong>{recipe.name}</strong>
          <span>{recipe.output} un / tanda · proceso completo ⏱️ ~{fxDur(recipe.totalMinutes)}</span>
        </div>
      </div>
      <div className="fx-steps-row">
        {recipe.steps.map((step, i) => {
          const machine = engine.machine(step.machineId);
          return (
            <React.Fragment key={step.id}>
              {i > 0 && <span className="fx-step-arrow">›</span>}
              <span className="fx-step-chip" title={step.name}>
                {machine ? machine.def.emoji : "❓"} {step.name} <em>{fxDur(step.minutes)}</em>
              </span>
            </React.Fragment>
          );
        })}
      </div>
      <div className="fx-recipe-needs">
        {Object.keys(totals).map((key) => {
          const insumo = engine.insumo(key);
          if (!insumo) return null;
          const ok = insumo.has(totals[key] * batches);
          return (
            <span key={key} className={`fx-need ${ok ? "" : "fx-need-missing"}`}>
              {insumo.def.emoji} {fxQty(totals[key] * batches)} {insumo.def.unit}
            </span>
          );
        })}
      </div>
      <div className="fx-launch">
        <div className="fx-stepper">
          <button type="button" onClick={() => setBatches((b) => Math.max(1, b - 1))} aria-label="Menos tandas">−</button>
          <strong>{batches}</strong>
          <button type="button" onClick={() => setBatches((b) => Math.min(8, b + 1))} aria-label="Más tandas">+</button>
        </div>
        <div className="fx-launch-info">
          <strong>{batches} tanda{batches > 1 ? "s" : ""} → {recipe.output * batches} un</strong>
          <span>ETA estimada: {fxEta(Date.now() + recipe.totalMinutes * FX_MINUTE)}</span>
        </div>
        <button type="button" className="fx-btn fx-btn-amber" onClick={() => { onStart(recipe.productId, batches); setBatches(1); }}>
          ▶ Producir
        </button>
      </div>
      {!enough && <div className="fx-launch-warn">⚠️ Insumos insuficientes para {batches} tanda{batches > 1 ? "s" : ""}: la orden quedará frenada hasta reponer.</div>}
    </div>
  );
}

function FxJobCard({ job, engine, now, onCancel, onSkip, onSkipStep, onAssign }) {
  const status = FX_STATE_LABEL[job.state] || FX_STATE_LABEL.queued;
  const step = job.step;
  const machine = step
    ? engine.machine(job.runningMachineId || job.waitingMachineId || step.machineId)
    : null;
  const choices = step && job.state === "waiting-choice" ? engine.candidatesFor(step) : [];
  const endAt = job.stepEndsAt;
  const eta = job.etaAt(now);
  return (
    <div className={`fx-card fx-job fx-job-${status.tone}`}>
      <div className="fx-job-head">
        <span className="fx-job-emoji">{job.recipe.emoji}</span>
        <div className="fx-job-title">
          <strong>Orden #{job.id} · {job.recipe.name}</strong>
          <span>{job.batches} tanda{job.batches > 1 ? "s" : ""} → {job.outputQty} un · ETA {fxEta(eta)}</span>
        </div>
        <span className={`fx-status fx-status-${status.tone}`}>{status.text}</span>
        <button className="fx-job-x fx-job-skip" onClick={() => onSkipStep(job.id)} aria-label="Terminar paso actual (test)" title={`Terminar solo "${step ? step.name : "paso"}" (test)`}>⏭</button>
        <button className="fx-job-x fx-job-skip" onClick={() => onSkip(job.id)} aria-label="Terminar orden completa (test)" title="Terminar orden completa (test)">⏩</button>
        <button className="fx-job-x" onClick={() => onCancel(job.id)} aria-label="Cancelar orden">✕</button>
      </div>
      <div className="fx-job-steps">
        {job.recipe.steps.map((s, i) => (
          <div key={s.id} className={`fx-job-seg ${i < job.stepIndex ? "done" : ""} ${i === job.stepIndex ? "current" : ""}`}>
            {i === job.stepIndex && job.state === "running" && (
              <div className="fx-job-seg-fill" style={{ width: `${job.progressAt(now) * 100}%` }} />
            )}
            {i < job.stepIndex && <div className="fx-job-seg-fill full" />}
          </div>
        ))}
      </div>
      <div className="fx-job-foot">
        {step ? (
          <span>
            {machine ? machine.def.emoji : "❓"} {step.name}
            {job.state === "running" && ` · termina ${fxEta(endAt)} · faltan ${fxRemaining(endAt - now)}`}
            {job.state === "waiting-machine" && machine && ` · esperando ${machine.def.name}`}
            {job.state === "waiting-insumos" && ` · faltan: ${job.blocked}`}
            {job.state === "waiting-choice" && ` · ¿a dónde lo mandamos?`}
          </span>
        ) : <span>Finalizando…</span>}
        <span className="fx-job-stepcount">Paso {Math.min(job.stepIndex + 1, job.recipe.steps.length)}/{job.recipe.steps.length}</span>
      </div>
      {choices.length > 0 && (
        <div className="fx-choice-row">
          {choices.map((m) => (
            <button key={m.id} type="button" className="fx-choice-btn" onClick={() => onAssign(job.id, m.id)}>
              📥 {m.def.emoji} {m.def.name}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function FxStationCard({ machine, jobs, now }) {
  const runningJobs = jobs.filter((j) => j.state === "running" && j.runningMachineId === machine.id);
  const waitingJobs = jobs.filter((j) => j.state === "waiting-machine" && j.waitingMachineId === machine.id);
  const load = runningJobs.length;
  const working = load > 0;
  const full = load >= machine.capacity;
  const slots = [];
  for (let i = 0; i < machine.capacity; i++) slots.push(i < load);
  const unitCap = machine.def.unitCap || 0;
  const unitsIn = runningJobs.reduce((sum, j) => sum + j.outputQty, 0);
  return (
    <div className={`fx-card fx-machine fx-station ${working ? "fx-working" : ""}`}>
      {waitingJobs.length > 0 && <span className="fx-bottleneck">⚠️ {waitingJobs.length} en espera</span>}
      <div className="fx-station-top">
        {working && <span className="fx-steam" aria-hidden="true"><i /><i /><i /></span>}
        <div className="fx-machine-emoji">{machine.def.emoji}</div>
      </div>
      <strong className="fx-machine-name">{machine.def.name}</strong>
      <span className="fx-machine-desc">{machine.def.desc}</span>

      <div className="fx-chefs" title={`${load} de ${machine.capacity} puestos en uso`}>
        {slots.map((busy, i) => busy
          ? <span key={i} className="fx-chef">👨‍🍳</span>
          : <span key={i} className="fx-chef-empty" />
        )}
        <span className={`fx-occup ${full ? "full" : ""}`}>{load}/{machine.capacity}</span>
      </div>

      {unitCap > 0 && (
        <div className="fx-units">
          <div className="fx-units-label">
            <span>Espacio</span>
            <strong className={unitsIn >= unitCap ? "full" : ""}>{unitsIn}/{unitCap} un</strong>
          </div>
          <div className="fx-bar">
            <div className={`fx-bar-fill ${unitsIn >= unitCap ? "fx-bar-danger" : "fx-bar-green"}`}
              style={{ width: `${Math.min(100, (unitsIn / unitCap) * 100)}%` }} />
          </div>
        </div>
      )}

      {runningJobs.map((job) => (
        <div key={job.id} className="fx-station-job">
          <div className="fx-station-job-head">
            <span>{job.recipe.emoji} {job.step.name} · #{job.id}</span>
            <span>{fxRemaining(job.stepEndsAt - now)}</span>
          </div>
          <div className="fx-bar fx-bar-sm">
            <div className="fx-bar-fill" style={{ width: `${job.progressAt(now) * 100}%` }} />
          </div>
        </div>
      ))}

      {waitingJobs.length > 0 && (
        <div className="fx-station-queue">
          {waitingJobs.map((job) => (
            <span key={job.id} className="fx-queue-chip">{job.recipe.emoji} #{job.id} espera</span>
          ))}
        </div>
      )}

      {!working && waitingJobs.length === 0 && <span className="fx-station-idle">Libre</span>}
      {machine.def.price > 0 && <span className="fx-station-price">R$ {machine.def.price.toLocaleString("pt-BR")}</span>}
    </div>
  );
}

function FxInsumoCard({ insumo, onRestock }) {
  const full = insumo.stock >= insumo.def.max;
  return (
    <div className={`fx-card fx-insumo ${insumo.low ? "fx-insumo-low" : ""}`}>
      <div className="fx-insumo-head">
        <span className="fx-insumo-emoji">{insumo.def.emoji}</span>
        <div>
          <strong>{insumo.def.name}</strong>
          <span>{fxQty(insumo.stock)} / {insumo.def.max} {insumo.def.unit}</span>
        </div>
      </div>
      <div className="fx-bar">
        <div className={`fx-bar-fill ${insumo.low ? "fx-bar-danger" : "fx-bar-green"}`} style={{ width: `${insumo.pct * 100}%` }} />
      </div>
      <button className={`fx-btn fx-btn-block ${full ? "fx-btn-off" : "fx-btn-amber"}`} onClick={() => onRestock(insumo.id)}>
        {full ? "Depósito lleno" : `Reposición +${insumo.def.pack} ${insumo.def.unit}`}
      </button>
    </div>
  );
}

// ---------- Cocina ilustrada (escena de juego) ----------
const FXK_SPRITES = {
  mesa: "uploads/kitchen/fxk-mesa.png?v=2",
  "heladera-cond": "uploads/kitchen/fxk-heladera-cond.png?v=2",
  "heladera-casa": "uploads/kitchen/fxk-heladera-casa.png?v=2",
  cortadora: "uploads/kitchen/fxk-cortadora.png?v=2",
  horno4: "uploads/kitchen/fxk-horno4.png?v=2",
  "horno-casa": "uploads/kitchen/fxk-horno-casa.png?v=2",
  empaque: "uploads/kitchen/fxk-empaque.png?v=2",
};
const FXK_DEFAULT_SPRITE = "uploads/kitchen/fxk-generic.png?v=2";
const FXK_SIZE = {
  mesa: 118, "heladera-cond": 158, "heladera-casa": 124, cortadora: 96,
  horno4: 138, "horno-casa": 116, empaque: 112,
};

// posiciones espaciales en la cocina (vista desde arriba, % del ambiente)
const FXK_SPOTS = {
  "heladera-cond": { left: "2.5%", top: "5%" },
  horno4: { left: "25%", top: "8%" },
  "horno-casa": { left: "47%", top: "10%" },
  "heladera-casa": { left: "66%", top: "8%" },
  cortadora: { left: "3%", top: "52%" },
  mesa: { left: "36%", top: "47%" },
  empaque: { left: "76%", top: "50%" },
};
const FXK_FALLBACK_SPOTS = [
  { left: "16%", top: "70%" },
  { left: "58%", top: "72%" },
  { left: "3%", top: "78%" },
  { left: "84%", top: "76%" },
];

function FxJobMiniActions({ job, onSkipStep, onSkip, onCancel }) {
  return (
    <span className="fxk-mini-actions">
      <button className="fxk-mini-btn" onClick={() => onSkipStep(job.id)} title={`Terminar solo "${job.step ? job.step.name : "paso"}" (test)`} aria-label="Terminar paso (test)">⏭</button>
      <button className="fxk-mini-btn" onClick={() => onSkip(job.id)} title="Terminar orden completa (test)" aria-label="Terminar orden (test)">⏩</button>
      <button className="fxk-mini-btn fxk-mini-x" onClick={() => onCancel(job.id)} title="Cancelar orden" aria-label="Cancelar orden">✕</button>
    </span>
  );
}

function FxKitchenStation({ machine, engine, jobs, now, onSkipStep, onSkip, onCancel, onAssign }) {
  const runningJobs = jobs.filter((j) => j.state === "running" && j.runningMachineId === machine.id);
  const waitingJobs = jobs.filter((j) => j.state === "waiting-machine" && j.waitingMachineId === machine.id);
  const chooseJobs = jobs.filter((j) =>
    j.state === "waiting-choice" && j.step &&
    engine.candidatesFor(j.step).some((m) => m.id === machine.id)
  );
  const load = runningJobs.length;
  const working = load > 0;
  const full = load >= machine.capacity;
  const unitCap = machine.def.unitCap || 0;
  const unitsIn = runningJobs.reduce((sum, j) => sum + j.outputQty, 0);
  const sprite = FXK_SPRITES[machine.id] || FXK_DEFAULT_SPRITE;
  const height = FXK_SIZE[machine.id] || 120;
  const chefs = [];
  for (let i = 0; i < load; i++) chefs.push(i);
  return (
    <div className={`fxk-station ${working ? "working" : ""}`}>
      <div className="fxk-badges">
        <span className={`fxk-occ ${full ? "full" : ""}`}>👨‍🍳 {load}/{machine.capacity}</span>
        {unitCap > 0 && unitsIn > 0 && <span className="fxk-units">📦 {unitsIn}/{unitCap}</span>}
        {waitingJobs.length > 0 && <span className="fxk-wait">⚠️ {waitingJobs.length} esperan</span>}
      </div>
      <div className="fxk-body">
        {working && <span className="fx-steam fxk-steam" aria-hidden="true"><i /><i /><i /></span>}
        <img className="fxk-sprite" src={sprite} alt={machine.def.name} style={{ height }} draggable="false" />
        {chefs.length > 0 && (
          <div className="fxk-chefs">
            {chefs.map((i) => <span key={i} className="fx-chef">👨‍🍳</span>)}
          </div>
        )}
      </div>
      {runningJobs.slice(0, 2).map((job) => (
        <div key={job.id} className="fxk-job-row">
          <div className="fxk-prog" title={`${job.recipe.name} · ${job.step.name}`}>
            <div style={{ width: `${job.progressAt(now) * 100}%` }} />
            <span>{job.recipe.emoji} {job.step.name} · {fxRemaining(job.stepEndsAt - now)}</span>
          </div>
          <FxJobMiniActions job={job} onSkipStep={onSkipStep} onSkip={onSkip} onCancel={onCancel} />
        </div>
      ))}
      {waitingJobs.slice(0, 2).map((job) => (
        <div key={job.id} className="fxk-job-row">
          <span className="fxk-wait-chip">{job.recipe.emoji} #{job.id} espera</span>
          <FxJobMiniActions job={job} onSkipStep={onSkipStep} onSkip={onSkip} onCancel={onCancel} />
        </div>
      ))}
      {chooseJobs.slice(0, 3).map((job) => (
        <button key={job.id} type="button" className="fxk-recv" onClick={() => onAssign(job.id, machine.id)}
          title={`Mandar "${job.step.name}" de la orden #${job.id} a ${machine.def.name}`}>
          📥 Recibir {job.recipe.emoji} #{job.id}
        </button>
      ))}
      <span className="fxk-label">{machine.def.name}</span>
    </div>
  );
}

function FxLaunchRow({ recipe, engine, onStart }) {
  const [batches, setBatches] = React.useState(1);
  const enough = engine.hasInsumosFor(recipe, batches);
  return (
    <div className="fxk-panel-row">
      <span className="fxk-panel-emoji">{recipe.emoji}</span>
      <div className="fxk-panel-info">
        <strong>{recipe.name}</strong>
        <span>{batches} tanda{batches > 1 ? "s" : ""} → {recipe.output * batches} un · ETA {fxEta(Date.now() + recipe.totalMinutes * FX_MINUTE)}</span>
        {!enough && <em>⚠️ Insumos insuficientes: quedará frenada</em>}
      </div>
      <div className="fx-stepper fxk-stepper-sm">
        <button type="button" onClick={() => setBatches((b) => Math.max(1, b - 1))} aria-label="Menos tandas">−</button>
        <strong>{batches}</strong>
        <button type="button" onClick={() => setBatches((b) => Math.min(8, b + 1))} aria-label="Más tandas">+</button>
      </div>
      <button type="button" className="fx-btn fx-btn-amber fxk-go" onClick={() => { onStart(recipe.productId, batches); setBatches(1); }} title="Lanzar producción">
        ▶
      </button>
    </div>
  );
}

function FxRestockRow({ insumo, onRestock }) {
  const full = insumo.stock >= insumo.def.max;
  return (
    <div className={`fxk-panel-row ${insumo.low ? "low" : ""}`}>
      <span className="fxk-panel-emoji">{insumo.def.emoji}</span>
      <div className="fxk-panel-info">
        <strong>{insumo.def.name}</strong>
        <span>{fxQty(insumo.stock)} / {insumo.def.max} {insumo.def.unit}</span>
        <div className="fx-bar fxk-panel-bar">
          <div className={`fx-bar-fill ${insumo.low ? "fx-bar-danger" : "fx-bar-green"}`} style={{ width: `${insumo.pct * 100}%` }} />
        </div>
      </div>
      <button type="button" className={`fx-btn ${full ? "fx-btn-off" : "fx-btn-amber"}`} onClick={() => onRestock(insumo.id)}>
        {full ? "Lleno" : `+${insumo.def.pack} ${insumo.def.unit}`}
      </button>
    </div>
  );
}

function FxKitchenScene({ engine, machines, jobs, now, onStart, onRestock, onSkipStep, onSkip, onCancel, onSkipAll, onAssign }) {
  const [panel, setPanel] = React.useState(null); // null | "launch" | "insumos"
  let fallbackIdx = 0;
  return (
    <div className="fxk-viewport">
      <div className="fxk-room">
        <div className="fxk-fabs">
          <button className="fxk-fab fxk-fab-main" onClick={() => setPanel("launch")}>➕ Nueva tanda</button>
          <button className="fxk-fab" onClick={() => setPanel("insumos")}>🧺 Insumos</button>
          {jobs.length > 0 && (
            <button className="fxk-fab fxk-fab-test" onClick={onSkipAll} title="Completa todas las órdenes al instante (solo para testear)">
              ⏩ Todo (test)
            </button>
          )}
        </div>
        <div className="fxk-backwall" aria-hidden="true">
          <span className="fxk-window"><i /></span>
          <span className="fxk-sign">BREAK · COCINA</span>
          <span className="fxk-deco fxk-deco-plant">🪴</span>
          <span className="fxk-deco fxk-deco-pans">🍳 🥘 🔪</span>
        </div>
        <div className="fxk-counter fxk-counter-back" aria-hidden="true" />
        <div className="fxk-counter fxk-counter-left" aria-hidden="true" />
        <div className="fxk-counter fxk-counter-right" aria-hidden="true" />
        <div className="fxk-rug" aria-hidden="true" />
        <div className="fxk-doormat" aria-hidden="true">🚚 Despacho</div>
        {machines.map((m) => {
          const spot = FXK_SPOTS[m.id] || FXK_FALLBACK_SPOTS[fallbackIdx++ % FXK_FALLBACK_SPOTS.length];
          const z = 10 + Math.round(parseFloat(spot.top));
          return (
            <div key={m.id} className="fxk-spot" style={{ left: spot.left, top: spot.top, zIndex: z }}>
              <FxKitchenStation machine={m} engine={engine} jobs={jobs} now={now} onSkipStep={onSkipStep} onSkip={onSkip} onCancel={onCancel} onAssign={onAssign} />
            </div>
          );
        })}
      </div>
      {panel && (
        <div className="fxk-overlay" onClick={() => setPanel(null)}>
          <div className="fxk-panel" onClick={(e) => e.stopPropagation()}>
            <div className="fxk-panel-head">
              <strong>{panel === "launch" ? "🧾 Lanzar nueva tanda" : "🧺 Almacén de insumos"}</strong>
              <button className="fxk-mini-btn fxk-mini-x" onClick={() => setPanel(null)} aria-label="Cerrar">✕</button>
            </div>
            <div className="fxk-panel-body">
              {panel === "launch"
                ? engine.recipes.map((r) => <FxLaunchRow key={r.productId} recipe={r} engine={engine} onStart={onStart} />)
                : engine.insumos.map((i) => <FxRestockRow key={i.id} insumo={i} onRestock={onRestock} />)}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ---------- Configuración (edición de variables meta) ----------
function fxMetaToDraft(meta) {
  const clone = JSON.parse(JSON.stringify(meta));
  clone.recipes = clone.recipes.map((r) => ({
    ...r,
    steps: r.steps.map((s) => ({
      ...s,
      inputs: Object.keys(s.inputs || {}).map((insumoId) => ({ insumoId, qty: s.inputs[insumoId] })),
    })),
  }));
  return clone;
}

function fxDraftToMeta(draft) {
  const num = (v, min, isInt) => {
    let n = Number.parseFloat(String(v).replace(",", "."));
    if (!Number.isFinite(n)) n = min;
    if (isInt) n = Math.round(n);
    return Math.max(min, n);
  };
  return {
    machines: draft.machines.map((m) => ({
      id: m.id, emoji: (m.emoji || "⚙️").trim(), name: (m.name || "Máquina").trim(),
      capacity: num(m.capacity, 1, true), price: num(m.price || 0, 0),
      unitCap: num(m.unitCap || 0, 0, true), group: (m.group || "").trim(), desc: (m.desc || "").trim(),
    })),
    insumos: draft.insumos.map((i) => ({
      id: i.id, emoji: (i.emoji || "📦").trim(), name: (i.name || "Insumo").trim(),
      unit: (i.unit || "un").trim(), start: num(i.start || 0, 0),
      pack: num(i.pack, 1), max: num(i.max, 1),
    })),
    recipes: draft.recipes.map((r) => ({
      productId: r.productId, emoji: (r.emoji || "🍞").trim(), name: (r.name || "Producto").trim(),
      output: num(r.output, 1, true),
      steps: r.steps.map((s) => ({
        id: s.id, name: (s.name || "Paso").trim(), machineId: s.machineId,
        minutes: num(s.minutes, 1),
        inputs: s.inputs.reduce((acc, line) => {
          const qty = num(line.qty, 0);
          if (line.insumoId && qty > 0) acc[line.insumoId] = qty;
          return acc;
        }, {}),
      })),
    })),
  };
}

function FxField({ label, children, grow }) {
  return (
    <label className="fx-cfg-field" style={grow ? { flex: 1, minWidth: 120 } : undefined}>
      <span>{label}</span>
      {children}
    </label>
  );
}

function FxConfigView({ meta, onSave, onRestore, onClose }) {
  const [draft, setDraft] = React.useState(() => fxMetaToDraft(meta));

  const upd = (fn) => setDraft((d) => {
    const copy = JSON.parse(JSON.stringify(d));
    fn(copy);
    return copy;
  });

  const machineUsed = (id) => draft.recipes.some((r) => r.steps.some((s) => s.machineId === id));
  const insumoUsed = (id) => draft.recipes.some((r) => r.steps.some((s) => s.inputs.some((line) => line.insumoId === id)));

  const removeMachine = (idx) => {
    const machine = draft.machines[idx];
    if (machineUsed(machine.id)) {
      window.alert(`No se puede eliminar "${machine.name}": está usada en un procedimiento. Cambiá primero esos pasos a otra máquina.`);
      return;
    }
    upd((d) => { d.machines.splice(idx, 1); });
  };

  const removeInsumo = (idx) => {
    const insumo = draft.insumos[idx];
    if (insumoUsed(insumo.id)) {
      window.alert(`No se puede eliminar "${insumo.name}": está usado en un procedimiento. Sacalo primero de esos pasos.`);
      return;
    }
    upd((d) => { d.insumos.splice(idx, 1); });
  };

  const save = () => {
    if (!draft.machines.length) { window.alert("Tiene que haber al menos una máquina."); return; }
    if (draft.recipes.some((r) => !r.steps.length)) { window.alert("Cada producto necesita al menos un paso."); return; }
    onSave(fxDraftToMeta(draft));
  };

  return (
    <main className="fx-page">
      <div className="fx-max">
        <header className="fx-top">
          <button className="fx-back" onClick={onClose} aria-label="Volver al monitor">‹</button>
          <div className="fx-title">
            <strong>⚙️ Configuración</strong>
            <span>Variables meta · maquinaria, insumos y procedimientos</span>
          </div>
        </header>

        <section className="fx-section">
          <h2>🏗️ Maquinaria</h2>
          <div className="fx-card">
            {draft.machines.map((m, idx) => (
              <div className="fx-config-row" key={m.id}>
                <FxField label="Icono">
                  <input className="fx-input fx-input-emoji" value={m.emoji} maxLength={4}
                    onChange={(e) => upd((d) => { d.machines[idx].emoji = e.target.value; })} />
                </FxField>
                <FxField label="Nombre" grow>
                  <input className="fx-input" value={m.name} placeholder="Horno a piedra"
                    onChange={(e) => upd((d) => { d.machines[idx].name = e.target.value; })} />
                </FxField>
                <FxField label="Capacidad">
                  <input className="fx-input fx-input-sm" type="number" min="1" value={m.capacity}
                    onChange={(e) => upd((d) => { d.machines[idx].capacity = e.target.value; })} />
                </FxField>
                <FxField label="Precio R$">
                  <input className="fx-input fx-input-sm" type="number" min="0" value={m.price || 0}
                    onChange={(e) => upd((d) => { d.machines[idx].price = e.target.value; })} />
                </FxField>
                <FxField label="Espacio (un)">
                  <input className="fx-input fx-input-sm" type="number" min="0" value={m.unitCap || 0}
                    onChange={(e) => upd((d) => { d.machines[idx].unitCap = e.target.value; })} />
                </FxField>
                <FxField label="Grupo (alternativas)">
                  <input className="fx-input fx-input-sm" value={m.group || ""} placeholder="Hornos"
                    onChange={(e) => upd((d) => { d.machines[idx].group = e.target.value; })} />
                </FxField>
                <FxField label="Descripción" grow>
                  <input className="fx-input" value={m.desc} placeholder="Detalle del equipo"
                    onChange={(e) => upd((d) => { d.machines[idx].desc = e.target.value; })} />
                </FxField>
                <button className="fx-del" onClick={() => removeMachine(idx)} aria-label="Eliminar máquina">✕</button>
              </div>
            ))}
            <button className="fx-add" onClick={() => upd((d) => {
              d.machines.push({ id: fxNewId("maq"), emoji: "⚙️", name: "", capacity: 1, price: 0, unitCap: 0, group: "", desc: "" });
            })}>+ Agregar máquina</button>
          </div>
        </section>

        <section className="fx-section">
          <h2>🧺 Insumos</h2>
          <div className="fx-card">
            {draft.insumos.map((ins, idx) => (
              <div className="fx-config-row" key={ins.id}>
                <FxField label="Icono">
                  <input className="fx-input fx-input-emoji" value={ins.emoji} maxLength={4}
                    onChange={(e) => upd((d) => { d.insumos[idx].emoji = e.target.value; })} />
                </FxField>
                <FxField label="Nombre" grow>
                  <input className="fx-input" value={ins.name} placeholder="Harina 000"
                    onChange={(e) => upd((d) => { d.insumos[idx].name = e.target.value; })} />
                </FxField>
                <FxField label="Unidad">
                  <input className="fx-input fx-input-sm" value={ins.unit} placeholder="kg"
                    onChange={(e) => upd((d) => { d.insumos[idx].unit = e.target.value; })} />
                </FxField>
                <FxField label="Pack repo.">
                  <input className="fx-input fx-input-sm" type="number" min="1" value={ins.pack}
                    onChange={(e) => upd((d) => { d.insumos[idx].pack = e.target.value; })} />
                </FxField>
                <FxField label="Tope depósito">
                  <input className="fx-input fx-input-sm" type="number" min="1" value={ins.max}
                    onChange={(e) => upd((d) => { d.insumos[idx].max = e.target.value; })} />
                </FxField>
                <button className="fx-del" onClick={() => removeInsumo(idx)} aria-label="Eliminar insumo">✕</button>
              </div>
            ))}
            <button className="fx-add" onClick={() => upd((d) => {
              d.insumos.push({ id: fxNewId("ins"), emoji: "📦", name: "", unit: "un", start: 0, pack: 10, max: 50 });
            })}>+ Agregar insumo</button>
          </div>
        </section>

        <section className="fx-section">
          <h2>🧾 Productos y procedimientos</h2>
          {draft.recipes.map((r, ri) => (
            <div className="fx-card" key={r.productId} style={{ marginBottom: 14 }}>
              <div className="fx-config-row">
                <FxField label="Icono">
                  <input className="fx-input fx-input-emoji" value={r.emoji} maxLength={4}
                    onChange={(e) => upd((d) => { d.recipes[ri].emoji = e.target.value; })} />
                </FxField>
                <FxField label="Producto" grow>
                  <input className="fx-input" value={r.name} placeholder="Focaccia"
                    onChange={(e) => upd((d) => { d.recipes[ri].name = e.target.value; })} />
                </FxField>
                <FxField label="Un. por tanda">
                  <input className="fx-input fx-input-sm" type="number" min="1" value={r.output}
                    onChange={(e) => upd((d) => { d.recipes[ri].output = e.target.value; })} />
                </FxField>
                <button className="fx-del" onClick={() => {
                  if (window.confirm(`¿Eliminar el producto "${r.name}" y su procedimiento?`)) {
                    upd((d) => { d.recipes.splice(ri, 1); });
                  }
                }} aria-label="Eliminar producto">✕</button>
              </div>

              <div className="fx-config-sub">Procedimiento (en orden)</div>
              {r.steps.map((s, si) => (
                <div className="fx-step-edit" key={s.id}>
                  <div className="fx-config-row">
                    <FxField label="Paso" grow>
                      <input className="fx-input" value={s.name} placeholder="Amasado"
                        onChange={(e) => upd((d) => { d.recipes[ri].steps[si].name = e.target.value; })} />
                    </FxField>
                    <FxField label="Máquina" grow>
                      <select className="fx-input" value={s.machineId}
                        onChange={(e) => upd((d) => { d.recipes[ri].steps[si].machineId = e.target.value; })}>
                        {draft.machines.map((m) => <option key={m.id} value={m.id}>{m.emoji} {m.name || "(sin nombre)"}</option>)}
                      </select>
                    </FxField>
                    <FxField label="Minutos">
                      <input className="fx-input fx-input-sm" type="number" min="1" value={s.minutes}
                        onChange={(e) => upd((d) => { d.recipes[ri].steps[si].minutes = e.target.value; })} />
                    </FxField>
                    <span className="fx-dur-hint">{fxDur(Number(s.minutes) || 0)}</span>
                    <button className="fx-del" onClick={() => upd((d) => { d.recipes[ri].steps.splice(si, 1); })} aria-label="Eliminar paso">✕</button>
                  </div>

                  <div className="fx-config-sub">Insumos que consume (por tanda)</div>
                  {s.inputs.map((line, li) => {
                    const ins = draft.insumos.find((i) => i.id === line.insumoId);
                    return (
                      <div className="fx-config-row" key={`${s.id}-${li}`}>
                        <select className="fx-input" style={{ flex: 1 }} value={line.insumoId}
                          onChange={(e) => upd((d) => { d.recipes[ri].steps[si].inputs[li].insumoId = e.target.value; })}>
                          {draft.insumos.map((i) => <option key={i.id} value={i.id}>{i.emoji} {i.name || "(sin nombre)"}</option>)}
                        </select>
                        <input className="fx-input fx-input-sm" type="number" min="0" step="0.1" value={line.qty}
                          onChange={(e) => upd((d) => { d.recipes[ri].steps[si].inputs[li].qty = e.target.value; })} />
                        <span className="fx-dur-hint">{ins ? ins.unit : ""}</span>
                        <button className="fx-del" onClick={() => upd((d) => { d.recipes[ri].steps[si].inputs.splice(li, 1); })} aria-label="Quitar insumo">✕</button>
                      </div>
                    );
                  })}
                  {draft.insumos.length > 0 && (
                    <button className="fx-add" onClick={() => upd((d) => {
                      d.recipes[ri].steps[si].inputs.push({ insumoId: draft.insumos[0].id, qty: 1 });
                    })}>+ Insumo</button>
                  )}
                </div>
              ))}
              <button className="fx-add" onClick={() => upd((d) => {
                d.recipes[ri].steps.push({ id: fxNewId("paso"), name: "", machineId: draft.machines[0] ? draft.machines[0].id : "", minutes: 30, inputs: [] });
              })}>+ Agregar paso</button>
            </div>
          ))}
          <button className="fx-add fx-add-light" onClick={() => upd((d) => {
            d.recipes.push({
              productId: fxNewId("prod"), emoji: "🍞", name: "", output: 1,
              steps: [{ id: fxNewId("paso"), name: "", machineId: draft.machines[0] ? draft.machines[0].id : "", minutes: 30, inputs: [] }],
            });
          })}>+ Agregar producto</button>
        </section>

        <div className="fx-savebar">
          <button className="fx-btn fx-btn-amber fx-savebar-main" onClick={save}>💾 Guardar configuración</button>
          <button className="fx-btn" onClick={onClose}>Cancelar</button>
          <button className="fx-btn fx-btn-off" onClick={() => {
            if (window.confirm("¿Restaurar la configuración original de fábrica? Se pierden tus cambios de máquinas, insumos y procedimientos.")) onRestore();
          }}>Restaurar original</button>
        </div>
      </div>
    </main>
  );
}

// ---------- Componente principal ----------
function Factory({ onExit, onProduced }) {
  const producedRef = React.useRef(onProduced);
  producedRef.current = onProduced;
  const hooks = {
    onProduced: (name, qty) => { if (producedRef.current) producedRef.current(name, qty); },
  };

  const [meta, setMeta] = React.useState(loadFactoryMeta);
  const [view, setView] = React.useState("floor"); // "floor" | "config"

  const engineRef = React.useRef(null);
  const buildEngine = (m) => {
    const engine = new FactoryEngine(loadFactoryState(), hooks, m);
    engine.tick(Date.now());
    saveFactoryState(engine);
    return engine;
  };
  if (!engineRef.current) engineRef.current = buildEngine(meta);
  const engine = engineRef.current;

  const [now, setNow] = React.useState(() => Date.now());
  const [pops, setPops] = React.useState([]);

  React.useEffect(() => {
    let saveAcc = 0;
    const iv = setInterval(() => {
      const ts = Date.now();
      const eng = engineRef.current;
      eng.tick(ts);
      if (eng.pendingPops.length) {
        const drained = eng.pendingPops.splice(0);
        setPops((p) => [...p, ...drained]);
        drained.forEach((pop) => {
          setTimeout(() => setPops((p) => p.filter((x) => x.id !== pop.id)), 3500);
        });
        saveFactoryState(eng);
      }
      saveAcc += 1;
      if (saveAcc >= 5) { saveAcc = 0; saveFactoryState(eng); }
      setNow(ts);
    }, 1000);
    return () => { clearInterval(iv); saveFactoryState(engineRef.current); };
  }, []);

  const act = (fn) => { fn(); engine.tick(Date.now()); saveFactoryState(engine); setNow(Date.now()); };
  const startJob = (recipeId, batches) => act(() => engine.startJob(recipeId, batches));
  const cancelJob = (jobId) => act(() => engine.cancelJob(jobId));
  const skipJob = (jobId) => act(() => engine.finishJobNow(jobId));
  const skipStep = (jobId) => act(() => engine.finishStepNow(jobId));
  const assignJob = (jobId, machineId) => act(() => engine.assignMachine(jobId, machineId));
  const skipAll = () => act(() => engine.finishAllNow());
  const restockInsumo = (id) => act(() => engine.restockInsumo(id));

  const applyMeta = (newMeta) => {
    saveFactoryState(engineRef.current); // preservar stock y órdenes en curso
    saveFactoryMeta(newMeta);
    setMeta(newMeta);
    engineRef.current = buildEngine(newMeta);
    setView("floor");
    setNow(Date.now());
  };

  const restoreMeta = () => {
    localStorage.removeItem(FX_META_KEY);
    applyMeta(fxDefaultMeta());
  };

  const resetFactory = () => {
    if (!window.confirm("¿Reiniciar el monitor de producción? Se borran órdenes, stock de insumos e historial (no afecta la caja ni la configuración).")) return;
    localStorage.removeItem(FX_STORAGE_KEY);
    engineRef.current = new FactoryEngine(null, hooks, meta);
    setNow(Date.now());
  };

  if (view === "config") {
    return <FxConfigView meta={meta} onSave={applyMeta} onRestore={restoreMeta} onClose={() => setView("floor")} />;
  }

  const activeJobs = engine.jobs;
  const totalProduced = Object.keys(engine.produced).reduce((s, k) => s + engine.produced[k], 0);
  const nextDone = activeJobs.length
    ? activeJobs.reduce((min, j) => Math.min(min, j.etaAt(now)), Infinity)
    : null;

  return (
    <main className="fx-page">
      <div className="fx-max">
        <header className="fx-top">
          <button className="fx-back" onClick={onExit} aria-label="Volver a caja">‹</button>
          <div className="fx-title">
            <strong>🏭 Producción Break</strong>
            <span>Monitor en vivo · tiempos reales</span>
          </div>
          <div className="fx-coins">⚙️ {activeJobs.length} en curso</div>
          <button className="fx-back fx-config-btn" onClick={() => setView("config")} aria-label="Configuración" title="Configurar maquinaria, insumos y procedimientos">⚙️</button>
        </header>

        <section className="fx-card fx-level">
          <div className="fx-level-badge">📦</div>
          <div className="fx-level-body">
            <div className="fx-level-row">
              <strong>Producción acumulada</strong>
              {nextDone && <span>Próxima entrega: {fxEta(nextDone)}</span>}
            </div>
            <div className="fx-produced">
              <span className="fx-tag">Total: {totalProduced} un</span>
              {engine.recipes.map((r) => (
                <span key={r.productId} className="fx-tag">{r.emoji} {r.name}: {engine.produced[r.productId] || 0}</span>
              ))}
            </div>
          </div>
        </section>

        <section className="fx-section">
          <h2>🍳 Cocina en vivo</h2>
          <FxKitchenScene
            engine={engine}
            machines={engine.machines}
            jobs={activeJobs}
            now={now}
            onStart={startJob}
            onRestock={restockInsumo}
            onSkipStep={skipStep}
            onSkip={skipJob}
            onCancel={cancelJob}
            onSkipAll={skipAll}
            onAssign={assignJob}
          />
        </section>

        <section className="fx-section">
          <h2>🧾 Lanzar producción</h2>
          {engine.recipes.map((recipe) => (
            <FxRecipeCard key={recipe.productId} recipe={recipe} engine={engine} onStart={startJob} />
          ))}
        </section>

        <section className="fx-section">
          <h2>
            ⚙️ Órdenes en curso {activeJobs.length > 0 && <span className="fx-count">{activeJobs.length}</span>}
            {activeJobs.length > 0 && (
              <button className="fx-test-btn" onClick={skipAll} title="Completa todas las órdenes al instante (solo para testear)">
                ⏩ Terminar todo (test)
              </button>
            )}
          </h2>
          {activeJobs.length === 0 ? (
            <div className="fx-card fx-empty">Sin órdenes activas. Lanzá una producción arriba. 👆</div>
          ) : (
            activeJobs.map((job) => <FxJobCard key={job.id} job={job} engine={engine} now={now} onCancel={cancelJob} onSkip={skipJob} onSkipStep={skipStep} onAssign={assignJob} />)
          )}
        </section>

        <section className="fx-section">
          <h2>🏗️ Detalle de estaciones</h2>
          <div className="fx-kitchen">
            <div className="fx-grid fx-grid-machines">
              {engine.machines.map((machine) => (
                <FxStationCard key={machine.id} machine={machine} jobs={activeJobs} now={now} />
              ))}
            </div>
          </div>
        </section>

        <section className="fx-section">
          <h2>🧺 Almacén de insumos</h2>
          <div className="fx-grid fx-grid-insumos">
            {engine.insumos.map((insumo) => (
              <FxInsumoCard key={insumo.id} insumo={insumo} onRestock={restockInsumo} />
            ))}
          </div>
        </section>

        <section className="fx-section">
          <h2>📜 Registro</h2>
          <div className="fx-card fx-log">
            {engine.log.map((entry, i) => (
              <div key={`${entry.t}-${i}`} className={`fx-log-row fx-log-${entry.tone}`}>
                <span className="fx-log-time">{entry.t}</span>
                <span>{entry.msg}</span>
              </div>
            ))}
          </div>
        </section>

        <footer className="fx-footer">
          <button className="fx-reset" onClick={resetFactory}>Reiniciar monitor</button>
        </footer>
      </div>

      <div className="fx-pops" aria-hidden="true">
        {pops.map((pop) => <div key={pop.id} className="fx-pop">{pop.text}</div>)}
      </div>
    </main>
  );
}

Object.assign(window, {
  Factory,
  BreakFactoryApi: {
    loadMeta: loadFactoryMeta,
    loadState: loadFactoryState,
    insumoSnapshot: factoryInsumoSnapshot,
    applyInsumoDeltas: applyFactoryInsumoDeltas,
  },
});
