/* global React, Icon */
const JR_STORAGE_KEY = "break_journal_entries_v1";
const JR_CATALOG_STORAGE_KEY = "break_journal_catalog_v1";
const JR_INVENTORY_ACCOUNT_IDS = ["1210", "1220", "1230", "1310", "1320", "1330"];

const JR_DEFAULT_CATALOG = {
  areas: [
    { id: "main", name: "Insumos principales", accountId: "1210", aliases: "stock principal, insumos, principal, harina, queso, aceite, levadura, sal" },
    { id: "toppings", name: "Toppings", accountId: "1220", aliases: "topping, toppings, tomate, rucula, rúcula, pesto, cebolla, aceituna, hongos, salsa" },
    { id: "packaging", name: "Packaging", accountId: "1230", aliases: "packaging, envases, envase, bolsas, bolsa, caja, cajas" },
    { id: "wip", name: "Focaccias en proceso", accountId: "1310", aliases: "proceso, en proceso, produccion, producción" },
    { id: "finished", name: "Focaccias envasadas", accountId: "1320", aliases: "envasadas, terminadas, listas" },
    { id: "consignment", name: "Focaccias en consignación", accountId: "1330", aliases: "consignacion, consignación, local, locales" },
  ],
  items: [
    { id: "harina", areaId: "main", name: "Harina", unit: "kg", aliases: "farinha", unitCost: 0 },
    { id: "queso", areaId: "main", name: "Queso", unit: "kg", aliases: "queijo", unitCost: 0 },
    { id: "aceite", areaId: "main", name: "Aceite", unit: "l", aliases: "oleo, óleo", unitCost: 0 },
    { id: "levadura", areaId: "main", name: "Levadura", unit: "kg", aliases: "fermento", unitCost: 0 },
    { id: "sal", areaId: "main", name: "Sal", unit: "kg", aliases: "", unitCost: 0 },
    { id: "tomate", areaId: "toppings", name: "Tomate", unit: "kg", aliases: "", unitCost: 0 },
    { id: "rucula", areaId: "toppings", name: "Rúcula", unit: "kg", aliases: "rucula", unitCost: 0 },
    { id: "pesto", areaId: "toppings", name: "Pesto", unit: "kg", aliases: "", unitCost: 0 },
    { id: "cebolla", areaId: "toppings", name: "Cebolla", unit: "kg", aliases: "cebola", unitCost: 0 },
    { id: "aceituna", areaId: "toppings", name: "Aceituna", unit: "kg", aliases: "aceitunas, azeitona", unitCost: 0 },
    { id: "bolsas", areaId: "packaging", name: "Bolsas", unit: "un", aliases: "bolsa", unitCost: 0 },
    { id: "envases", areaId: "packaging", name: "Envases", unit: "un", aliases: "envase, embalagem", unitCost: 0 },
    { id: "focaccia-proceso", areaId: "wip", name: "Focaccia en proceso", unit: "un", aliases: "focaccias en proceso", unitCost: 0 },
    { id: "focaccia-envasada", areaId: "finished", name: "Focaccia envasada", unit: "un", aliases: "focaccias envasadas", unitCost: 0 },
    { id: "focaccia-consignacion", areaId: "consignment", name: "Focaccia en consignación", unit: "un", aliases: "focaccias en consignacion, focaccias en consignación", unitCost: 0 },
  ],
};

const JR_ACCOUNTS = [
  { id: "1010", name: "Caja / dinero total", group: "Activo", normal: "debit", tags: ["cash"] },
  { id: "1110", name: "Pix por cobrar", group: "Activo", normal: "debit" },
  { id: "1120", name: "Cuentas por cobrar consignación", group: "Activo", normal: "debit" },
  { id: "1130", name: "Cuentas por cobrar socios", group: "Activo", normal: "debit" },
  { id: "1210", name: "Inventario insumos principales", group: "Activo", normal: "debit", tags: ["inventory"] },
  { id: "1220", name: "Inventario toppings", group: "Activo", normal: "debit", tags: ["inventory"] },
  { id: "1230", name: "Inventario packaging", group: "Activo", normal: "debit", tags: ["inventory"] },
  { id: "1310", name: "Focaccias en proceso", group: "Activo", normal: "debit", tags: ["inventory", "wip"] },
  { id: "1320", name: "Focaccias envasadas", group: "Activo", normal: "debit", tags: ["inventory", "finished"] },
  { id: "1330", name: "Focaccias en consignación", group: "Activo", normal: "debit", tags: ["inventory", "stores"] },
  { id: "1390", name: "Diferencias por revisar", group: "Activo", normal: "debit" },
  { id: "1410", name: "Equipamiento", group: "Activo", normal: "debit" },
  { id: "1420", name: "Utensilios / tuppers", group: "Activo", normal: "debit" },
  { id: "2010", name: "Proveedores por pagar", group: "Pasivo", normal: "credit" },
  { id: "2020", name: "Deudas con socios", group: "Pasivo", normal: "credit" },
  { id: "2090", name: "Otros pasivos", group: "Pasivo", normal: "credit" },
  { id: "3010", name: "Capital socios", group: "Patrimonio", normal: "credit" },
  { id: "3020", name: "Aportes de socios", group: "Patrimonio", normal: "credit" },
  { id: "3090", name: "Patrimonio inicial", group: "Patrimonio", normal: "credit" },
  { id: "4010", name: "Ventas a la calle", group: "Ingresos", normal: "credit" },
  { id: "4020", name: "Ventas consignación", group: "Ingresos", normal: "credit" },
  { id: "4030", name: "Ventas take-away", group: "Ingresos", normal: "credit" },
  { id: "4090", name: "Otros ingresos", group: "Ingresos", normal: "credit" },
  { id: "5010", name: "CMV venta a la calle", group: "Costos", normal: "debit" },
  { id: "5020", name: "CMV consignación", group: "Costos", normal: "debit" },
  { id: "5030", name: "CMV take-away", group: "Costos", normal: "debit" },
  { id: "5110", name: "Marketing / producto regalado", group: "Costos", normal: "debit" },
  { id: "5120", name: "Merma de producto", group: "Costos", normal: "debit" },
  { id: "5130", name: "Consumo interno", group: "Costos", normal: "debit" },
  { id: "5190", name: "Ajuste negativo inventario", group: "Costos", normal: "debit" },
  { id: "6010", name: "Alquiler", group: "Gastos", normal: "debit" },
  { id: "6020", name: "Servicios", group: "Gastos", normal: "debit" },
  { id: "6030", name: "Sueldos / pagos personal", group: "Gastos", normal: "debit" },
  { id: "6040", name: "Transporte", group: "Gastos", normal: "debit" },
  { id: "6050", name: "Limpieza", group: "Gastos", normal: "debit" },
  { id: "6060", name: "Marketing general", group: "Gastos", normal: "debit" },
  { id: "6070", name: "Herramientas / utensilios", group: "Gastos", normal: "debit" },
  { id: "6080", name: "Gastos varios", group: "Gastos", normal: "debit" },
];

const JR_GROUPS = ["Activo", "Pasivo", "Patrimonio", "Ingresos", "Costos", "Gastos"];
const JR_STATUS = [
  { id: "draft", label: "Borrador", tone: "audit" },
  { id: "posted", label: "Aprobado", tone: "in" },
];
const JR_DIARY_COLUMNS = [
  { key: "date", label: "fecha" },
  { key: "status", label: "estado" },
  { key: "memo", label: "memo" },
  { key: "account", label: "cuenta" },
  { key: "debit", label: "debe" },
  { key: "credit", label: "haber" },
  { key: "note", label: "nota" },
];
const JR_INVENTORY_COLUMNS = [
  { key: "date", label: "Fecha" },
  { key: "area", label: "Área" },
  { key: "item", label: "Item" },
  { key: "qty", label: "Cantidad", align: "right" },
  { key: "unit", label: "Unidad" },
  { key: "unitCost", label: "Costo unit.", align: "right" },
  { key: "value", label: "Valor", align: "right" },
  { key: "status", label: "Estado" },
  { key: "source", label: "Origen" },
];
const JR_SALES_COLUMNS = [
  { key: "date", label: "Fecha" },
  { key: "channel", label: "Canal" },
  { key: "location", label: "Punto / local" },
  { key: "qty", label: "Cantidad", align: "right" },
  { key: "amount", label: "Monto", align: "right" },
  { key: "status", label: "Estado" },
  { key: "source", label: "Origen" },
];
const JR_EXPENSE_COLUMNS = [
  { key: "date", label: "Fecha" },
  { key: "type", label: "Tipo" },
  { key: "account", label: "Cuenta" },
  { key: "detail", label: "Detalle" },
  { key: "amount", label: "Monto", align: "right" },
  { key: "status", label: "Estado" },
  { key: "source", label: "Origen" },
];
const JR_BALANCE_COLUMNS = [
  { key: "group", label: "Grupo" },
  { key: "account", label: "Cuenta" },
  { key: "value", label: "Saldo", align: "right" },
];
const JR_RESULT_COLUMNS = [
  { key: "group", label: "Grupo" },
  { key: "account", label: "Cuenta" },
  { key: "value", label: "Importe", align: "right" },
];
const JR_CASHFLOW_COLUMNS = [
  { key: "date", label: "Fecha" },
  { key: "direction", label: "Tipo" },
  { key: "detail", label: "Detalle" },
  { key: "amount", label: "Monto", align: "right" },
  { key: "source", label: "Origen" },
];
const JR_PRODUCTION_COLUMNS = [
  { key: "component", label: "Componente" },
  { key: "group", label: "Grupo" },
  { key: "stock", label: "Stock" },
  { key: "recipe", label: "Uso receta" },
  { key: "possible", label: "Focaccias posibles" },
  { key: "dailyNeed", label: "Necesario objetivo/día" },
  { key: "shortage", label: "Falta para 1 día" },
  { key: "days", label: "Días cubiertos" },
];
const JR_FOCACCIA_DAILY_LIMIT = 150;
const JR_FOCACCIA_COMPONENTS = [
  { id: "harina", label: "Harina", group: "Masa", matcher: /harina|farinha/, stockUnit: "kg", recipeQty: 1, recipeUnit: "kg", recipeOutput: 8 },
  { id: "aceite", label: "Aceite oliva", group: "Masa", matcher: /aceite|azeite|oliva|oleo|óleo/, stockUnit: "l", recipeQty: 0.045, recipeUnit: "l", recipeOutput: 8 },
  { id: "levadura", label: "Levadura", group: "Masa", matcher: /levadura|fermento/, stockUnit: "g", recipeQty: 5, recipeUnit: "g", recipeOutput: 8 },
  { id: "molde", label: "Molde", group: "Packaging", matcher: /molde|aluminio/, stockUnit: "u", recipeQty: 1, recipeUnit: "u", recipeOutput: 1 },
  { id: "sticker", label: "Sticker", group: "Packaging", matcher: /sticker|etiqueta/, stockUnit: "u", recipeQty: 1, recipeUnit: "u", recipeOutput: 1 },
  { id: "bolsa", label: "Bolsa", group: "Packaging", matcher: /bolsa|embalagem/, stockUnit: "m", recipeQty: 0.25, recipeUnit: "m", recipeOutput: 1 },
];
const JR_BRL = new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" });
const JR_NUM = new Intl.NumberFormat("es-AR", { maximumFractionDigits: 2, minimumFractionDigits: 0 });
const JR_NUM_PRECISE = new Intl.NumberFormat("es-AR", { maximumFractionDigits: 3, minimumFractionDigits: 0 });

function jrTodayISO() {
  const now = new Date();
  const local = new Date(now.getTime() - now.getTimezoneOffset() * 60000);
  return local.toISOString().slice(0, 10);
}

function jrDateLabel(value) {
  if (!value) return "";
  return new Date(`${value}T12:00:00`).toLocaleDateString("es-AR", { day: "2-digit", month: "short" });
}

function jrMonthLabel(value) {
  if (value === "all") return "Todo";
  const [year, month] = value.split("-").map(Number);
  return new Date(year, month - 1, 1).toLocaleDateString("es-AR", { month: "short", year: "numeric" });
}

function jrRound(value) {
  const number = Number(value);
  if (!Number.isFinite(number)) return 0;
  return Math.round(number * 100) / 100;
}

function jrParseMoney(value) {
  if (typeof value === "number") return Number.isFinite(value) ? value : 0;
  const text = String(value || "").trim();
  if (!text) return 0;
  const normalized = text.includes(",") ? text.replace(/\./g, "").replace(",", ".") : text;
  const number = Number.parseFloat(normalized.replace(/[^\d.-]/g, ""));
  return Number.isFinite(number) ? number : 0;
}

function jrFmtMoney(value) {
  return JR_BRL.format(jrRound(value));
}

function jrFmtQty(value) {
  return JR_NUM.format(jrRound(value));
}

function jrFmtQtyPrecise(value) {
  const number = Number(value);
  if (!Number.isFinite(number)) return JR_NUM_PRECISE.format(0);
  return JR_NUM_PRECISE.format(Math.round(number * 1000) / 1000);
}

function jrClampProductionTarget(value) {
  const parsed = Math.round(jrParseMoney(value));
  if (!Number.isFinite(parsed) || parsed <= 0) return 1;
  return Math.min(JR_FOCACCIA_DAILY_LIMIT, parsed);
}

function jrMakeId() {
  return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 9)}`;
}

function jrSlugId(value) {
  return String(value || "")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "") || jrMakeId();
}

function jrClone(value) {
  return JSON.parse(JSON.stringify(value));
}

function jrDefaultCatalog() {
  return jrClone(JR_DEFAULT_CATALOG);
}

function jrNormalizeCatalog(catalog) {
  const defaults = jrDefaultCatalog();
  const incoming = catalog || {};
  const incomingAreas = Array.isArray(incoming.areas) ? incoming.areas : [];
  const byId = new Map(incomingAreas.map((area) => [String(area.id || "").trim(), area]));
  const areas = defaults.areas.map((area) => {
    const override = byId.get(area.id) || {};
    return {
      id: area.id,
      name: String(override.name || area.name).trim().slice(0, 80),
      accountId: JR_INVENTORY_ACCOUNT_IDS.includes(String(override.accountId || "")) ? String(override.accountId) : area.accountId,
      aliases: String(override.aliases ?? area.aliases ?? "").trim().slice(0, 400),
      locked: true,
    };
  });
  incomingAreas.forEach((area) => {
    const id = jrSlugId(area.id || area.name);
    if (!id || areas.some((item) => item.id === id)) return;
    areas.push({
      id,
      name: String(area.name || id).trim().slice(0, 80),
      accountId: JR_INVENTORY_ACCOUNT_IDS.includes(String(area.accountId || "")) ? String(area.accountId) : "1210",
      aliases: String(area.aliases || "").trim().slice(0, 400),
      locked: false,
    });
  });

  const areaIds = new Set(areas.map((area) => area.id));
  const sourceItems = Array.isArray(incoming.items) ? incoming.items : defaults.items;
  const seen = new Set();
  const items = sourceItems.map((item) => {
    const name = String(item.name || item.id || "").trim().slice(0, 100);
    const id = jrSlugId(item.id || name);
    if (!name || seen.has(id)) return null;
    seen.add(id);
    const areaId = areaIds.has(String(item.areaId || "")) ? String(item.areaId) : "main";
    return {
      id,
      areaId,
      name,
      unit: String(item.unit || "").trim().slice(0, 20),
      aliases: String(item.aliases || "").trim().slice(0, 400),
      unitCost: jrRound(jrParseMoney(item.unitCost)),
      active: item.active === false ? false : true,
    };
  }).filter(Boolean);

  return { areas, items };
}

function jrAccount(id) {
  return JR_ACCOUNTS.find((account) => account.id === id) || JR_ACCOUNTS[0];
}

function jrStatusMeta(status) {
  return JR_STATUS.find((item) => item.id === status) || JR_STATUS[0];
}

function jrLineTotals(lines) {
  return (Array.isArray(lines) ? lines : []).reduce((acc, line) => {
    acc.debit += jrParseMoney(line.debit);
    acc.credit += jrParseMoney(line.credit);
    return acc;
  }, { debit: 0, credit: 0 });
}

function jrIsBalanced(entry) {
  const totals = jrLineTotals(entry.lines);
  return totals.debit > 0 && Math.abs(jrRound(totals.debit - totals.credit)) < 0.01;
}

function jrNormalizeLine(line) {
  const safeLine = line || {};
  const credit = Math.max(0, jrRound(jrParseMoney(safeLine.credit)));
  return {
    accountId: JR_ACCOUNTS.some((account) => account.id === safeLine.accountId) ? safeLine.accountId : "1010",
    debit: credit > 0 ? 0 : Math.max(0, jrRound(jrParseMoney(safeLine.debit))),
    credit,
    note: String(safeLine.note || ""),
  };
}

function jrDefaultDetails() {
  return {
    report: {
      moneyTotal: 0,
      expensesText: "",
      wipQty: 0,
      packagedQty: 0,
      streetSoldQty: 0,
      deliveredToStoresText: "",
      storeSoldText: "",
      exceptionsText: "",
      stockMainText: "",
      toppingsText: "",
      weeklyToppings: false,
      notesText: "",
    },
    movements: [],
    inventory: [],
    sales: [],
  };
}

function jrNormalizeEntry(entry) {
  const safeEntry = entry || {};
  const lines = Array.isArray(safeEntry.lines) ? safeEntry.lines.map(jrNormalizeLine).filter((line) => line.debit > 0 || line.credit > 0) : [];
  const details = safeEntry.details || jrDefaultDetails();
  return {
    id: safeEntry.id || jrMakeId(),
    date: /^\d{4}-\d{2}-\d{2}$/.test(safeEntry.date || "") ? safeEntry.date : jrTodayISO(),
    status: safeEntry.status === "posted" ? "posted" : "draft",
    memo: String(safeEntry.memo || "Asiento contable"),
    source: String(safeEntry.source || "manual"),
    originText: String(safeEntry.originText || ""),
    confidence: Math.max(0, Math.min(1, Number(safeEntry.confidence) || 0)),
    lines,
    details: {
      report: { ...jrDefaultDetails().report, ...(details.report || {}) },
      movements: Array.isArray(details.movements) ? details.movements : [],
      inventory: Array.isArray(details.inventory) ? details.inventory : [],
      sales: Array.isArray(details.sales) ? details.sales : [],
    },
    createdAt: safeEntry.createdAt || new Date().toISOString(),
    updatedAt: safeEntry.updatedAt || null,
  };
}

function jrDefaultEntry() {
  return {
    id: jrMakeId(),
    date: jrTodayISO(),
    status: "draft",
    memo: "",
    source: "manual",
    originText: "",
    confidence: 0,
    lines: [
      { accountId: "1010", debit: "", credit: "", note: "" },
      { accountId: "4010", debit: "", credit: "", note: "" },
    ],
    details: jrDefaultDetails(),
    createdAt: new Date().toISOString(),
    updatedAt: null,
  };
}

function jrDefaultReport() {
  return {
    date: jrTodayISO(),
    moneyTotal: "",
    expensesText: "",
    wipQty: "",
    packagedQty: "",
    streetSoldQty: "",
    deliveredToStoresText: "",
    storeSoldText: "",
    exceptionsText: "",
    stockMainText: "",
    weeklyToppings: false,
    toppingsText: "",
    notesText: "",
  };
}

function jrLoadLocalEntries() {
  try {
    const saved = localStorage.getItem(JR_STORAGE_KEY);
    const parsed = saved ? JSON.parse(saved) : [];
    return Array.isArray(parsed) ? parsed.map(jrNormalizeEntry) : [];
  } catch {
    return [];
  }
}

function jrLoadLocalCatalog() {
  try {
    const saved = localStorage.getItem(JR_CATALOG_STORAGE_KEY);
    return jrNormalizeCatalog(saved ? JSON.parse(saved) : jrDefaultCatalog());
  } catch {
    return jrDefaultCatalog();
  }
}

async function jrRequest(path, options = {}) {
  const response = await fetch(path, {
    ...options,
    headers: {
      "Content-Type": "application/json",
      ...(options.headers || {}),
    },
  });
  const result = await response.json().catch(() => ({}));
  if (!response.ok) throw new Error(result.error || "No se pudo sincronizar el diario.");
  return result;
}

function jrRawAccountBalance(entries, accountId, onlyPeriod) {
  return entries
    .filter((entry) => entry.status === "posted" && (!onlyPeriod || entry.date.startsWith(onlyPeriod)))
    .reduce((sum, entry) => sum + entry.lines.reduce((lineSum, line) => {
      if (line.accountId !== accountId) return lineSum;
      return lineSum + jrParseMoney(line.debit) - jrParseMoney(line.credit);
    }, 0), 0);
}

function jrDisplayAccountBalance(entries, account, onlyPeriod) {
  const raw = jrRawAccountBalance(entries, account.id, onlyPeriod);
  return account.normal === "credit" ? -raw : raw;
}

function jrEntrySortKey(entry) {
  return `${entry.date || ""}${entry.createdAt || ""}${entry.id || ""}`;
}

function jrEntryHasInventoryMovement(entry) {
  const text = [
    entry.memo,
    entry.source,
    entry.originText,
    ...(entry.details?.movements || []).flatMap((item) => [item.type, item.label, item.detail]),
  ].join(" ");
  return /consumo|producci[oó]n|produc|a proceso|us[oó]|use|gast[eé]|terminad|envasad/i.test(text);
}

function jrEntryIsSnapshot(entry) {
  if (!entry.details?.report) return false;
  const report = entry.details.report;
  return Boolean(
    Number(report.wipQty) ||
    Number(report.packagedQty) ||
    report.stockMainText ||
    report.toppingsText
  );
}

function jrLatestSnapshotEntry(entries) {
  return entries
    .filter(jrEntryIsSnapshot)
    .sort((a, b) => jrEntrySortKey(b).localeCompare(jrEntrySortKey(a)))[0] || null;
}

function jrLatestSnapshot(entries) {
  return jrLatestSnapshotEntry(entries)?.details?.report || jrDefaultDetails().report;
}

function jrComputeReports(entries, month) {
  const accountBalances = Object.fromEntries(JR_ACCOUNTS.map((account) => [account.id, jrDisplayAccountBalance(entries, account)]));
  const periodBalances = Object.fromEntries(JR_ACCOUNTS.map((account) => [account.id, jrDisplayAccountBalance(entries, account, month === "all" ? "" : month)]));
  const sumGroup = (group, source = accountBalances) => JR_ACCOUNTS
    .filter((account) => account.group === group)
    .reduce((sum, account) => sum + (source[account.id] || 0), 0);
  const sumIds = (ids, source = accountBalances) => ids.reduce((sum, id) => sum + (source[id] || 0), 0);
  const income = sumGroup("Ingresos", periodBalances);
  const costs = sumGroup("Costos", periodBalances);
  const expenses = sumGroup("Gastos", periodBalances);
  const totalIncome = sumGroup("Ingresos");
  const totalCosts = sumGroup("Costos");
  const totalExpenses = sumGroup("Gastos");
  const currentResult = totalIncome - totalCosts - totalExpenses;
  const snapshotEntry = jrLatestSnapshotEntry(entries);
  const snapshot = jrLatestSnapshot(entries);
  return {
    accountBalances,
    periodBalances,
    assets: sumGroup("Activo"),
    liabilities: sumGroup("Pasivo"),
    equity: sumGroup("Patrimonio"),
    income,
    costs,
    expenses,
    result: income - costs - expenses,
    totalIncome,
    totalCosts,
    totalExpenses,
    currentResult,
    balanceCheck: sumGroup("Activo") - sumGroup("Pasivo") - sumGroup("Patrimonio") - currentResult,
    cash: accountBalances["1010"] || 0,
    cashDelta: periodBalances["1010"] || 0,
    inventoryValue: sumIds(["1210", "1220", "1230", "1310", "1320", "1330"]),
    inventoryMain: accountBalances["1210"] || 0,
    inventoryToppings: accountBalances["1220"] || 0,
    inventoryPackaging: accountBalances["1230"] || 0,
    inventoryWip: accountBalances["1310"] || 0,
    inventoryFinished: accountBalances["1320"] || 0,
    inventoryStores: accountBalances["1330"] || 0,
    snapshot,
    snapshotDate: snapshotEntry?.date || "",
  };
}

function jrBuildReportText(report) {
  const rows = [];
  if (String(report.notesText || "").trim()) rows.push(`Movimiento puntual: ${report.notesText}`);
  if (String(report.moneyTotal || "").trim()) rows.push(`Dinero total: ${report.moneyTotal}`);
  if (String(report.expensesText || "").trim()) rows.push(`Gastos con detalle: ${report.expensesText}`);
  if (String(report.wipQty || "").trim()) rows.push(`Focaccias en proceso: ${report.wipQty}`);
  if (String(report.packagedQty || "").trim()) rows.push(`Focaccias envasadas: ${report.packagedQty}`);
  if (String(report.streetSoldQty || "").trim()) rows.push(`Vendidas en la calle: ${report.streetSoldQty}`);
  if (String(report.deliveredToStoresText || "").trim()) rows.push(`Entregadas en consignación: ${report.deliveredToStoresText}`);
  if (String(report.storeSoldText || "").trim()) rows.push(`Vendidas en consignación: ${report.storeSoldText}`);
  if (String(report.exceptionsText || "").trim()) rows.push(`Regaladas / dañadas / marketing: ${report.exceptionsText}`);
  if (String(report.stockMainText || "").trim()) rows.push(`Stock principal: ${report.stockMainText}`);
  if (report.weeklyToppings && String(report.toppingsText || "").trim()) rows.push(`Stock toppings: ${report.toppingsText}`);
  return rows.join("\n");
}

function jrHasReportContent(report) {
  return Boolean(jrBuildReportText(report).trim());
}

function jrAccountForExpense(label) {
  const text = String(label || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
  if (/cigarr|socio|personal|retiro/.test(text)) return "1130";
  if (/harina|queso|jamon|mortad|relleno|aceite|levadura|sal|tomate|rucula|pesto|cebolla|aceituna|hongo/.test(text)) return "1210";
  if (/topping|salsa/.test(text)) return "1220";
  if (/envase|bolsa|pack|caja/.test(text)) return "1230";
  if (/alquiler|renta/.test(text)) return "6010";
  if (/luz|agua|gas|internet|servicio/.test(text)) return "6020";
  if (/sueldo|salario|pago equipo|personal/.test(text)) return "6030";
  if (/taxi|uber|transporte|nafta|combustible/.test(text)) return "6040";
  if (/limpieza|jabon|detergente/.test(text)) return "6050";
  if (/marketing|publicidad|instagram|ads/.test(text)) return "6060";
  if (/herramienta|utensilio|maquina|equipo/.test(text)) return "6070";
  return "6080";
}

function jrParseExpenseLines(text) {
  return String(text || "")
    .split(/\n|;|,(?=\s*\d|\s*[A-Za-zÀ-ÿ]+\s+\d)/)
    .map((item) => item.trim())
    .filter(Boolean)
    .map((item) => {
      const amountMatch = item.match(/(?:R\$|\$)?\s*(-?\d+(?:[.,]\d+)?)/);
      const amount = amountMatch ? Math.abs(jrParseMoney(amountMatch[1])) : 0;
      const label = item.replace(amountMatch ? amountMatch[0] : "", "").replace(/[-:]+/g, " ").trim() || item;
      return { label, amount, accountId: jrAccountForExpense(label || item), raw: item };
    })
    .filter((line) => line.amount > 0);
}

function jrBuildRuleDraft(report, metrics) {
  const originText = jrBuildReportText(report);
  const entries = [];
  const warnings = [];
  const movements = [];
  const inventory = [];
  const sales = [];
  const reportDetails = {
    moneyTotal: jrParseMoney(report.moneyTotal),
    expensesText: report.expensesText,
    wipQty: jrParseMoney(report.wipQty),
    packagedQty: jrParseMoney(report.packagedQty),
    streetSoldQty: jrParseMoney(report.streetSoldQty),
    deliveredToStoresText: report.deliveredToStoresText,
    storeSoldText: report.storeSoldText,
    exceptionsText: report.exceptionsText,
    stockMainText: report.stockMainText,
    toppingsText: report.toppingsText,
    weeklyToppings: Boolean(report.weeklyToppings),
    notesText: report.notesText,
  };

  const cashCount = jrParseMoney(report.moneyTotal);
  if (cashCount > 0) {
    const diff = jrRound(cashCount - metrics.cash);
    if (Math.abs(diff) >= 0.01) {
      entries.push(jrNormalizeEntry({
        date: report.date,
        status: "draft",
        memo: "Arqueo caja 8 AM",
        source: "santiago_8am",
        originText,
        lines: diff > 0
          ? [
              { accountId: "1010", debit: Math.abs(diff), credit: 0, note: "Caja contada" },
              { accountId: "1390", debit: 0, credit: Math.abs(diff), note: "Diferencia por revisar" },
            ]
          : [
              { accountId: "1390", debit: Math.abs(diff), credit: 0, note: "Diferencia por revisar" },
              { accountId: "1010", debit: 0, credit: Math.abs(diff), note: "Caja contada" },
            ],
        details: { report: reportDetails, movements, inventory, sales },
      }));
    }
  }

  const expenses = jrParseExpenseLines(report.expensesText);
  expenses.forEach((expense) => {
    entries.push(jrNormalizeEntry({
      date: report.date,
      status: "draft",
      memo: `Gasto: ${expense.label}`,
      source: "santiago_8am",
      originText,
      lines: [
        { accountId: expense.accountId, debit: expense.amount, credit: 0, note: expense.raw },
        { accountId: "1010", debit: 0, credit: expense.amount, note: "Salida de dinero" },
      ],
      details: {
        report: reportDetails,
        movements: [{ type: "egreso", label: expense.label, detail: expense.raw, qty: 0, amount: expense.amount }],
        inventory,
        sales,
      },
    }));
  });

  if (reportDetails.wipQty > 0) inventory.push({ area: "Focaccias en proceso", item: "Focaccia", qty: reportDetails.wipQty, unit: "un", unitCost: 0, value: 0 });
  if (reportDetails.packagedQty > 0) inventory.push({ area: "Focaccias envasadas", item: "Focaccia", qty: reportDetails.packagedQty, unit: "un", unitCost: 0, value: 0 });
  if (reportDetails.streetSoldQty > 0) sales.push({ channel: "calle", location: "", qty: reportDetails.streetSoldQty, amount: 0 });
  if (report.deliveredToStoresText) movements.push({ type: "entrega_consignacion", label: "Entregas en consignación", detail: report.deliveredToStoresText, qty: 0, amount: 0 });
  if (report.storeSoldText) movements.push({ type: "venta_consignacion", label: "Ventas en consignación", detail: report.storeSoldText, qty: 0, amount: 0 });
  if (report.exceptionsText) movements.push({ type: "salida_producto", label: "Marketing / merma", detail: report.exceptionsText, qty: 0, amount: 0 });
  if (report.stockMainText) inventory.push({ area: "Stock principal", item: report.stockMainText, qty: 0, unit: "", unitCost: 0, value: 0 });
  if (report.weeklyToppings && report.toppingsText) inventory.push({ area: "Toppings", item: report.toppingsText, qty: 0, unit: "", unitCost: 0, value: 0 });
  if (report.notesText) movements.push({ type: "reporte_parcial", label: "Movimiento puntual", detail: report.notesText, qty: 0, amount: 0 });
  if (sales.length || movements.length || inventory.length) warnings.push("Hay datos operativos sin valorizacion monetaria; quedan para revision de costo.");

  return { entries, warnings, questions: [], movements, inventory, sales };
}

function jrCsvCell(value) {
  const text = value === undefined || value === null ? "" : String(value);
  return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
}

function jrRowsCsv(columns, rows) {
  const header = columns.map((column) => column.label);
  const body = rows.map((row) => columns.map((column) => row[column.key] ?? ""));
  return [header, ...body].map((row) => row.map(jrCsvCell).join(",")).join("\n");
}

function jrDownloadCsv(filename, csv) {
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = `${filename}-${jrTodayISO()}.csv`;
  link.click();
  setTimeout(() => URL.revokeObjectURL(url), 0);
}

function jrDiaryRows(entries) {
  const rows = [];
  entries.forEach((entry) => {
    entry.lines.forEach((line) => {
      rows.push({
        date: entry.date,
        status: entry.status,
        memo: entry.memo,
        account: `${line.accountId} ${jrAccount(line.accountId).name}`,
        debit: line.debit || "",
        credit: line.credit || "",
        note: line.note || "",
      });
    });
  });
  return rows;
}

function jrExportCsv(entries) {
  jrExportRowsCsv("break-asientos", JR_DIARY_COLUMNS, jrDiaryRows(entries));
}

function jrExportRowsCsv(filename, columns, rows) {
  jrDownloadCsv(filename, jrRowsCsv(columns, rows));
}

function jrExportAllPagesCsv(pages) {
  const csvRows = [];
  pages.forEach((page, index) => {
    if (index > 0) csvRows.push([]);
    csvRows.push(["pagina", page.label || page.filename]);
    csvRows.push(page.columns.map((column) => column.label));
    if (page.rows.length) {
      page.rows.forEach((row) => csvRows.push(page.columns.map((column) => row[column.key] ?? "")));
    } else {
      csvRows.push(["Sin filas"]);
    }
  });
  const csv = csvRows.map((row) => row.map(jrCsvCell).join(",")).join("\n");
  jrDownloadCsv("break-todas-las-paginas", csv);
}

function jrPeriodEntries(entries, month) {
  return entries
    .filter((entry) => month === "all" || entry.date.startsWith(month))
    .sort((a, b) => `${b.date}${b.createdAt}`.localeCompare(`${a.date}${a.createdAt}`));
}

function jrInventoryRows(entries, month, catalog = jrDefaultCatalog()) {
  const inventoryAccounts = new Set(["1210", "1220", "1230", "1310", "1320", "1330"]);
  const rows = [];
  jrPeriodEntries(entries, month).forEach((entry) => {
    const detailRows = entry.details?.inventory || [];
    let visibleDetailRows = 0;
    detailRows.forEach((item, index) => {
      const qtyNumber = jrParseMoney(item.qty);
      const valueNumber = jrParseMoney(item.value);
      if (!qtyNumber && !valueNumber) return;
      visibleDetailRows += 1;
      const area = jrInventoryAreaLabel(jrInventoryAreaForItem(item.item, item.area || jrAccount(item.accountId).name || "Inventario", catalog), catalog);
      const itemLabel = jrInventoryItemLabel(item.item, area, catalog);
      rows.push({
        id: `${entry.id}-inv-${index}`,
        date: entry.date,
        area,
        item: itemLabel || "-",
        qty: qtyNumber ? jrFmtQty(Math.abs(qtyNumber)) : "",
        unit: item.unit || "",
        unitCost: item.unitCost ? jrFmtMoney(item.unitCost) : "",
        valueNumber,
        value: valueNumber ? jrFmtMoney(valueNumber) : "",
        status: jrStatusMeta(entry.status).label,
        source: entry.memo,
      });
    });
    if (visibleDetailRows) return;
    entry.lines
      .filter((line) => inventoryAccounts.has(line.accountId))
      .forEach((line, index) => {
        const signed = jrRound(jrParseMoney(line.debit) - jrParseMoney(line.credit));
        rows.push({
          id: `${entry.id}-inv-line-${index}`,
          date: entry.date,
          area: jrInventoryAreaLabel(jrAccount(line.accountId).name, catalog),
          item: line.note || entry.memo,
          qty: "",
          unit: "",
          unitCost: "",
          valueNumber: signed,
          value: jrFmtMoney(signed),
          status: jrStatusMeta(entry.status).label,
          source: entry.memo,
        });
      });
  });
  return rows;
}

function jrInventoryLatestItems(entries) {
  const sortedEntries = entries
    .slice()
    .sort((a, b) => jrEntrySortKey(b).localeCompare(jrEntrySortKey(a)));
  const rawItems = [];
  sortedEntries.forEach((entry) => {
      if (jrEntryHasInventoryMovement(entry)) return;
      (entry.details?.inventory || []).forEach((item, index) => {
        const area = String(item.area || "").trim();
        const name = String(item.item || "").trim();
        if (!area && !name) return;
        const qty = jrParseMoney(item.qty);
        const unitCost = jrParseMoney(item.unitCost);
        const value = jrParseMoney(item.value);
        const unit = item.unit || "";
        const looksLikePackedText = !qty && !unitCost && !value && !unit && /[,;\n]|\d/.test(name) && /stock principal|toppings/i.test(area);
        if (looksLikePackedText) return;
        rawItems.push({
          id: `${entry.id}-latest-${index}`,
          date: entry.date,
          createdAt: entry.createdAt,
          status: entry.status,
          source: entry.memo,
          area,
          item: name,
          qty,
          unit,
          unitCost,
          value,
        });
      });
    });

  const itemMeta = jrInventoryItemMeta(rawItems);
  const reportItems = [];
  sortedEntries.forEach((entry) => {
    if (jrEntryHasInventoryMovement(entry)) return;
    const report = entry.details?.report || {};
    const base = {
      date: entry.date,
      createdAt: entry.createdAt,
      status: entry.status,
      source: entry.memo,
    };
    const wipQty = jrParseMoney(report.wipQty);
    const packagedQty = jrParseMoney(report.packagedQty);
    if (wipQty > 0) reportItems.push(jrInventoryReportItem("Focaccias en proceso", "Focaccia", wipQty, "un", `${entry.id}-report-wip`, base, itemMeta));
    if (packagedQty > 0) reportItems.push(jrInventoryReportItem("Focaccias envasadas", "Focaccia", packagedQty, "un", `${entry.id}-report-packaged`, base, itemMeta));
    reportItems.push(...jrParseInventoryText(report.stockMainText, "Stock principal", `${entry.id}-report-main`, base, itemMeta));
    if (report.weeklyToppings) reportItems.push(...jrParseInventoryText(report.toppingsText, "Toppings", `${entry.id}-report-toppings`, base, itemMeta));
  });

  const items = [...reportItems, ...rawItems]
    .sort((a, b) => `${b.date}${b.createdAt}${b.id}`.localeCompare(`${a.date}${a.createdAt}${a.id}`));
  const latestByKey = new Map();
  items.forEach((item) => {
    const key = `${item.area}|${item.item}`.toLowerCase();
    if (!latestByKey.has(key)) latestByKey.set(key, item);
  });
  return [...latestByKey.values()];
}

function jrInventoryNameKey(value) {
  return String(value || "")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, " ")
    .trim();
}

function jrCatalogTerms(value) {
  return String(value || "")
    .split(/[,;\n|]/)
    .map((part) => jrInventoryNameKey(part))
    .filter(Boolean);
}

function jrCatalogArea(catalog, areaId) {
  const safeCatalog = jrNormalizeCatalog(catalog);
  return safeCatalog.areas.find((area) => area.id === areaId) || safeCatalog.areas[0];
}

function jrCatalogAreaTerms(area) {
  return [area.id, area.name, ...jrCatalogTerms(area.aliases)]
    .map(jrInventoryNameKey)
    .filter(Boolean);
}

function jrCatalogItemTerms(item) {
  return [item.id, item.name, ...jrCatalogTerms(item.aliases)]
    .map(jrInventoryNameKey)
    .filter(Boolean);
}

function jrCatalogTermMatch(text, terms) {
  const key = jrInventoryNameKey(text);
  if (!key) return false;
  return terms.some((term) => term && (key === term || key.includes(term) || term.includes(key)));
}

function jrCatalogFindArea(catalog, text) {
  const safeCatalog = jrNormalizeCatalog(catalog);
  return safeCatalog.areas.find((area) => jrCatalogTermMatch(text, jrCatalogAreaTerms(area))) || null;
}

function jrCatalogFindItem(catalog, itemName, areaText = "") {
  const safeCatalog = jrNormalizeCatalog(catalog);
  const area = jrCatalogFindArea(safeCatalog, areaText);
  const activeItems = safeCatalog.items.filter((item) => item.active !== false);
  const scoped = area ? activeItems.filter((item) => item.areaId === area.id) : activeItems;
  return scoped.find((item) => jrCatalogTermMatch(itemName, jrCatalogItemTerms(item)))
    || activeItems.find((item) => jrCatalogTermMatch(itemName, jrCatalogItemTerms(item)))
    || null;
}

function jrCatalogInventoryLabels(itemName, fallbackArea, catalog) {
  const safeCatalog = jrNormalizeCatalog(catalog);
  const item = jrCatalogFindItem(safeCatalog, itemName, fallbackArea);
  const area = item
    ? jrCatalogArea(safeCatalog, item.areaId)
    : (jrCatalogFindArea(safeCatalog, `${fallbackArea} ${itemName}`) || jrCatalogFindArea(safeCatalog, fallbackArea));
  return {
    areaName: area?.name || fallbackArea || "Inventario",
    itemName: item?.name || String(itemName || "").trim(),
    unit: item?.unit || "",
    unitCost: item?.unitCost || 0,
  };
}

function jrCatalogAreaMatcher(catalog, areaId, fallbackPattern) {
  const safeCatalog = jrNormalizeCatalog(catalog);
  const area = jrCatalogArea(safeCatalog, areaId);
  const terms = [
    ...jrCatalogAreaTerms(area),
    ...safeCatalog.items
      .filter((item) => item.areaId === areaId && item.active !== false)
      .flatMap(jrCatalogItemTerms),
  ].filter(Boolean);
  if (!terms.length) return fallbackPattern;
  const escaped = terms.map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
  return new RegExp(escaped.join("|"), "i");
}

function jrInventoryItemMeta(items) {
  const meta = new Map();
  items.forEach((item) => {
    const key = jrInventoryNameKey(item.item);
    if (!key) return;
    const current = meta.get(key) || {};
    if (!current.unitCost && item.unitCost > 0) current.unitCost = item.unitCost;
    if (!current.unit && item.unit) current.unit = item.unit;
    meta.set(key, current);
  });
  return meta;
}

function jrInventoryReportItem(area, item, qty, unit, id, base, itemMeta) {
  const meta = itemMeta.get(jrInventoryNameKey(item)) || {};
  const unitCost = meta.unitCost || 0;
  return {
    id,
    date: base.date,
    createdAt: base.createdAt,
    status: base.status,
    source: base.source,
    area,
    item,
    qty,
    unit: unit || meta.unit || "",
    unitCost,
    value: qty && unitCost ? jrRound(qty * unitCost) : 0,
  };
}

function jrInventoryAreaForItem(item, fallbackArea, catalog = jrDefaultCatalog()) {
  const catalogLabels = jrCatalogInventoryLabels(item, fallbackArea, catalog);
  if (catalogLabels.areaName && catalogLabels.areaName !== "Inventario") return catalogLabels.areaName;
  const text = jrInventoryNameKey(`${fallbackArea} ${item}`);
  if (/envase|bolsa|packaging|pack|caja/.test(text)) return "Packaging";
  if (/topping|tomate|rucula|pesto|cebolla|aceituna|hongo|salsa/.test(text)) return "Toppings";
  if (/proceso/.test(text)) return "Focaccias en proceso";
  if (/envasad|terminad|lista/.test(text)) return "Focaccias envasadas";
  if (/consign|local/.test(text)) return "Focaccias en consignación";
  if (/stock principal|insumo|harina|queso|aceite|levadura|sal/.test(text)) return "Stock principal";
  return fallbackArea;
}

function jrInventoryAreaLabel(area, catalog = jrDefaultCatalog()) {
  const configured = jrCatalogFindArea(catalog, area);
  if (configured) return configured.name;
  const text = jrInventoryNameKey(area);
  if (/stock principal|insumo|principal/.test(text)) return "Insumos principales";
  if (/topping/.test(text)) return "Toppings";
  if (/packaging|envase|bolsa|caja/.test(text)) return "Packaging";
  if (/proceso/.test(text)) return "Focaccias en proceso";
  if (/envasad|terminad|lista/.test(text)) return "Focaccias envasadas";
  if (/consign|local/.test(text)) return "Focaccias en consignación";
  return area || "Inventario";
}

function jrInventoryItemLabel(item, area, catalog = jrDefaultCatalog()) {
  const labels = jrCatalogInventoryLabels(item, area, catalog);
  return labels.itemName || item || "";
}

function jrParseInventoryText(text, area, idPrefix, base, itemMeta, catalog = jrDefaultCatalog()) {
  return String(text || "")
    .split(/\n|;|,/)
    .map((part) => part.trim())
    .filter(Boolean)
    .map((part, index) => {
      const match = part.match(/^(.+?)\s+(-?\d+(?:[.,]\d+)?)\s*([A-Za-zÀ-ÿ]+|un|und|uds?)?(?:\s*(?:x|@|a|precio|costo)\s*(?:R\$|\$)?\s*(-?\d+(?:[.,]\d+)?))?$/i);
      if (!match) {
        return {
          id: `${idPrefix}-${index}`,
          date: base.date,
          createdAt: base.createdAt,
          status: base.status,
          source: base.source,
          area: jrInventoryAreaForItem(part, area, catalog),
          item: part,
          qty: 0,
          unit: "",
          unitCost: 0,
          value: 0,
        };
      }
      const item = match[1].trim();
      const qty = jrParseMoney(match[2]);
      const labels = jrCatalogInventoryLabels(item, area, catalog);
      const itemLabel = labels.itemName || item;
      const meta = itemMeta.get(jrInventoryNameKey(itemLabel)) || itemMeta.get(jrInventoryNameKey(item)) || {};
      const unitCost = jrParseMoney(match[4]) || meta.unitCost || labels.unitCost || 0;
      return {
        id: `${idPrefix}-${index}`,
        date: base.date,
        createdAt: base.createdAt,
        status: base.status,
        source: base.source,
        area: jrInventoryAreaForItem(item, area, catalog),
        item: itemLabel,
        qty,
        unit: match[3] || meta.unit || labels.unit || "",
        unitCost,
        value: qty && unitCost ? jrRound(qty * unitCost) : 0,
      };
    });
}

function jrInventoryNormalizeItem(item, base, itemMeta, catalog = jrDefaultCatalog()) {
  const rawArea = String(item.area || "").trim();
  const rawName = String(item.item || "").trim();
  const labels = jrCatalogInventoryLabels(rawName, rawArea, catalog);
  const area = jrInventoryAreaForItem(rawName, rawArea, catalog);
  const name = labels.itemName || rawName;
  const qty = jrParseMoney(item.qty);
  const meta = itemMeta?.get(jrInventoryNameKey(name)) || {};
  const unit = item.unit || meta.unit || labels.unit || "";
  const unitCost = jrRound(jrParseMoney(item.unitCost) || meta.unitCost || labels.unitCost || 0);
  const value = jrRound(jrParseMoney(item.value) || (qty && unitCost ? qty * unitCost : 0));
  return {
    id: item.id || `${base.id}-current`,
    date: base.date,
    createdAt: base.createdAt,
    status: base.status,
    source: base.source,
    area,
    item: name,
    qty,
    unit,
    unitCost,
    value,
  };
}

function jrInventoryApplyCurrentItem(map, item, mode) {
  if (!item.item && !item.area) return;
  const key = `${jrInventoryNameKey(item.area)}|${jrInventoryNameKey(item.item)}`;
  if (!key.includes("|") || !jrInventoryNameKey(item.item)) return;
  const current = map.get(key);
  if (mode === "snapshot" || !current) {
    map.set(key, {
      ...item,
      id: item.id,
      qty: jrRound(item.qty),
      value: jrRound(item.value),
      unitCost: jrRound(item.unitCost || (item.qty && item.value ? item.value / item.qty : 0)),
    });
    return;
  }
  const qty = jrRound((current.qty || 0) + (item.qty || 0));
  const value = jrRound((current.value || 0) + (item.value || 0));
  map.set(key, {
    ...current,
    id: `${current.id}+${item.id}`,
    date: item.date || current.date,
    createdAt: item.createdAt || current.createdAt,
    status: item.status || current.status,
    source: current.source === item.source ? current.source : "Movimientos acumulados",
    qty,
    unit: current.unit || item.unit,
    unitCost: qty && value ? jrRound(value / qty) : (item.unitCost || current.unitCost || 0),
    value,
  });
}

function jrInventoryCurrentItems(entries, catalog = jrDefaultCatalog()) {
  const sortedEntries = entries
    .slice()
    .sort((a, b) => jrEntrySortKey(a).localeCompare(jrEntrySortKey(b)));
  const metaItems = [];
  sortedEntries.forEach((entry) => {
    (entry.details?.inventory || []).forEach((item, index) => {
      metaItems.push(jrInventoryNormalizeItem({ ...item, id: `${entry.id}-meta-${index}` }, {
        id: entry.id,
        date: entry.date,
        createdAt: entry.createdAt,
        status: entry.status,
        source: entry.memo,
      }, null, catalog));
    });
  });
  const itemMeta = jrInventoryItemMeta(metaItems);
  const currentByKey = new Map();

  sortedEntries.forEach((entry) => {
    const base = {
      id: entry.id,
      date: entry.date,
      createdAt: entry.createdAt,
      status: entry.status,
      source: entry.memo,
    };
    const report = entry.details?.report || {};
    const detailRows = entry.details?.inventory || [];
    const hasStructuredDetails = detailRows.some((item) => {
      const area = String(item.area || "").trim();
      const name = String(item.item || "").trim();
      return (area || name) && (jrParseMoney(item.qty) || jrParseMoney(item.value) || jrParseMoney(item.unitCost) || item.unit);
    });
    const snapshotItems = hasStructuredDetails ? [] : [
      ...jrParseInventoryText(report.stockMainText, "Stock principal", `${entry.id}-report-main`, base, itemMeta, catalog),
      ...(report.weeklyToppings ? jrParseInventoryText(report.toppingsText, "Toppings", `${entry.id}-report-toppings`, base, itemMeta, catalog) : []),
    ];
    snapshotItems.forEach((item) => {
      jrInventoryApplyCurrentItem(currentByKey, jrInventoryNormalizeItem(item, base, itemMeta, catalog), "snapshot");
    });

    const detailsMode = (hasStructuredDetails && jrEntryIsSnapshot(entry)) || snapshotItems.length || (jrEntryIsSnapshot(entry) && !jrEntryHasInventoryMovement(entry)) ? "snapshot" : "movement";
    detailRows.forEach((item, index) => {
      const area = String(item.area || "").trim();
      const name = String(item.item || "").trim();
      const qty = jrParseMoney(item.qty);
      const unitCost = jrParseMoney(item.unitCost);
      const value = jrParseMoney(item.value);
      const unit = item.unit || "";
      const looksLikePackedText = !qty && !unitCost && !value && !unit && /[,;\n]|\d/.test(name) && /stock principal|toppings/i.test(area);
      if (looksLikePackedText) return;
      jrInventoryApplyCurrentItem(currentByKey, jrInventoryNormalizeItem({ ...item, id: `${entry.id}-current-${index}` }, base, itemMeta, catalog), detailsMode);
    });
  });

  return [...currentByKey.values()];
}

function jrInventoryBucketItems(latestItems, matcher) {
  return latestItems
    .filter((item) => matcher.test(jrInventoryNameKey(`${item.area} ${item.item}`)))
    .map((item) => {
      const qty = jrRound(item.qty);
      const unitCost = jrRound(item.unitCost || (qty && item.value ? item.value / qty : 0));
      const value = jrRound(item.value || (qty && unitCost ? qty * unitCost : 0));
      return {
        id: item.id,
        name: item.item || item.area || "-",
        qtyNumber: qty,
        qty: qty ? jrFmtQty(qty) : "-",
        unit: item.unit || "-",
        unitCostNumber: unitCost,
        unitCost: unitCost ? jrFmtMoney(unitCost) : "-",
        valueNumber: value,
        value: value ? jrFmtMoney(value) : "-",
        source: item.source || "-",
        status: jrStatusMeta(item.status).label,
        date: item.date || "-",
      };
    })
    .sort((a, b) => a.name.localeCompare(b.name, "es"));
}

function jrInventoryGroupSummary(children, fallbackQty, fallbackDate) {
  if (!children.length) return { qty: fallbackQty || "Sin conteo", detail: "Sin detalle cargado", date: fallbackDate || "-" };
  const latestDate = children.map((item) => item.date).sort().reverse()[0] || fallbackDate || "-";
  return {
    qty: children.length === 1 && children[0].qty !== "-" ? `${children[0].qty} ${children[0].unit === "-" ? "" : children[0].unit}`.trim() : `${children.length} insumos`,
    detail: `${children.length} fila${children.length === 1 ? "" : "s"} con detalle`,
    date: latestDate,
  };
}

function jrOperationalQtyFromEntry(entry, matcher, accountId) {
  const movements = entry.details?.movements || [];
  const rawMovementQty = movements.reduce((sum, item) => {
    const text = `${item.type || ""} ${item.label || ""} ${item.detail || ""}`;
    if (/cmv|costo de venta|costo venta/i.test(text)) return sum;
    if (/consumo|insumo|harina|queso|topping|envase/i.test(`${item.type || ""} ${item.label || ""}`) && !/producci[oó]n|produc/i.test(`${item.type || ""}`)) return sum;
    if (!matcher.test(text)) return sum;
    return sum + Math.max(0, jrParseMoney(item.qty));
  }, 0);
  const rawQty = rawMovementQty > 0 ? rawMovementQty : (entry.details?.inventory || []).reduce((sum, item) => {
    const text = `${item.area || ""} ${item.item || ""}`;
    if (!matcher.test(text)) return sum;
    return sum + Math.max(0, jrParseMoney(item.qty));
  }, 0);
  if (rawQty <= 0) return 0;
  const accountValue = jrNetLineValue(entry, accountId);
  const direction = accountValue < -0.01 ? -1 : 1;
  return jrRound(rawQty * direction);
}

function jrNetLineValue(entry, accountId) {
  return (entry.lines || []).reduce((sum, line) => {
    if (line.accountId !== accountId) return sum;
    return sum + jrParseMoney(line.debit) - jrParseMoney(line.credit);
  }, 0);
}

function jrOperationalInventoryPosition(entries, metrics, kind) {
  const snapshotEntry = jrLatestSnapshotEntry(entries);
  const afterKey = snapshotEntry ? jrEntrySortKey(snapshotEntry) : "";
  const accountId = kind === "wip" ? "1310" : "1320";
  const label = kind === "wip" ? "Focaccia en proceso" : "Focaccia envasada";
  const matcher = kind === "wip" ? /producci[oó]n|produc|proceso/i : /terminad|envasad/i;
  const snapshotDetail = (snapshotEntry?.details?.inventory || [])
    .find((item) => matcher.test(jrInventoryNameKey(`${item.area || ""} ${item.item || ""}`)));
  const snapshotQty = jrParseMoney(snapshotDetail?.qty);
  const baseQty = snapshotQty || (kind === "wip" ? jrParseMoney(metrics.snapshot.wipQty) : jrParseMoney(metrics.snapshot.packagedQty));
  let incrementQty = 0;
  let latestDate = metrics.snapshotDate || "";

  entries
    .filter((entry) => jrEntryHasInventoryMovement(entry))
    .filter((entry) => !afterKey || jrEntrySortKey(entry) > afterKey)
    .sort((a, b) => jrEntrySortKey(a).localeCompare(jrEntrySortKey(b)))
    .forEach((entry) => {
      const qty = jrOperationalQtyFromEntry(entry, matcher, accountId);
      if (Math.abs(qty) <= 0) return;
      incrementQty += qty;
      latestDate = entry.date || latestDate;
    });

  const qty = jrRound(baseQty + incrementQty);
  const accountValue = jrRound(metrics.accountBalances[accountId] || 0);
  const snapshotValue = jrRound(jrParseMoney(snapshotDetail?.value));
  const snapshotUnitCost = jrRound(jrParseMoney(snapshotDetail?.unitCost) || (snapshotQty > 0 && snapshotValue > 0 ? snapshotValue / snapshotQty : 0));
  const unitCost = snapshotUnitCost || (qty > 0 && accountValue > 0 ? jrRound(accountValue / qty) : 0);
  const value = snapshotDetail && unitCost > 0 ? jrRound(qty * unitCost) : accountValue;
  return {
    qty,
    value: accountValue,
    unitCost,
    date: latestDate,
    detail: incrementQty > 0 ? `conteo + ${jrFmtQty(incrementQty)} producidas` : incrementQty < 0 ? `conteo - ${jrFmtQty(Math.abs(incrementQty))} salidas` : "último conteo operativo",
    child: qty > 0 ? {
      id: `current-${kind}`,
      name: label,
      qtyNumber: qty,
      qty: jrFmtQty(qty),
      unit: "un",
      unitCostNumber: unitCost,
      unitCost: unitCost ? jrFmtMoney(unitCost) : "-",
      valueNumber: value,
      value: value ? jrFmtMoney(value) : "-",
      source: incrementQty > 0 ? "Conteo + producción incremental" : "Último conteo operativo",
      status: "Operativo",
      date: latestDate || "-",
    } : null,
  };
}

function jrInventoryPositionRows(entries, metrics, catalog = jrDefaultCatalog()) {
  const latestItems = jrInventoryCurrentItems(entries, catalog);
  const wipPosition = jrOperationalInventoryPosition(entries, metrics, "wip");
  const finishedPosition = jrOperationalInventoryPosition(entries, metrics, "finished");
  const safeCatalog = jrNormalizeCatalog(catalog);
  const areaName = (id, fallback) => jrCatalogArea(safeCatalog, id)?.name || fallback;
  const buckets = [
    {
      id: "pos-main",
      area: areaName("main", "Insumos principales"),
      account: "1210",
      value: metrics.inventoryMain,
      matcher: jrCatalogAreaMatcher(safeCatalog, "main", /stock principal|insumo|harina|queso|aceite|levadura|sal/i),
    },
    {
      id: "pos-toppings",
      area: areaName("toppings", "Toppings"),
      account: "1220",
      value: metrics.inventoryToppings,
      matcher: jrCatalogAreaMatcher(safeCatalog, "toppings", /topping|tomate|rucula|rúcula|pesto|cebolla|aceituna|hongo|salsa/i),
    },
    {
      id: "pos-packaging",
      area: areaName("packaging", "Packaging"),
      account: "1230",
      value: metrics.inventoryPackaging,
      matcher: jrCatalogAreaMatcher(safeCatalog, "packaging", /packaging|envase|bolsa|caja/i),
    },
    {
      id: "pos-wip",
      area: areaName("wip", "Focaccias en proceso"),
      account: "1310",
      value: metrics.inventoryWip,
      matcher: jrCatalogAreaMatcher(safeCatalog, "wip", /proceso/i),
      fallbackQty: metrics.snapshot.wipQty > 0 ? `${jrFmtQty(metrics.snapshot.wipQty)} un` : "",
      fallbackDate: metrics.snapshotDate,
      forcedChildren: wipPosition.child ? [wipPosition.child] : [],
      forcedSummary: {
        qty: `${jrFmtQty(wipPosition.qty)} un`,
        detail: wipPosition.qty > 0 ? wipPosition.detail : "sin unidades actuales",
        date: wipPosition.date,
      },
    },
    {
      id: "pos-finished",
      area: areaName("finished", "Focaccias envasadas"),
      account: "1320",
      value: metrics.inventoryFinished,
      matcher: jrCatalogAreaMatcher(safeCatalog, "finished", /envasad|terminad/i),
      fallbackQty: metrics.snapshot.packagedQty > 0 ? `${jrFmtQty(metrics.snapshot.packagedQty)} un` : "",
      fallbackDate: metrics.snapshotDate,
      forcedChildren: finishedPosition.child ? [finishedPosition.child] : [],
      forcedSummary: {
        qty: `${jrFmtQty(finishedPosition.qty)} un`,
        detail: finishedPosition.qty > 0 ? finishedPosition.detail : "sin unidades actuales",
        date: finishedPosition.date,
      },
    },
    {
      id: "pos-stores",
      area: areaName("consignment", "Focaccias en consignación"),
      account: "1330",
      value: metrics.inventoryStores,
      matcher: jrCatalogAreaMatcher(safeCatalog, "consignment", /consign|local/i),
    },
  ];
  safeCatalog.areas
    .filter((area) => !["main", "toppings", "packaging", "wip", "finished", "consignment"].includes(area.id))
    .forEach((area) => {
      buckets.push({
        id: `pos-custom-${area.id}`,
        area: area.name,
        account: area.accountId,
        value: 0,
        matcher: jrCatalogAreaMatcher(safeCatalog, area.id, new RegExp(jrInventoryNameKey(area.name), "i")),
      });
    });

  return buckets.map((row) => {
    const children = row.forcedChildren || jrInventoryBucketItems(latestItems, row.matcher);
    const summary = row.forcedSummary || jrInventoryGroupSummary(children, row.fallbackQty, row.fallbackDate);
    const physicalValue = jrRound(children.reduce((sum, item) => sum + item.valueNumber, 0));
    const difference = jrRound(physicalValue - row.value);
    return {
      ...row,
      children,
      qty: summary.qty,
      detail: summary.detail,
      date: summary.date,
      physicalValue,
      physicalValueText: children.length ? jrFmtMoney(physicalValue) : "-",
      difference,
      differenceText: children.length ? jrFmtMoney(difference) : "-",
      differenceTone: Math.abs(difference) < 0.01 ? "ok" : difference > 0 ? "over" : "under",
      accountLabel: `${row.account} · ${jrAccount(row.account).name}`,
      valueText: jrFmtMoney(row.value),
    };
  });
}

function jrNormalizeUnit(value) {
  return String(value || "")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .trim();
}

function jrConvertQty(value, unit, targetUnit) {
  const qty = jrParseMoney(value);
  const from = jrNormalizeUnit(unit);
  const to = jrNormalizeUnit(targetUnit);
  if (!qty || !to || from === to || !from) return qty;
  if (to === "kg" && ["g", "gr", "gramo", "gramos"].includes(from)) return qty / 1000;
  if (to === "g" && ["kg", "kilo", "kilos"].includes(from)) return qty * 1000;
  if (to === "l" && ["ml", "mililitro", "mililitros"].includes(from)) return qty / 1000;
  if (to === "ml" && ["l", "lt", "litro", "litros"].includes(from)) return qty * 1000;
  if (to === "m" && ["cm", "centimetro", "centimetros"].includes(from)) return qty / 100;
  if (to === "cm" && ["m", "metro", "metros"].includes(from)) return qty * 100;
  return qty;
}

function jrFmtStockQty(value, unit, precise = false) {
  return `${precise ? jrFmtQtyPrecise(value) : jrFmtQty(value)} ${unit}`.trim();
}

function jrProductionCapacity(entries, catalog = jrDefaultCatalog(), dailyTarget = JR_FOCACCIA_DAILY_LIMIT) {
  const safeDailyTarget = jrClampProductionTarget(dailyTarget);
  const currentItems = jrInventoryCurrentItems(entries, catalog);
  const components = JR_FOCACCIA_COMPONENTS.map((component) => {
    const matchedItems = currentItems.filter((item) => component.matcher.test(jrInventoryNameKey(`${item.area} ${item.item}`)));
    const stockQty = jrRound(matchedItems.reduce((sum, item) => sum + jrConvertQty(item.qty, item.unit, component.stockUnit), 0));
    const possibleRaw = component.recipeQty > 0 ? (stockQty / component.recipeQty) * component.recipeOutput : 0;
    const possibleFocaccias = Math.max(0, Math.floor(possibleRaw + 0.000001));
    const dailyNeed = (safeDailyTarget / component.recipeOutput) * component.recipeQty;
    const shortageForDay = Math.max(0, dailyNeed - stockQty);
    return {
      ...component,
      stockQty,
      stockText: jrFmtStockQty(stockQty, component.stockUnit),
      recipeText: `${jrFmtQtyPrecise(component.recipeQty)} ${component.recipeUnit} / ${jrFmtQty(component.recipeOutput)} focaccia${component.recipeOutput === 1 ? "" : "s"}`,
      possibleFocaccias,
      possibleText: `${jrFmtQty(possibleFocaccias)} un`,
      dailyNeed,
      dailyNeedText: jrFmtStockQty(dailyNeed, component.stockUnit, true),
      shortageForDay,
      shortageText: shortageForDay > 0 ? jrFmtStockQty(shortageForDay, component.stockUnit, true) : "OK",
      daysText: possibleFocaccias > 0 ? `${jrFmtQty(jrRound(possibleFocaccias / safeDailyTarget))} d` : "0 d",
      source: matchedItems.map((item) => item.item).filter(Boolean).join(", ") || "-",
    };
  });
  const doughComponents = components.filter((component) => component.group === "Masa");
  const packagingComponents = components.filter((component) => component.group === "Packaging");
  const minBy = (rows) => rows.reduce((min, row) => Math.min(min, row.possibleFocaccias), Number.POSITIVE_INFINITY);
  const doughFocaccias = minBy(doughComponents);
  const packagingFocaccias = minBy(packagingComponents);
  const maxFocaccias = Math.min(doughFocaccias, packagingFocaccias);
  const bottlenecks = components.filter((component) => component.possibleFocaccias === maxFocaccias).map((component) => component.label);
  return {
    components: components.map((component) => ({ ...component, bottleneck: component.possibleFocaccias === maxFocaccias })),
    maxFocaccias,
    doughFocaccias,
    doughs: Math.floor(doughFocaccias / 8),
    packagingFocaccias,
    dailyTarget: safeDailyTarget,
    dailyLimit: JR_FOCACCIA_DAILY_LIMIT,
    dailyCoverage: maxFocaccias / safeDailyTarget,
    missingForDay: Math.max(0, safeDailyTarget - maxFocaccias),
    bottleneckText: bottlenecks.join(", ") || "-",
  };
}

function jrProductionCapacityRows(capacity) {
  return (capacity?.components || []).map((component) => ({
    id: component.id,
    component: component.label,
    group: component.group,
    stock: component.stockText,
    recipe: component.recipeText,
    possible: component.possibleText,
    dailyNeed: component.dailyNeedText,
    shortage: component.shortageText,
    days: component.daysText,
  }));
}

function jrInventoryCostHints(entries, catalog = jrDefaultCatalog()) {
  const hints = new Map();
  jrInventoryCurrentItems(entries, catalog).forEach((item) => {
    const qty = jrRound(item.qty);
    const unitCost = jrRound(item.unitCost || (qty && item.value ? item.value / qty : 0));
    if (unitCost <= 0) return;
    const key = jrInventoryNameKey(item.item);
    if (!key || hints.has(key)) return;
    hints.set(key, {
      item: item.item,
      area: jrInventoryAreaForItem(item.item, item.area, catalog),
      unit: item.unit || "",
      unitCost,
      date: item.date || "",
    });
  });
  return [...hints.values()].slice(0, 80);
}

function jrSalesRows(entries, month) {
  const salesAccounts = new Set(["4010", "4020", "4030", "4090"]);
  const rows = [];
  jrPeriodEntries(entries, month).forEach((entry) => {
    const detailRows = entry.details?.sales || [];
    const salesLines = entry.lines.filter((line) => salesAccounts.has(line.accountId));
    const salesLineTotal = jrRound(salesLines.reduce((sum, line) => sum + jrParseMoney(line.credit) - jrParseMoney(line.debit), 0));
    const detailAmountTotal = jrRound(detailRows.reduce((sum, sale) => sum + jrParseMoney(sale.amount), 0));
    detailRows.forEach((sale, index) => {
      const amount = jrParseMoney(sale.amount) || (detailRows.length === 1 && detailAmountTotal <= 0 ? salesLineTotal : 0);
      rows.push({
        id: `${entry.id}-sale-${index}`,
        date: entry.date,
        channel: jrSalesChannelLabel(sale.channel),
        location: jrSalesLocationLabel(sale),
        qty: sale.qty ? jrFmtQty(sale.qty) : "",
        amountNumber: amount,
        amount: amount ? jrFmtMoney(amount) : "",
        status: jrStatusMeta(entry.status).label,
        source: entry.memo,
      });
    });
    if (detailRows.length) return;
    salesLines.forEach((line, index) => {
      const amount = jrRound(jrParseMoney(line.credit) - jrParseMoney(line.debit));
      rows.push({
        id: `${entry.id}-sale-line-${index}`,
        date: entry.date,
        channel: jrSalesChannelLabel("", line.accountId),
        location: jrSalesLocationLabel({ channel: jrSalesChannelLabel("", line.accountId), location: line.note }),
        qty: "",
        amountNumber: amount,
        amount: jrFmtMoney(amount),
        status: jrStatusMeta(entry.status).label,
        source: entry.memo,
      });
    });
  });
  return rows;
}

function jrSalesChannelKey(channel, accountId = "") {
  const text = jrInventoryNameKey(`${accountId} ${channel}`);
  if (accountId === "4010" || /\bcalle\b|street/.test(text)) return "street";
  if (accountId === "4020" || /consign|local|loja|tienda/.test(text)) return "consignment";
  if (accountId === "4030" || /take ?away|takeaway|retir|para llevar/.test(text)) return "takeaway";
  return "other";
}

function jrSalesChannelLabel(channel, accountId = "") {
  const key = jrSalesChannelKey(channel, accountId);
  if (key === "street") return "Venta a la calle";
  if (key === "consignment") return "Venta en consignación";
  if (key === "takeaway") return "Venta take-away";
  return String(channel || "").trim() || "Otros ingresos";
}

function jrSalesLocationLabel(sale) {
  const channel = jrSalesChannelLabel(sale.channel || "");
  const location = String(sale.location || "").trim();
  if (!location) return "";
  const channelKey = jrInventoryNameKey(channel);
  const locationKey = jrInventoryNameKey(location);
  if (locationKey === channelKey) return "";
  if (jrSalesChannelKey(channel) !== "consignment") return "";
  if (/^(venta )?(en )?(consignacion|local|locales)$/.test(locationKey)) return "";
  return location;
}

function jrExpenseRows(entries, month) {
  const purchaseInventoryAccounts = new Set(["1210", "1220", "1230"]);
  const purchaseAssetAccounts = new Set(["1410", "1420"]);
  const isOpeningLine = (line) => /inventario inicial|saldo inicial|apertura inicial/i.test(line.note || "");
  const rows = [];
  jrPeriodEntries(entries, month).forEach((entry) => {
    const cashOut = entry.lines.reduce((sum, line) => line.accountId === "1010" ? sum + jrParseMoney(line.credit) : sum, 0);
    if (cashOut <= 0) return;
    entry.lines
      .filter((line) => line.accountId !== "1010" && jrParseMoney(line.debit) > 0)
      .filter((line) => {
        if (isOpeningLine(line)) return false;
        const account = jrAccount(line.accountId);
        if (account.group === "Gastos") return true;
        if (line.accountId === "1130") return true;
        return purchaseInventoryAccounts.has(line.accountId) || purchaseAssetAccounts.has(line.accountId);
      })
      .forEach((line, index) => {
        const account = jrAccount(line.accountId);
        rows.push({
          id: `${entry.id}-expense-${index}`,
          date: entry.date,
          type: line.accountId === "1130" ? "Socio" : purchaseInventoryAccounts.has(line.accountId) ? "Compra inventario" : purchaseAssetAccounts.has(line.accountId) ? "Inversión activo" : account.group,
          account: `${line.accountId} · ${account.name}`,
          detail: line.note || entry.memo,
          amountNumber: jrParseMoney(line.debit),
          amount: jrFmtMoney(line.debit),
          status: jrStatusMeta(entry.status).label,
          source: entry.memo,
        });
      });
  });
  return rows;
}

function jrBalanceRows(metrics) {
  const rows = JR_ACCOUNTS
    .filter((account) => ["Activo", "Pasivo", "Patrimonio"].includes(account.group))
    .map((account) => {
      const value = jrRound(metrics.accountBalances[account.id] || 0);
      return {
        id: `balance-${account.id}`,
        group: account.group,
        account: `${account.id} · ${account.name}`,
        valueNumber: value,
        value: jrFmtMoney(value),
      };
    })
    .filter((row) => Math.abs(row.valueNumber) >= 0.01);

  if (Math.abs(metrics.currentResult) >= 0.01) {
    rows.push({
      id: "balance-current-result",
      group: "Patrimonio",
      account: "Resultado del ejercicio",
      valueNumber: metrics.currentResult,
      value: jrFmtMoney(metrics.currentResult),
    });
  }
  return rows;
}

function jrResultRows(metrics) {
  const resultGroups = new Set(["Ingresos", "Costos", "Gastos"]);
  const rows = JR_ACCOUNTS
    .filter((account) => resultGroups.has(account.group))
    .map((account) => {
      const value = jrRound(metrics.periodBalances[account.id] || 0);
      return {
        id: `result-${account.id}`,
        group: account.group,
        account: `${account.id} · ${account.name}`,
        valueNumber: value,
        value: jrFmtMoney(value),
      };
    })
    .filter((row) => Math.abs(row.valueNumber) >= 0.01);
  rows.push({
    id: "result-net",
    group: "Resultado",
    account: "Resultado neto",
    valueNumber: metrics.result,
    value: jrFmtMoney(metrics.result),
  });
  return rows;
}

function jrCashflowRows(entries, month) {
  const rows = [];
  jrPeriodEntries(entries, month)
    .filter((entry) => entry.status === "posted")
    .forEach((entry) => {
      entry.lines
        .filter((line) => line.accountId === "1010")
        .forEach((line, index) => {
          const signed = jrRound(jrParseMoney(line.debit) - jrParseMoney(line.credit));
          if (Math.abs(signed) < 0.01) return;
          rows.push({
            id: `${entry.id}-cash-${index}`,
            date: entry.date,
            direction: signed >= 0 ? "Entrada" : "Salida",
            detail: line.note || entry.memo,
            source: entry.memo,
            amountNumber: signed,
            amount: jrFmtMoney(Math.abs(signed)),
          });
        });
    });
  return rows;
}

function jrStatementChild(row, labelKey = "account", valueKey = "value") {
  return {
    id: row.id,
    label: row[labelKey] || row.account || row.detail || "-",
    meta: [row.date, row.source].filter(Boolean).join(" · "),
    valueNumber: jrRound(row.valueNumber ?? row.amountNumber ?? 0),
    value: row[valueKey] || row.amount || jrFmtMoney(row.valueNumber ?? row.amountNumber ?? 0),
  };
}

function jrBalanceStatementGroups(rows, metrics) {
  return [
    {
      id: "statement-assets",
      label: "Activos",
      caption: "Caja, cuentas por cobrar e inventarios",
      valueNumber: metrics.assets,
      value: jrFmtMoney(metrics.assets),
      children: rows.filter((row) => row.group === "Activo").map((row) => jrStatementChild(row)),
    },
    {
      id: "statement-liabilities",
      label: "Pasivos",
      caption: "Deudas y obligaciones",
      valueNumber: metrics.liabilities,
      value: jrFmtMoney(metrics.liabilities),
      children: rows.filter((row) => row.group === "Pasivo").map((row) => jrStatementChild(row)),
    },
    {
      id: "statement-equity",
      label: "Patrimonio + resultado",
      caption: "Capital, aportes y resultado acumulado",
      valueNumber: metrics.equity + metrics.currentResult,
      value: jrFmtMoney(metrics.equity + metrics.currentResult),
      children: rows.filter((row) => row.group === "Patrimonio").map((row) => jrStatementChild(row)),
    },
    {
      id: "statement-check",
      label: "Chequeo de balance",
      caption: "Activos - pasivos - patrimonio - resultado",
      valueNumber: metrics.balanceCheck,
      value: jrFmtMoney(metrics.balanceCheck),
      children: [],
      locked: true,
    },
  ];
}

function jrResultStatementGroups(rows, metrics) {
  return [
    {
      id: "statement-income",
      label: "Ingresos",
      caption: "Ventas y otros ingresos del periodo",
      valueNumber: metrics.income,
      value: jrFmtMoney(metrics.income),
      children: rows.filter((row) => row.group === "Ingresos").map((row) => jrStatementChild(row)),
    },
    {
      id: "statement-costs",
      label: "CMV y costos",
      caption: "Costo de mercadería, marketing de producto y merma",
      valueNumber: metrics.costs,
      value: jrFmtMoney(metrics.costs),
      children: rows.filter((row) => row.group === "Costos").map((row) => jrStatementChild(row)),
    },
    {
      id: "statement-expenses",
      label: "Gastos operativos",
      caption: "Alquiler, servicios, equipo y gastos varios",
      valueNumber: metrics.expenses,
      value: jrFmtMoney(metrics.expenses),
      children: rows.filter((row) => row.group === "Gastos").map((row) => jrStatementChild(row)),
    },
    {
      id: "statement-net-result",
      label: "Resultado neto",
      caption: "Ingresos - costos - gastos",
      valueNumber: metrics.result,
      value: jrFmtMoney(metrics.result),
      children: [],
      locked: true,
    },
  ];
}

function jrCashflowStatementGroups(rows) {
  const inflows = rows.filter((row) => row.amountNumber > 0);
  const outflows = rows.filter((row) => row.amountNumber < 0);
  const inflowTotal = inflows.reduce((sum, row) => sum + row.amountNumber, 0);
  const outflowTotal = Math.abs(outflows.reduce((sum, row) => sum + row.amountNumber, 0));
  const net = rows.reduce((sum, row) => sum + row.amountNumber, 0);
  const child = (row) => ({
    id: row.id,
    label: row.detail || row.source || "-",
    meta: [row.date, row.source].filter(Boolean).join(" · "),
    valueNumber: row.amountNumber,
    value: jrFmtMoney(Math.abs(row.amountNumber)),
  });
  return [
    {
      id: "statement-cash-in",
      label: "Entradas de caja",
      caption: "Cobros, aportes y aumentos reales de caja",
      valueNumber: inflowTotal,
      value: jrFmtMoney(inflowTotal),
      children: inflows.map(child),
    },
    {
      id: "statement-cash-out",
      label: "Salidas de caja",
      caption: "Pagos, compras, gastos y retiros",
      valueNumber: -outflowTotal,
      value: jrFmtMoney(outflowTotal),
      children: outflows.map(child),
    },
    {
      id: "statement-cash-net",
      label: "Flujo neto",
      caption: "Entradas - salidas",
      valueNumber: net,
      value: jrFmtMoney(net),
      children: [],
      locked: true,
    },
  ];
}

function JournalAccounting() {
  const [entries, setEntries] = React.useState(jrLoadLocalEntries);
  const [catalog, setCatalog] = React.useState(jrLoadLocalCatalog);
  const [view, setView] = React.useState("dashboard");
  const [activeTab, setActiveTab] = React.useState("borradores");
  const [productionTarget, setProductionTarget] = React.useState(JR_FOCACCIA_DAILY_LIMIT);
  const [statusFilter, setStatusFilter] = React.useState("all");
  const [month, setMonth] = React.useState(jrTodayISO().slice(0, 7));
  const [query, setQuery] = React.useState("");
  const [selectedId, setSelectedId] = React.useState(null);
  const [form, setForm] = React.useState(jrDefaultEntry);
  const [editingId, setEditingId] = React.useState(null);
  const [report, setReport] = React.useState(jrDefaultReport);
  const [pendingDrafts, setPendingDrafts] = React.useState([]);
  const [draftWarnings, setDraftWarnings] = React.useState([]);
  const [syncMessage, setSyncMessage] = React.useState("");
  const [error, setError] = React.useState("");
  const [loadingDraft, setLoadingDraft] = React.useState(false);
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    try { localStorage.setItem(JR_STORAGE_KEY, JSON.stringify(entries)); } catch {}
  }, [entries]);

  React.useEffect(() => {
    try { localStorage.setItem(JR_CATALOG_STORAGE_KEY, JSON.stringify(catalog)); } catch {}
  }, [catalog]);

  React.useEffect(() => {
    let cancelled = false;
    const load = async () => {
      try {
        setSyncMessage("Sincronizando asientos...");
        const result = await jrRequest("/api/journal");
        const serverEntries = Array.isArray(result.entries) ? result.entries.map(jrNormalizeEntry) : [];
        const configResult = await jrRequest("/api/journal/config").catch(() => null);
        if (!cancelled) {
          setEntries(serverEntries);
          if (configResult?.catalog) setCatalog(jrNormalizeCatalog(configResult.catalog));
          setSyncMessage("");
        }
      } catch {
        if (!cancelled) setSyncMessage("Sin conexión con la base del servidor. Los cambios pueden quedar locales.");
      }
    };
    load();
    return () => { cancelled = true; };
  }, []);

  const months = React.useMemo(() => {
    const unique = [...new Set(entries.map((entry) => entry.date.slice(0, 7)))].sort().reverse();
    return unique.length ? unique : [jrTodayISO().slice(0, 7)];
  }, [entries]);

  React.useEffect(() => {
    if (month !== "all" && !months.includes(month)) setMonth(months[0] || "all");
  }, [month, months]);

  const metrics = React.useMemo(() => jrComputeReports(entries, month), [entries, month]);
  const inventoryRows = React.useMemo(() => jrInventoryRows(entries, month, catalog), [entries, month, catalog]);
  const inventoryPositionRows = React.useMemo(() => jrInventoryPositionRows(entries, metrics, catalog), [entries, metrics, catalog]);
  const productionCapacity = React.useMemo(() => jrProductionCapacity(entries, catalog, productionTarget), [entries, catalog, productionTarget]);
  const wipOperationalPosition = React.useMemo(() => jrOperationalInventoryPosition(entries, metrics, "wip"), [entries, metrics]);
  const finishedOperationalPosition = React.useMemo(() => jrOperationalInventoryPosition(entries, metrics, "finished"), [entries, metrics]);
  const salesRows = React.useMemo(() => jrSalesRows(entries, month), [entries, month]);
  const expenseRows = React.useMemo(() => jrExpenseRows(entries, month), [entries, month]);
  const balanceRows = React.useMemo(() => jrBalanceRows(metrics), [metrics]);
  const resultRows = React.useMemo(() => jrResultRows(metrics), [metrics]);
  const cashflowRows = React.useMemo(() => jrCashflowRows(entries, month), [entries, month]);
  const csvPages = React.useMemo(() => [
    { label: "Diario", filename: "break-asientos", columns: JR_DIARY_COLUMNS, rows: jrDiaryRows(jrPeriodEntries(entries, month)) },
    { label: "Inventario", filename: "break-inventario", columns: JR_INVENTORY_COLUMNS, rows: inventoryRows },
    { label: "Producción con stock", filename: "break-produccion-stock", columns: JR_PRODUCTION_COLUMNS, rows: jrProductionCapacityRows(productionCapacity) },
    { label: "Ventas", filename: "break-ventas", columns: JR_SALES_COLUMNS, rows: salesRows },
    { label: "Egresos", filename: "break-egresos", columns: JR_EXPENSE_COLUMNS, rows: expenseRows },
    { label: "Balance", filename: "break-balance", columns: JR_BALANCE_COLUMNS, rows: balanceRows },
    { label: "Resultados", filename: "break-resultados", columns: JR_RESULT_COLUMNS, rows: resultRows },
    { label: "Cashflow", filename: "break-cashflow", columns: JR_CASHFLOW_COLUMNS, rows: cashflowRows },
  ], [entries, month, inventoryRows, productionCapacity, salesRows, expenseRows, balanceRows, resultRows, cashflowRows]);
  const balanceStatementGroups = React.useMemo(() => jrBalanceStatementGroups(balanceRows, metrics), [balanceRows, metrics]);
  const resultStatementGroups = React.useMemo(() => jrResultStatementGroups(resultRows, metrics), [resultRows, metrics]);
  const cashflowStatementGroups = React.useMemo(() => jrCashflowStatementGroups(cashflowRows), [cashflowRows]);
  const visibleEntries = React.useMemo(() => {
    const needle = query.trim().toLowerCase();
    return entries
      .filter((entry) => month === "all" || entry.date.startsWith(month))
      .filter((entry) => statusFilter === "all" || entry.status === statusFilter)
      .filter((entry) => {
        if (!needle) return true;
        return [
          entry.memo,
          entry.source,
          entry.originText,
          ...entry.lines.map((line) => `${line.accountId} ${jrAccount(line.accountId).name} ${line.note}`),
        ].join(" ").toLowerCase().includes(needle);
      })
      .sort((a, b) => `${b.date}${b.createdAt}`.localeCompare(`${a.date}${a.createdAt}`));
  }, [entries, month, query, statusFilter]);

  React.useEffect(() => {
    if (selectedId && !visibleEntries.some((entry) => entry.id === selectedId)) setSelectedId(null);
  }, [selectedId, visibleEntries]);

  const selectedEntry = React.useMemo(() => {
    if (!visibleEntries.length) return null;
    return visibleEntries.find((entry) => entry.id === selectedId) || visibleEntries[0];
  }, [visibleEntries, selectedId]);

  const updateReport = (field, value) => setReport((current) => ({ ...current, [field]: value }));
  const updateForm = (field, value) => setForm((current) => ({ ...current, [field]: value }));
  const updateLine = (index, field, value) => {
    setForm((current) => {
      const lines = [...current.lines];
      lines[index] = { ...lines[index], [field]: value };
      if (field === "debit" && jrParseMoney(value) > 0) lines[index].credit = "";
      if (field === "credit" && jrParseMoney(value) > 0) lines[index].debit = "";
      return { ...current, lines };
    });
  };

  const addLine = () => {
    setForm((current) => ({
      ...current,
      lines: [...current.lines, { accountId: "6080", debit: "", credit: "", note: "" }],
    }));
  };

  const removeLine = (index) => {
    setForm((current) => ({
      ...current,
      lines: current.lines.length <= 2 ? current.lines : current.lines.filter((_, i) => i !== index),
    }));
  };

  const resetForm = () => {
    setEditingId(null);
    setError("");
    setForm(jrDefaultEntry());
  };

  const saveEntriesToServer = async (nextEntries, methodEntryId = "") => {
    if (methodEntryId) {
      return jrRequest(`/api/journal/entries/${encodeURIComponent(methodEntryId)}`, {
        method: "PUT",
        body: JSON.stringify({ entry: nextEntries[0] }),
      });
    }
    return jrRequest("/api/journal/entries", {
      method: "POST",
      body: JSON.stringify({ entries: nextEntries }),
    });
  };

  const saveEntry = async (targetStatus) => {
    if (saving) return;
    const entry = jrNormalizeEntry({
      ...form,
      status: targetStatus,
      id: editingId || form.id || jrMakeId(),
      updatedAt: editingId ? new Date().toISOString() : null,
    });
    if (targetStatus === "posted" && !jrIsBalanced(entry)) {
      setError("Para aprobar, el debe y el haber tienen que ser iguales.");
      return;
    }
    if (entry.lines.length < 2) {
      setError("El asiento necesita al menos dos lineas.");
      return;
    }
    setSaving(true);
    setError("");
    try {
      const result = await saveEntriesToServer([entry], editingId);
      setEntries(Array.isArray(result.entries)
        ? result.entries.map(jrNormalizeEntry)
        : (current) => editingId ? current.map((item) => item.id === entry.id ? entry : item) : [entry, ...current]);
      resetForm();
      setView("dashboard");
    } catch (err) {
      setError(err.message || "No se pudo guardar.");
      setEntries((current) => editingId ? current.map((item) => item.id === entry.id ? entry : item) : [entry, ...current]);
      resetForm();
      setView("dashboard");
    } finally {
      setSaving(false);
    }
  };

  const editEntry = (entry) => {
    setEditingId(entry.id);
    setForm(jrNormalizeEntry(entry));
    setError("");
    setView("new");
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  const approveEntry = async (entry) => {
    if (!jrIsBalanced(entry)) {
      setSyncMessage("Ese asiento no esta balanceado.");
      return;
    }
    const updated = jrNormalizeEntry({ ...entry, status: "posted", updatedAt: new Date().toISOString() });
    try {
      const result = await saveEntriesToServer([updated], entry.id);
      setEntries(Array.isArray(result.entries) ? result.entries.map(jrNormalizeEntry) : (current) => current.map((item) => item.id === entry.id ? updated : item));
    } catch (err) {
      setSyncMessage(err.message || "No se pudo aprobar.");
    }
  };

  const deleteEntry = async (id) => {
    try {
      const result = await jrRequest(`/api/journal/entries/${encodeURIComponent(id)}`, { method: "DELETE" });
      setEntries(Array.isArray(result.entries) ? result.entries.map(jrNormalizeEntry) : (current) => current.filter((entry) => entry.id !== id));
    } catch (err) {
      setSyncMessage(err.message || "No se pudo borrar.");
    }
  };

  const prepareReportDraft = async () => {
    if (!jrHasReportContent(report)) {
      setDraftWarnings(["Cargá al menos un dato: una venta, un gasto, stock o un movimiento puntual."]);
      setPendingDrafts([]);
      return;
    }
    setLoadingDraft(true);
    setError("");
    setDraftWarnings([]);
    setPendingDrafts([]);
    try {
      const result = await jrRequest("/api/journal/draft-report", {
        method: "POST",
        body: JSON.stringify({
          date: report.date,
          report,
          balances: {
            cash: metrics.cash,
            mainInventory: metrics.inventoryMain,
            toppings: metrics.inventoryToppings,
            packaging: metrics.inventoryPackaging,
            wip: metrics.inventoryWip,
            packaged: metrics.inventoryFinished,
            localInventory: metrics.inventoryStores,
            previousSnapshotDate: wipOperationalPosition.date || finishedOperationalPosition.date || metrics.snapshotDate,
            previousWipQty: wipOperationalPosition.qty,
            previousPackagedQty: finishedOperationalPosition.qty,
            costHints: jrInventoryCostHints(entries, catalog),
            inventoryState: jrInventoryCurrentItems(entries, catalog),
            catalog,
          },
        }),
      });
      const drafts = Array.isArray(result.entries) ? result.entries.map(jrNormalizeEntry) : [];
      setPendingDrafts(drafts);
      setDraftWarnings([...(result.warnings || []), ...(result.questions || [])]);
      if (!drafts.length && !(result.warnings || []).length) setDraftWarnings(["No salieron asientos monetarios. Quedaron solo datos operativos."]);
    } catch (err) {
      const fallback = jrBuildRuleDraft(report, metrics);
      setPendingDrafts(fallback.entries);
      setDraftWarnings([`IA no disponible: ${err.message || "sin respuesta"}`, ...fallback.warnings]);
    } finally {
      setLoadingDraft(false);
    }
  };

  const savePendingDrafts = async () => {
    if (!pendingDrafts.length || saving) return;
    setSaving(true);
    try {
      const result = await saveEntriesToServer(pendingDrafts);
      setEntries(Array.isArray(result.entries) ? result.entries.map(jrNormalizeEntry) : (current) => [...pendingDrafts, ...current]);
      setPendingDrafts([]);
      setReport(jrDefaultReport());
      setDraftWarnings([]);
      setSyncMessage("Borradores guardados.");
    } catch (err) {
      setEntries((current) => [...pendingDrafts, ...current]);
      setPendingDrafts([]);
      setSyncMessage(err.message || "Borradores guardados localmente.");
    } finally {
      setSaving(false);
    }
  };

  const editPendingDraft = (entry) => {
    setPendingDrafts((current) => current.filter((item) => item.id !== entry.id));
    setEditingId(null);
    setForm(jrNormalizeEntry(entry));
    setError("");
    setView("new");
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  const removePendingDraft = (id) => {
    setPendingDrafts((current) => current.filter((entry) => entry.id !== id));
  };

  const saveCatalog = async () => {
    const normalized = jrNormalizeCatalog(catalog);
    setCatalog(normalized);
    try {
      const result = await jrRequest("/api/journal/config", {
        method: "PUT",
        body: JSON.stringify({ catalog: normalized }),
      });
      if (result.catalog) setCatalog(jrNormalizeCatalog(result.catalog));
      setSyncMessage("Catálogo guardado.");
    } catch (err) {
      setSyncMessage(err.message || "Catálogo guardado localmente.");
    }
  };

  const totals = jrLineTotals(form.lines);
  const formBalanced = Math.abs(jrRound(totals.debit - totals.credit)) < 0.01 && totals.debit > 0;
  const reportHasContent = jrHasReportContent(report);

  if (view === "new") {
    return (
      <main className="acc-page jr-page">
        <div className="acc-max">
          <header className="acc-header">
            <button className="acc-icon-btn" onClick={() => { resetForm(); setView("dashboard"); }} aria-label="Volver">
              <Icon name="x" size={20} />
            </button>
            <h1 className="acc-title-sm">{editingId ? "Editar asiento" : "Nuevo asiento"}</h1>
            <div style={{ width: 40 }} />
          </header>
          <section className="acc-form jr-entry-form">
            <div className="jr-form-grid">
              <div className="acc-field">
                <label>Fecha</label>
                <input className="acc-input" type="date" value={form.date} onChange={(event) => updateForm("date", event.target.value)} />
              </div>
              <div className="acc-field">
                <label>Estado</label>
                <select className="acc-input" value={form.status} onChange={(event) => updateForm("status", event.target.value)}>
                  <option value="draft">Borrador</option>
                  <option value="posted">Aprobado</option>
                </select>
              </div>
            </div>
            <div className="acc-field">
              <label>Concepto</label>
              <input className="acc-input" value={form.memo} onChange={(event) => updateForm("memo", event.target.value)} placeholder="Venta calle, compra harina, ajuste inventario..." />
            </div>
            <div className="jr-lines-editor">
              <div className="jr-lines-head">
                <span>Cuenta</span>
                <span>Debe</span>
                <span>Haber</span>
                <span>Nota</span>
                <span></span>
              </div>
              {form.lines.map((line, index) => (
                <div className="jr-line-edit" key={`${line.accountId}-${index}`}>
                  <select className="acc-input" value={line.accountId} onChange={(event) => updateLine(index, "accountId", event.target.value)}>
                    {JR_GROUPS.map((group) => (
                      <optgroup key={group} label={group}>
                        {JR_ACCOUNTS.filter((account) => account.group === group).map((account) => (
                          <option key={account.id} value={account.id}>{account.id} · {account.name}</option>
                        ))}
                      </optgroup>
                    ))}
                  </select>
                  <input className="acc-input num" inputMode="decimal" value={line.debit || ""} onChange={(event) => updateLine(index, "debit", event.target.value)} placeholder="0" />
                  <input className="acc-input num" inputMode="decimal" value={line.credit || ""} onChange={(event) => updateLine(index, "credit", event.target.value)} placeholder="0" />
                  <input className="acc-input" value={line.note || ""} onChange={(event) => updateLine(index, "note", event.target.value)} placeholder="Detalle" />
                  <button type="button" className="acc-icon-btn" onClick={() => removeLine(index)} aria-label="Quitar linea">
                    <Icon name="x" size={14} />
                  </button>
                </div>
              ))}
              <button type="button" className="btn btn-ghost btn-sm" onClick={addLine}>
                <Icon name="plus" size={14} /> Linea
              </button>
            </div>
            <div className={`jr-balance-strip ${formBalanced ? "ok" : "bad"}`}>
              <span>Debe {jrFmtMoney(totals.debit)}</span>
              <span>Haber {jrFmtMoney(totals.credit)}</span>
              <strong>{formBalanced ? "Balanceado" : `Diferencia ${jrFmtMoney(Math.abs(totals.debit - totals.credit))}`}</strong>
            </div>
            {error && <p className="ledger-error jr-error">{error}</p>}
            <div className="jr-form-actions">
              <button type="button" className="btn btn-ghost" onClick={() => saveEntry("draft")} disabled={saving}>
                Guardar borrador
              </button>
              <button type="button" className="btn btn-pine" onClick={() => saveEntry("posted")} disabled={saving || !formBalanced}>
                <Icon name="check" size={16} /> Aprobar
              </button>
            </div>
          </section>
        </div>
      </main>
    );
  }

  const kpis = [
    { label: "Caja", value: jrFmtMoney(metrics.cash), icon: "wallet" },
    { label: "Inventario", value: jrFmtMoney(metrics.inventoryValue), icon: "receipt" },
    { label: "Proceso", value: `${jrFmtQty(wipOperationalPosition.qty)} un`, icon: "trend" },
    { label: "Envasadas", value: `${jrFmtQty(finishedOperationalPosition.qty)} un`, icon: "bag2" },
    { label: "Resultado", value: jrFmtMoney(metrics.result), icon: "trend", tone: metrics.result >= 0 ? "in" : "out" },
  ];

  return (
    <main className="acc-page jr-page">
      <div className="acc-max">
        <header className="acc-header jr-header">
          <div>
            <h1 className="acc-title">Asientos contables</h1>
            <p className="jr-subtitle">Diario contable y planillas separadas para operación y estados.</p>
          </div>
          <div className="jr-header-actions">
            <button className="btn btn-ghost" onClick={() => jrExportAllPagesCsv(csvPages)} disabled={!entries.length}>
              <Icon name="download" size={16} /> Todo CSV
            </button>
            <button className="btn btn-ghost" onClick={() => { window.location.hash = "/contabilidad"; }}>
              Caja
            </button>
            <button className="btn btn-pine" onClick={() => { resetForm(); setView("new"); }}>
              <Icon name="plus" size={16} /> Nuevo
            </button>
          </div>
        </header>
        {syncMessage && <div className="acc-sync-msg">{syncMessage}</div>}

        <section className="jr-kpis">
          {kpis.map((item) => (
            <div className="jr-kpi" key={item.label}>
              <span className={`jr-kpi-icon ${item.tone || ""}`}><Icon name={item.icon} size={18} /></span>
              <span>{item.label}</span>
              <strong className={item.tone || ""}>{item.value}</strong>
            </div>
          ))}
        </section>

        <div className="acc-type-tabs jr-tabs" role="tablist" aria-label="Secciones contables">
          {[
            ["borradores", "Reporte 8 AM"],
            ["diario", "Diario"],
            ["inventario", "Inventario"],
            ["produccion", "Producción"],
            ["ventas", "Ventas"],
            ["egresos", "Egresos"],
            ["balance", "Balance"],
            ["resultados", "Resultados"],
            ["cashflow", "Cashflow"],
            ["config", "Config"],
          ].map(([id, label]) => (
            <button key={id} type="button" className={activeTab === id ? "active" : ""} onClick={() => setActiveTab(id)}>{label}</button>
          ))}
        </div>

        {activeTab === "borradores" && (
          <>
            <section className="jr-report-panel">
              <div className="jr-section-head">
                <div>
                  <h2>Reporte de Santiago</h2>
                  <p>{report.weeklyToppings ? "Parcial o completo, con toppings semanales" : "Parcial o completo, solo lo que corresponda"}</p>
                </div>
                <input className="acc-month-select-box jr-date-input" type="date" value={report.date} onChange={(event) => updateReport("date", event.target.value)} />
              </div>
              <div className="jr-report-grid">
                <label className="acc-field wide"><span>Movimiento puntual</span><textarea className="acc-input" rows="3" value={report.notesText} onChange={(event) => updateReport("notesText", event.target.value)} placeholder="Venta calle 8 focaccias R$ 56; consignación Local A vendió 4 R$ 32; compra harina 25kg R$ 80" /></label>
                <label className="acc-field"><span>Dinero total</span><input className="acc-input" inputMode="decimal" value={report.moneyTotal} onChange={(event) => updateReport("moneyTotal", event.target.value)} /></label>
                <label className="acc-field"><span>Focaccias en proceso</span><input className="acc-input" inputMode="decimal" value={report.wipQty} onChange={(event) => updateReport("wipQty", event.target.value)} /></label>
                <label className="acc-field"><span>Focaccias envasadas</span><input className="acc-input" inputMode="decimal" value={report.packagedQty} onChange={(event) => updateReport("packagedQty", event.target.value)} /></label>
                <label className="acc-field"><span>Vendidas en la calle</span><input className="acc-input" inputMode="decimal" value={report.streetSoldQty} onChange={(event) => updateReport("streetSoldQty", event.target.value)} /></label>
                <label className="acc-field wide"><span>Gastos con detalle</span><textarea className="acc-input" rows="3" value={report.expensesText} onChange={(event) => updateReport("expensesText", event.target.value)} placeholder="15 harina, 8 bolsas, 5 cigarrillos socio" /></label>
                <label className="acc-field wide"><span>Entregadas en consignación</span><textarea className="acc-input" rows="2" value={report.deliveredToStoresText} onChange={(event) => updateReport("deliveredToStoresText", event.target.value)} /></label>
                <label className="acc-field wide"><span>Vendidas en consignación</span><textarea className="acc-input" rows="2" value={report.storeSoldText} onChange={(event) => updateReport("storeSoldText", event.target.value)} /></label>
                <label className="acc-field wide"><span>Regaladas / dañadas / marketing</span><textarea className="acc-input" rows="2" value={report.exceptionsText} onChange={(event) => updateReport("exceptionsText", event.target.value)} /></label>
                <label className="acc-field wide"><span>Stock principal</span><textarea className="acc-input" rows="3" value={report.stockMainText} onChange={(event) => updateReport("stockMainText", event.target.value)} /></label>
              </div>
              <label className="jr-check">
                <input type="checkbox" checked={report.weeklyToppings} onChange={(event) => updateReport("weeklyToppings", event.target.checked)} />
                <span>Stock de toppings semanal</span>
              </label>
              {report.weeklyToppings && (
                <label className="acc-field jr-toppings"><span>Stock de toppings</span><textarea className="acc-input" rows="3" value={report.toppingsText} onChange={(event) => updateReport("toppingsText", event.target.value)} /></label>
              )}
              <div className="jr-report-actions">
                <button className="btn btn-pine" onClick={prepareReportDraft} disabled={loadingDraft || !reportHasContent}>
                  <Icon name="spark" size={16} /> {loadingDraft ? "Preparando..." : "Preparar borrador"}
                </button>
                <button className="btn btn-ghost" onClick={() => { setReport(jrDefaultReport()); setPendingDrafts([]); setDraftWarnings([]); }}>
                  Limpiar
                </button>
              </div>
            </section>

            {(draftWarnings.length > 0 || pendingDrafts.length > 0) && (
              <section className="jr-draft-review">
                <div className="jr-section-head">
                  <div>
                    <h2>Borradores preparados</h2>
                    <p>{pendingDrafts.length} asientos</p>
                  </div>
                  <button className="btn btn-amber" onClick={savePendingDrafts} disabled={!pendingDrafts.length || saving}>
                    Guardar borradores
                  </button>
                </div>
                {draftWarnings.length > 0 && (
                  <div className="jr-warnings">
                    {draftWarnings.map((warning, index) => <span key={`${warning}-${index}`}>{warning}</span>)}
                  </div>
                )}
                <JournalDraftReviewCards entries={pendingDrafts} catalog={catalog} onEdit={editPendingDraft} onRemove={removePendingDraft} />
              </section>
            )}
          </>
        )}

        {activeTab === "diario" && (
          <JournalDiary
            entries={visibleEntries}
            selectedEntry={selectedEntry}
            catalog={catalog}
            month={month}
            months={months}
            statusFilter={statusFilter}
            query={query}
            setMonth={setMonth}
            setStatusFilter={setStatusFilter}
            setQuery={setQuery}
            setSelectedId={setSelectedId}
            onEdit={editEntry}
            onApprove={approveEntry}
            onDelete={deleteEntry}
          />
        )}

        {activeTab === "inventario" && (
          <JournalSheet
            title="Inventario"
            subtitle="Stock, producción y valorización por línea"
            filename="break-inventario"
            positionRows={inventoryPositionRows}
            rows={inventoryRows}
            columns={JR_INVENTORY_COLUMNS}
            totalLabel="Valor inventario"
            totalValue={inventoryRows.reduce((sum, row) => sum + (row.valueNumber || 0), 0)}
            month={month}
            months={months}
            setMonth={setMonth}
          />
        )}
        {activeTab === "produccion" && (
          <JournalProductionCapacity capacity={productionCapacity} dailyTarget={productionTarget} setDailyTarget={setProductionTarget} />
        )}
        {activeTab === "ventas" && (
          <JournalSheet
            title="Ventas"
            subtitle="Venta a la calle, consignación y take-away"
            filename="break-ventas"
            rows={salesRows}
            columns={JR_SALES_COLUMNS}
            totalLabel="Ventas"
            totalValue={salesRows.reduce((sum, row) => sum + (row.amountNumber || 0), 0)}
            month={month}
            months={months}
            setMonth={setMonth}
          />
        )}
        {activeTab === "egresos" && (
          <JournalSheet
            title="Egresos"
            subtitle="Salidas de dinero: compras, gastos y socios"
            filename="break-egresos"
            rows={expenseRows}
            columns={JR_EXPENSE_COLUMNS}
            totalLabel="Egresos"
            totalValue={expenseRows.reduce((sum, row) => sum + (row.amountNumber || 0), 0)}
            month={month}
            months={months}
            setMonth={setMonth}
          />
        )}
        {activeTab === "balance" && (
          <JournalSheet
            title="Balance"
            subtitle="Activos, pasivos, patrimonio y resultado acumulado"
            filename="break-balance"
            statementGroups={balanceStatementGroups}
            rows={balanceRows}
            columns={JR_BALANCE_COLUMNS}
            totalLabel="Activos"
            totalValue={metrics.assets}
            summaryItems={[
              { label: "Activos", value: jrFmtMoney(metrics.assets) },
              { label: "Pasivos", value: jrFmtMoney(metrics.liabilities) },
              { label: "Patrimonio + resultado", value: jrFmtMoney(metrics.equity + metrics.currentResult) },
              { label: "Diferencia", value: jrFmtMoney(metrics.balanceCheck) },
            ]}
            month={month}
            months={months}
            setMonth={setMonth}
          />
        )}
        {activeTab === "resultados" && (
          <JournalSheet
            title="Resultados"
            subtitle="Ingresos, CMV/costos, gastos y resultado neto"
            filename="break-resultados"
            statementGroups={resultStatementGroups}
            rows={resultRows}
            columns={JR_RESULT_COLUMNS}
            totalLabel="Resultado"
            totalValue={metrics.result}
            summaryItems={[
              { label: "Ingresos", value: jrFmtMoney(metrics.income) },
              { label: "Costos", value: jrFmtMoney(metrics.costs) },
              { label: "Gastos", value: jrFmtMoney(metrics.expenses) },
              { label: "Resultado", value: jrFmtMoney(metrics.result) },
            ]}
            month={month}
            months={months}
            setMonth={setMonth}
          />
        )}
        {activeTab === "cashflow" && (
          <JournalSheet
            title="Cashflow"
            subtitle="Entradas y salidas reales de caja"
            filename="break-cashflow"
            statementGroups={cashflowStatementGroups}
            rows={cashflowRows}
            columns={JR_CASHFLOW_COLUMNS}
            totalLabel="Flujo neto"
            totalValue={cashflowRows.reduce((sum, row) => sum + (row.amountNumber || 0), 0)}
            summaryItems={[
              { label: "Entradas", value: jrFmtMoney(cashflowRows.filter((row) => row.amountNumber > 0).reduce((sum, row) => sum + row.amountNumber, 0)) },
              { label: "Salidas", value: jrFmtMoney(Math.abs(cashflowRows.filter((row) => row.amountNumber < 0).reduce((sum, row) => sum + row.amountNumber, 0))) },
              { label: "Flujo neto", value: jrFmtMoney(cashflowRows.reduce((sum, row) => sum + (row.amountNumber || 0), 0)) },
              { label: "Caja actual", value: jrFmtMoney(metrics.cash) },
            ]}
            month={month}
            months={months}
            setMonth={setMonth}
          />
        )}
        {activeTab === "config" && (
          <JournalCatalogConfig catalog={catalog} setCatalog={setCatalog} onSave={saveCatalog} />
        )}
      </div>
    </main>
  );
}

function JournalEntryList({ entries, onSelect, selectedEntry, compact = false }) {
  if (!entries.length) return <div className="acc-empty">Sin asientos.</div>;
  return (
    <div className={`jr-entry-list ${compact ? "compact" : ""}`}>
      {entries.map((entry) => {
        const totals = jrLineTotals(entry.lines);
        const status = jrStatusMeta(entry.status);
        return (
          <button key={entry.id} type="button" className={`jr-entry-row ${selectedEntry?.id === entry.id ? "active" : ""}`} onClick={() => onSelect(entry.id)}>
            <span>
              <strong>{entry.memo || "Asiento contable"}</strong>
              <small>{jrDateLabel(entry.date)} · {entry.lines.length} lineas</small>
            </span>
            <span className={`acc-type-chip ${status.tone}`}>{status.label}</span>
            <b>{jrFmtMoney(totals.debit)}</b>
          </button>
        );
      })}
    </div>
  );
}

function JournalDiary({ entries, selectedEntry, catalog, month, months, statusFilter, query, setMonth, setStatusFilter, setQuery, setSelectedId, onEdit, onApprove, onDelete }) {
  return (
    <section className="acc-feed jr-diary">
      <div className="acc-viewer-head">
        <div>
          <h2>Diario contable</h2>
          <p>{entries.length} asientos visibles</p>
        </div>
        <button type="button" className="btn btn-ghost btn-sm" onClick={() => jrExportCsv(entries)} disabled={!entries.length}>
          <Icon name="download" size={15} /> CSV
        </button>
      </div>
      <div className="acc-viewer-tools">
        <label className="acc-search">
          <Icon name="search" size={17} />
          <input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Buscar cuenta o concepto..." />
        </label>
        <select className="acc-month-select acc-month-select-box" value={month} onChange={(event) => setMonth(event.target.value)}>
          <option value="all">Todos los meses</option>
          {months.map((m) => <option key={m} value={m}>{jrMonthLabel(m)}</option>)}
        </select>
        <select className="acc-month-select acc-month-select-box" value={statusFilter} onChange={(event) => setStatusFilter(event.target.value)}>
          <option value="all">Todos</option>
          <option value="draft">Borradores</option>
          <option value="posted">Aprobados</option>
        </select>
      </div>
      <div className="acc-viewer-grid jr-viewer-grid">
        <div className="acc-table-shell">
          <JournalEntryList entries={entries} selectedEntry={selectedEntry} onSelect={setSelectedId} />
        </div>
        <JournalDetail entry={selectedEntry} catalog={catalog} onEdit={onEdit} onApprove={onApprove} onDelete={onDelete} />
      </div>
    </section>
  );
}

function JournalDoubleEntry({ entry }) {
  const totals = jrLineTotals(entry.lines);
  const difference = jrRound(totals.debit - totals.credit);
  const balanced = jrIsBalanced(entry);
  return (
    <section className="jr-double-entry" aria-label="Partida doble">
      <div className="jr-double-entry-head">
        <span>Partida doble</span>
        <strong className={balanced ? "ok" : "bad"}>{balanced ? "Cuadrada" : `Diferencia ${jrFmtMoney(Math.abs(difference))}`}</strong>
      </div>
      <div className="jr-double-entry-wrap">
        <table className="jr-double-entry-table">
          <thead>
            <tr>
              <th>Cuenta</th>
              <th>Detalle</th>
              <th className="num">Debe</th>
              <th className="num">Haber</th>
            </tr>
          </thead>
          <tbody>
            {entry.lines.map((line, index) => {
              const account = jrAccount(line.accountId);
              return (
                <tr key={`${line.accountId}-${index}`}>
                  <td>
                    <span className="jr-account-code">{line.accountId}</span>
                    <strong>{account.name}</strong>
                    <small>{account.group}</small>
                  </td>
                  <td>{line.note || "-"}</td>
                  <td className={`num ${line.debit > 0 ? "active" : ""}`}>{line.debit > 0 ? jrFmtMoney(line.debit) : "-"}</td>
                  <td className={`num ${line.credit > 0 ? "active" : ""}`}>{line.credit > 0 ? jrFmtMoney(line.credit) : "-"}</td>
                </tr>
              );
            })}
          </tbody>
          <tfoot>
            <tr>
              <th colSpan="2">Totales</th>
              <td className="num">{jrFmtMoney(totals.debit)}</td>
              <td className="num">{jrFmtMoney(totals.credit)}</td>
            </tr>
          </tfoot>
        </table>
      </div>
    </section>
  );
}

function JournalOperationalChanges({ entry, catalog = jrDefaultCatalog() }) {
  const inventoryRows = jrInventoryRows([entry], "all", catalog);
  const salesRows = jrSalesRows([entry], "all");
  if (!inventoryRows.length && !salesRows.length) return null;
  return (
    <section className="jr-op-review" aria-label="Modificaciones operativas">
      <div className="jr-op-review-head">
        <span>Modificaciones operativas</span>
        <small>Impacto explícito en planillas auxiliares</small>
      </div>
      {salesRows.length > 0 && (
        <div className="jr-op-block">
          <h4>Ventas</h4>
          <div className="jr-op-table-wrap">
            <table className="jr-op-table">
              <thead>
                <tr>
                  <th>Canal</th>
                  <th>Punto / local</th>
                  <th className="num">Cantidad</th>
                  <th className="num">Monto</th>
                </tr>
              </thead>
              <tbody>
                {salesRows.map((row) => (
                  <tr key={row.id}>
                    <td>{row.channel || "-"}</td>
                    <td>{row.location || "-"}</td>
                    <td className="num">{row.qty || "-"}</td>
                    <td className="num strong">{row.amount || "-"}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}
      {inventoryRows.length > 0 && (
        <div className="jr-op-block">
          <h4>Inventario</h4>
          <div className="jr-op-table-wrap">
            <table className="jr-op-table">
              <thead>
                <tr>
                  <th>Área</th>
                  <th>Item</th>
                  <th className="num">Cantidad</th>
                  <th>Unidad</th>
                  <th className="num">Valor</th>
                </tr>
              </thead>
              <tbody>
                {inventoryRows.map((row) => (
                  <tr key={row.id}>
                    <td>{row.area || "-"}</td>
                    <td>{row.item || "-"}</td>
                    <td className="num">{row.qty || "-"}</td>
                    <td>{row.unit || "-"}</td>
                    <td className="num strong">{row.value || "-"}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}
    </section>
  );
}

function JournalDraftReviewCards({ entries, catalog, onEdit, onRemove }) {
  if (!entries.length) return null;
  return (
    <div className="jr-draft-cards">
      {entries.map((entry) => {
        const totals = jrLineTotals(entry.lines);
        const balanced = jrIsBalanced(entry);
        return (
          <article className="jr-draft-card" key={entry.id}>
            <div className="jr-draft-card-head">
              <div>
                <span className={`acc-type-chip ${balanced ? "in" : "audit"}`}>{balanced ? "Listo para aprobar" : "Revisar"}</span>
                <h3>{entry.memo || "Asiento contable"}</h3>
                <p>{entry.date} · {entry.lines.length} líneas · {jrFmtMoney(totals.debit)}</p>
              </div>
              <div className="jr-draft-card-actions">
                <button type="button" className="btn btn-ghost btn-sm" onClick={() => onEdit(entry)}>
                  <Icon name="edit" size={14} /> Editar
                </button>
                <button type="button" className="btn btn-ghost btn-sm danger" onClick={() => onRemove(entry.id)}>
                  <Icon name="trash" size={14} /> Quitar
                </button>
              </div>
            </div>
            <JournalDoubleEntry entry={entry} />
            <JournalOperationalChanges entry={entry} catalog={catalog} />
          </article>
        );
      })}
    </div>
  );
}

function JournalDetail({ entry, catalog, onEdit, onApprove, onDelete }) {
  if (!entry) return <aside className="acc-detail-panel"><div className="acc-empty">Selecciona un asiento.</div></aside>;
  const totals = jrLineTotals(entry.lines);
  const status = jrStatusMeta(entry.status);
  const difference = jrRound(totals.debit - totals.credit);
  const balanced = jrIsBalanced(entry);
  return (
    <aside className="acc-detail-panel jr-detail-panel" aria-label="Detalle del asiento">
      <div className="acc-detail-top">
        <span className={`acc-type-chip ${status.tone}`}>{status.label}</span>
        <strong>{jrFmtMoney(totals.debit)}</strong>
      </div>
      <h3>{entry.memo || "Asiento contable"}</h3>
      <dl className="acc-detail-list">
        <div><dt>Fecha</dt><dd>{entry.date}</dd></div>
        <div><dt>Origen</dt><dd>{entry.source || "-"}</dd></div>
        <div><dt>Líneas</dt><dd>{entry.lines.length}</dd></div>
        <div><dt>Balance</dt><dd className={balanced ? "jr-balance-ok" : "jr-balance-bad"}>{balanced ? "Balanceado" : jrFmtMoney(Math.abs(difference))}</dd></div>
      </dl>
      <JournalDoubleEntry entry={entry} />
      <JournalOperationalChanges entry={entry} catalog={catalog} />
      <div className="acc-detail-actions">
        {entry.status !== "posted" && (
          <button type="button" className="btn btn-pine btn-sm" onClick={() => onApprove(entry)} disabled={!jrIsBalanced(entry)}>
            <Icon name="check" size={14} /> Aprobar
          </button>
        )}
        <button type="button" className="btn btn-ghost btn-sm" onClick={() => onEdit(entry)}>
          <Icon name="edit" size={14} /> Editar
        </button>
        <button type="button" className="btn btn-ghost btn-sm danger" onClick={() => onDelete(entry.id)}>
          <Icon name="trash" size={14} /> Eliminar
        </button>
      </div>
    </aside>
  );
}

function JournalCatalogConfig({ catalog, setCatalog, onSave }) {
  const safeCatalog = jrNormalizeCatalog(catalog);
  const [areaDraft, setAreaDraft] = React.useState({ name: "", accountId: "1210", aliases: "" });
  const [itemDraft, setItemDraft] = React.useState({ areaId: "main", name: "", unit: "kg", aliases: "", unitCost: "" });

  const updateArea = (id, field, value) => {
    setCatalog((current) => {
      const next = jrNormalizeCatalog(current);
      return {
        ...next,
        areas: next.areas.map((area) => area.id === id ? { ...area, [field]: value } : area),
      };
    });
  };

  const addArea = () => {
    const name = areaDraft.name.trim();
    if (!name) return;
    setCatalog((current) => jrNormalizeCatalog({
      ...current,
      areas: [...jrNormalizeCatalog(current).areas, {
        id: jrSlugId(name),
        name,
        accountId: areaDraft.accountId,
        aliases: areaDraft.aliases,
      }],
    }));
    setAreaDraft({ name: "", accountId: "1210", aliases: "" });
  };

  const removeArea = (id) => {
    const area = safeCatalog.areas.find((item) => item.id === id);
    if (!area || area.locked) return;
    setCatalog((current) => {
      const next = jrNormalizeCatalog(current);
      return {
        areas: next.areas.filter((item) => item.id !== id),
        items: next.items.map((item) => item.areaId === id ? { ...item, areaId: "main" } : item),
      };
    });
  };

  const updateItem = (id, field, value) => {
    setCatalog((current) => {
      const next = jrNormalizeCatalog(current);
      return {
        ...next,
        items: next.items.map((item) => item.id === id ? { ...item, [field]: value } : item),
      };
    });
  };

  const addItem = () => {
    const name = itemDraft.name.trim();
    if (!name) return;
    setCatalog((current) => {
      const next = jrNormalizeCatalog(current);
      return jrNormalizeCatalog({
        ...next,
        items: [...next.items, {
          id: jrSlugId(name),
          areaId: itemDraft.areaId,
          name,
          unit: itemDraft.unit,
          aliases: itemDraft.aliases,
          unitCost: jrParseMoney(itemDraft.unitCost),
          active: true,
        }],
      });
    });
    setItemDraft({ areaId: itemDraft.areaId, name: "", unit: itemDraft.unit || "kg", aliases: "", unitCost: "" });
  };

  const removeItem = (id) => {
    setCatalog((current) => {
      const next = jrNormalizeCatalog(current);
      return { ...next, items: next.items.filter((item) => item.id !== id) };
    });
  };

  const resetCatalog = () => setCatalog(jrDefaultCatalog());

  return (
    <section className="jr-catalog-panel">
      <div className="jr-section-head">
        <div>
          <h2>Configuración</h2>
          <p>Catálogo dinámico de áreas, items, unidades, alias y costos base.</p>
        </div>
        <div className="jr-catalog-actions">
          <button type="button" className="btn btn-ghost" onClick={resetCatalog}>Restaurar base</button>
          <button type="button" className="btn btn-pine" onClick={onSave}>
            <Icon name="check" size={15} /> Guardar catálogo
          </button>
        </div>
      </div>

      <div className="jr-catalog-grid">
        <div className="jr-catalog-section">
          <div className="jr-catalog-title">
            <h3>Áreas de inventario</h3>
            <span>{safeCatalog.areas.length} áreas</span>
          </div>
          <div className="jr-catalog-table-wrap">
            <table className="jr-catalog-table">
              <thead>
                <tr>
                  <th>Área</th>
                  <th>Cuenta</th>
                  <th>Alias</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {safeCatalog.areas.map((area) => (
                  <tr key={area.id}>
                    <td><input className="acc-input" value={area.name} onChange={(event) => updateArea(area.id, "name", event.target.value)} /></td>
                    <td>
                      <select className="acc-input" value={area.accountId} onChange={(event) => updateArea(area.id, "accountId", event.target.value)}>
                        {JR_INVENTORY_ACCOUNT_IDS.map((accountId) => <option key={accountId} value={accountId}>{accountId} · {jrAccount(accountId).name}</option>)}
                      </select>
                    </td>
                    <td><input className="acc-input" value={area.aliases || ""} onChange={(event) => updateArea(area.id, "aliases", event.target.value)} placeholder="alias separados por coma" /></td>
                    <td>
                      <button type="button" className="acc-icon-btn" onClick={() => removeArea(area.id)} disabled={area.locked} aria-label="Quitar área">
                        <Icon name="trash" size={14} />
                      </button>
                    </td>
                  </tr>
                ))}
                <tr className="jr-catalog-add-row">
                  <td><input className="acc-input" value={areaDraft.name} onChange={(event) => setAreaDraft((current) => ({ ...current, name: event.target.value }))} placeholder="Nueva área" /></td>
                  <td>
                    <select className="acc-input" value={areaDraft.accountId} onChange={(event) => setAreaDraft((current) => ({ ...current, accountId: event.target.value }))}>
                      {JR_INVENTORY_ACCOUNT_IDS.map((accountId) => <option key={accountId} value={accountId}>{accountId} · {jrAccount(accountId).name}</option>)}
                    </select>
                  </td>
                  <td><input className="acc-input" value={areaDraft.aliases} onChange={(event) => setAreaDraft((current) => ({ ...current, aliases: event.target.value }))} placeholder="alias" /></td>
                  <td><button type="button" className="acc-icon-btn" onClick={addArea} aria-label="Agregar área"><Icon name="plus" size={14} /></button></td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>

        <div className="jr-catalog-section">
          <div className="jr-catalog-title">
            <h3>Items por área</h3>
            <span>{safeCatalog.items.length} items</span>
          </div>
          <div className="jr-catalog-table-wrap">
            <table className="jr-catalog-table jr-catalog-items-table">
              <thead>
                <tr>
                  <th>Área</th>
                  <th>Item</th>
                  <th>Unidad</th>
                  <th className="num">Costo base</th>
                  <th>Alias</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {safeCatalog.items.map((item) => (
                  <tr key={item.id}>
                    <td>
                      <select className="acc-input" value={item.areaId} onChange={(event) => updateItem(item.id, "areaId", event.target.value)}>
                        {safeCatalog.areas.map((area) => <option key={area.id} value={area.id}>{area.name}</option>)}
                      </select>
                    </td>
                    <td><input className="acc-input" value={item.name} onChange={(event) => updateItem(item.id, "name", event.target.value)} /></td>
                    <td><input className="acc-input" value={item.unit || ""} onChange={(event) => updateItem(item.id, "unit", event.target.value)} /></td>
                    <td><input className="acc-input num" inputMode="decimal" value={item.unitCost || ""} onChange={(event) => updateItem(item.id, "unitCost", event.target.value)} /></td>
                    <td><input className="acc-input" value={item.aliases || ""} onChange={(event) => updateItem(item.id, "aliases", event.target.value)} placeholder="alias separados por coma" /></td>
                    <td><button type="button" className="acc-icon-btn" onClick={() => removeItem(item.id)} aria-label="Quitar item"><Icon name="trash" size={14} /></button></td>
                  </tr>
                ))}
                <tr className="jr-catalog-add-row">
                  <td>
                    <select className="acc-input" value={itemDraft.areaId} onChange={(event) => setItemDraft((current) => ({ ...current, areaId: event.target.value }))}>
                      {safeCatalog.areas.map((area) => <option key={area.id} value={area.id}>{area.name}</option>)}
                    </select>
                  </td>
                  <td><input className="acc-input" value={itemDraft.name} onChange={(event) => setItemDraft((current) => ({ ...current, name: event.target.value }))} placeholder="Nuevo item" /></td>
                  <td><input className="acc-input" value={itemDraft.unit} onChange={(event) => setItemDraft((current) => ({ ...current, unit: event.target.value }))} /></td>
                  <td><input className="acc-input num" inputMode="decimal" value={itemDraft.unitCost} onChange={(event) => setItemDraft((current) => ({ ...current, unitCost: event.target.value }))} placeholder="0" /></td>
                  <td><input className="acc-input" value={itemDraft.aliases} onChange={(event) => setItemDraft((current) => ({ ...current, aliases: event.target.value }))} placeholder="alias" /></td>
                  <td><button type="button" className="acc-icon-btn" onClick={addItem} aria-label="Agregar item"><Icon name="plus" size={14} /></button></td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </section>
  );
}

function JournalProductionCapacity({ capacity, dailyTarget, setDailyTarget }) {
  const updateTarget = (value) => {
    if (setDailyTarget) setDailyTarget(jrClampProductionTarget(value));
  };
  return (
    <section className="jr-sheet jr-production-window">
      <div className="jr-section-head">
        <div>
          <h2>Producción</h2>
          <p>Focaccias posibles con stock y objetivo diario por capacidad de heladera.</p>
        </div>
        <div className="jr-production-control">
          <label htmlFor="jr-production-target">Objetivo diario</label>
          <div>
            <input
              id="jr-production-target"
              className="acc-input num"
              type="number"
              min="1"
              max={capacity.dailyLimit}
              value={dailyTarget}
              onChange={(event) => updateTarget(event.target.value)}
            />
            <span>/ {capacity.dailyLimit} max</span>
          </div>
          <input
            className="jr-production-range"
            type="range"
            min="1"
            max={capacity.dailyLimit}
            step="1"
            value={dailyTarget}
            onChange={(event) => updateTarget(event.target.value)}
            aria-label="Objetivo diario de producción"
          />
        </div>
      </div>
      <div className="jr-production-note">
        <Icon name="receipt" size={15} />
        <span>El límite de {capacity.dailyLimit} focaccias por día viene de la heladera. El cálculo usa cantidades de stock y receta, sin mirar precios.</span>
      </div>
      <div className="jr-production-summary">
        <div>
          <span>Producción posible</span>
          <strong>{jrFmtQty(capacity.maxFocaccias)} un</strong>
          <small>Limita: {capacity.bottleneckText}</small>
        </div>
        <div>
          <span>Masa disponible</span>
          <strong>{jrFmtQty(capacity.doughFocaccias)} un</strong>
          <small>{jrFmtQty(capacity.doughs)} masas completas</small>
        </div>
        <div>
          <span>Packaging disponible</span>
          <strong>{jrFmtQty(capacity.packagingFocaccias)} un</strong>
          <small>Incluye molde, sticker y bolsa</small>
        </div>
        <div>
          <span>Cobertura objetivo</span>
          <strong>{jrFmtQty(jrRound(capacity.dailyCoverage * 100))}%</strong>
          <small>Faltan {jrFmtQty(capacity.missingForDay)} un para 1 día</small>
        </div>
      </div>
      <div className="jr-sheet-wrap jr-production-wrap">
        <table className="jr-sheet-table jr-production-table">
          <thead>
            <tr>
              <th>Componente</th>
              <th>Grupo</th>
              <th className="num">Stock</th>
              <th className="num">Uso receta</th>
              <th className="num">Focaccias posibles</th>
              <th className="num">Necesario objetivo/día</th>
              <th className="num">Falta para 1 día</th>
              <th className="num">Días cubiertos</th>
            </tr>
          </thead>
          <tbody>
            {capacity.components.map((component) => (
              <tr key={component.id} className={component.bottleneck ? "jr-production-bottleneck" : ""}>
                <td>{component.label}</td>
                <td>{component.group}</td>
                <td className="num">{component.stockText}</td>
                <td className="num">{component.recipeText}</td>
                <td className="num">{component.possibleText}</td>
                <td className="num">{component.dailyNeedText}</td>
                <td className={`num ${component.shortageForDay > 0 ? "jr-shortage" : "jr-ok"}`}>{component.shortageText}</td>
                <td className="num">{component.daysText}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </section>
  );
}

function JournalInventoryPosition({ groups }) {
  const [collapsed, setCollapsed] = React.useState({});
  const toggle = (id) => setCollapsed((current) => ({ ...current, [id]: !current[id] }));

  return (
    <div className="jr-position-block">
      <div className="jr-position-title">
        <h3>Posición actual</h3>
        <span>Comparación entre libros y conteo físico</span>
      </div>
      <div className="jr-sheet-wrap jr-position-wrap">
        <table className="jr-sheet-table jr-position-table">
          <thead>
            <tr>
              <th>Grupo / insumo</th>
              <th className="num">Cantidad</th>
              <th>Unidad</th>
              <th className="num">Precio</th>
              <th className="num">Valor contable</th>
              <th className="num">Valor físico</th>
              <th className="num">Diferencia</th>
              <th>Cuenta / origen</th>
              <th>Último dato</th>
            </tr>
          </thead>
          <tbody>
            {groups.map((group) => {
              const isOpen = !collapsed[group.id];
              return (
                <React.Fragment key={group.id}>
                  <tr className="jr-position-group-row">
                    <td>
                      <button
                        type="button"
                        className="jr-position-toggle"
                        onClick={() => toggle(group.id)}
                        aria-label={isOpen ? `Colapsar ${group.area}` : `Expandir ${group.area}`}
                        title={isOpen ? "Colapsar" : "Expandir"}
                      >
                        <Icon name={isOpen ? "minus" : "plus"} size={12} />
                      </button>
                      <span className="jr-position-group-name">{group.area}</span>
                    </td>
                    <td className="num">{group.qty}</td>
                    <td>{group.detail}</td>
                    <td className="num">-</td>
                    <td className="num">{group.valueText}</td>
                    <td className="num">{group.physicalValueText}</td>
                    <td className={`num jr-diff ${group.differenceTone}`}>{group.differenceText}</td>
                    <td>{group.accountLabel}</td>
                    <td>{group.date || "-"}</td>
                  </tr>
                  {isOpen && group.children.map((item) => (
                    <tr className="jr-position-child-row" key={item.id}>
                      <td><span className="jr-position-child-name">{item.name}</span></td>
                      <td className="num">{item.qty}</td>
                      <td>{item.unit}</td>
                      <td className="num">{item.unitCost}</td>
                      <td className="num">-</td>
                      <td className="num">{item.value}</td>
                      <td className="num">-</td>
                      <td>
                        <span className="jr-position-source">{item.source}</span>
                        <span className="jr-position-status">Estado: {item.status}</span>
                      </td>
                      <td>{item.date}</td>
                    </tr>
                  ))}
                  {isOpen && !group.children.length && (
                    <tr className="jr-position-child-row jr-position-empty-row">
                      <td colSpan="9">Sin detalle por insumo. Cargá el conteo con nombre, cantidad, unidad y precio para valorizarlo.</td>
                    </tr>
                  )}
                </React.Fragment>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function JournalStatement({ groups }) {
  const [collapsed, setCollapsed] = React.useState({});
  const toggle = (id) => setCollapsed((current) => ({ ...current, [id]: !current[id] }));

  return (
    <div className="jr-sheet-wrap jr-statement-wrap">
      <table className="jr-sheet-table jr-statement-table">
        <thead>
          <tr>
            <th>Partida</th>
            <th>Detalle</th>
            <th className="num">Importe</th>
          </tr>
        </thead>
        <tbody>
          {groups.map((group) => {
            const isOpen = !collapsed[group.id];
            const hasChildren = group.children.length > 0;
            return (
              <React.Fragment key={group.id}>
                <tr className={`jr-statement-group-row ${group.locked ? "locked" : ""}`}>
                  <td>
                    {hasChildren ? (
                      <button
                        type="button"
                        className="jr-position-toggle jr-statement-toggle"
                        onClick={() => toggle(group.id)}
                        aria-label={isOpen ? `Colapsar ${group.label}` : `Expandir ${group.label}`}
                        title={isOpen ? "Colapsar" : "Expandir"}
                      >
                        <Icon name={isOpen ? "minus" : "plus"} size={12} />
                      </button>
                    ) : <span className="jr-statement-spacer" />}
                    <span className="jr-position-group-name">{group.label}</span>
                  </td>
                  <td>{group.caption}</td>
                  <td className={`num jr-statement-value ${group.valueNumber < 0 ? "negative" : "positive"}`}>{group.value}</td>
                </tr>
                {hasChildren && isOpen && group.children.map((child) => (
                  <tr className="jr-statement-child-row" key={child.id}>
                    <td><span className="jr-position-child-name">{child.label}</span></td>
                    <td>{child.meta || "-"}</td>
                    <td className={`num jr-statement-value ${child.valueNumber < 0 ? "negative" : "positive"}`}>{child.value}</td>
                  </tr>
                ))}
                {hasChildren && isOpen && !group.children.length && (
                  <tr className="jr-statement-child-row jr-position-empty-row">
                    <td colSpan="3">Sin partidas para este grupo.</td>
                  </tr>
                )}
              </React.Fragment>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function JournalSheet({ title, subtitle, filename, positionRows = [], statementGroups = [], rows, columns, totalLabel, totalValue, summaryItems = [], month, months, setMonth }) {
  const summary = summaryItems.length ? summaryItems : [
    { label: "Filas", value: rows.length },
    { label: totalLabel, value: jrFmtMoney(totalValue) },
  ];
  return (
    <section className="jr-sheet">
      <div className="jr-section-head">
        <div>
          <h2>{title}</h2>
          <p>{subtitle}</p>
        </div>
        <div className="jr-sheet-actions">
          <select className="acc-month-select acc-month-select-box" value={month} onChange={(event) => setMonth(event.target.value)}>
            <option value="all">Todos los meses</option>
            {months.map((m) => <option key={m} value={m}>{jrMonthLabel(m)}</option>)}
          </select>
          <button type="button" className="btn btn-ghost btn-sm" onClick={() => jrExportRowsCsv(filename, columns, rows)} disabled={!rows.length}>
            <Icon name="download" size={15} /> CSV
          </button>
        </div>
      </div>
      <div className="jr-sheet-summary">
        {summary.map((item) => (
          <div key={item.label}>
            <span>{item.label}</span>
            <strong>{item.value}</strong>
          </div>
        ))}
      </div>
      {positionRows.length > 0 && <JournalInventoryPosition groups={positionRows} />}
      {statementGroups.length > 0 ? <JournalStatement groups={statementGroups} /> : (
      <div className="jr-sheet-wrap">
        <table className="jr-sheet-table">
          <thead>
            <tr>
              {columns.map((column) => (
                <th key={column.key} className={column.align === "right" ? "num" : ""}>{column.label}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.length ? rows.map((row) => (
              <tr key={row.id}>
                {columns.map((column) => (
                  <td key={column.key} className={column.align === "right" ? "num" : ""}>{row[column.key] || ""}</td>
                ))}
              </tr>
            )) : (
              <tr>
                <td colSpan={columns.length} className="jr-sheet-empty">Sin filas para este periodo.</td>
              </tr>
            )}
          </tbody>
        </table>
      </div>
      )}
    </section>
  );
}

Object.assign(window, { JournalAccounting });
