/* ANISAN — data layer.
   • ANISAN_DATA  → static reference (history, weekday series) — read-only.
   • ANISAN_SEED  → editable entities the store clones and persists (CRUD).
   • helpers: BRL, BRLk, parseBRL, uid, unitTotals. */

window.BRL  = (n) => "R$ " + (Number(n) || 0).toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
window.BRLk = (n) => "R$ " + (Number(n) || 0).toLocaleString("pt-BR", { maximumFractionDigits: 0 });
window.parseBRL = (s) => {
  if (typeof s === "number") return s;
  s = String(s == null ? "" : s).trim().replace(/[R$\s]/g, "");
  if (s === "") return 0;
  if (s.includes(",") && s.includes(".")) s = s.replace(/\./g, "").replace(",", ".");
  else if (s.includes(",")) s = s.replace(",", ".");
  const n = parseFloat(s);
  return isNaN(n) ? 0 : n;
};
window.uid = () => "id" + Math.random().toString(36).slice(2, 9);

const ACCENTS = { "Anisan": "var(--brass)", "Anisanzinho": "var(--jade)", "Anisan Express": "var(--clay)" };

window.ANISAN_DATA = {
  units: ["Anisan", "Anisanzinho", "Anisan Express"],
  accents: ACCENTS,
  // Registro central das abas controláveis por permissão (key → label/ícone).
  // A aba "Usuários" NÃO está aqui: é sempre exclusiva do Master.
  abas: [
    { key: "painel", label: "Painel", icon: "layout-dashboard" },
    { key: "faturamento", label: "Faturamento", icon: "banknote" },
    { key: "variaveis", label: "Despesas variáveis", icon: "shopping-cart" },
    { key: "fixas", label: "Despesas fixas", icon: "repeat" },
    { key: "fluxo", label: "Fluxo de caixa", icon: "trending-up" },
    { key: "dre", label: "DRE", icon: "scale" },
    { key: "historico", label: "Histórico & BI", icon: "line-chart" },
    { key: "metas", label: "Planejado × Realizado", icon: "target" },
    { key: "invest", label: "Investimentos", icon: "landmark" },
    { key: "checklist", label: "Check list", icon: "clipboard-check" },
  ],
  diasSemana: ["Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"],
  semana: {
    "Anisan":         [22000, 28000, 19000, 31000, 44000, 52000, 38000],
    "Anisanzinho":    [9000, 11000, 8500, 12000, 15000, 18500, 13000],
    "Anisan Express": [5000, 6200, 4800, 6800, 8200, 9600, 7000],
  },
  semanaAnterior: {
    "Anisan":         [20000, 24000, 18500, 27000, 39000, 47000, 34000],
    "Anisanzinho":    [8200, 9800, 8000, 10500, 13800, 16800, 12200],
    "Anisan Express": [4200, 5400, 4100, 5900, 6900, 8200, 6100],
  },
  meses: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"],
  faturamentoAnoAnt: [340, 366, 352, 398, 441, 372, 388, 402, 379, 418, 462, 510].map(v => v * 1000),
  contasOpcoes: ["Unicred", "Inter", "Stone", "Cash", "iFood", "Safra", "Reserva"],
  tiposInvest: ["Financiamento", "Empréstimo", "Parcelamento", "Consórcio", "Investimento"],
};

window.ANISAN_SEED = {
  // Usuários do sistema. role "master" vê tudo (inclusive a aba Usuários) e
  // controla as permissões; role "comum" só vê as abas com `abas[key] === true`.
  // (Mapeará para auth.users + RLS no Supabase: papel + abas permitidas.)
  // Usuários do SEED só servem ao modo local de desenvolvimento (sem Supabase).
  // NÃO carregam senha: o LocalAuth usa o ANISAN_DEV_PASSWORD (Auth.js) como
  // fallback. Em produção quem manda é o Supabase Auth (auth.users) + RLS — estes
  // registros são ignorados (a lista de usuarios vem do banco).
  usuarios: [
    { id: "u_master", nome: "Anderson (Master)", email: "anderson@anisan.com.br", role: "master", abas: {}, unidades: {} },
    { id: "u_gerente", nome: "Marina — Gerente", email: "marina@anisan.com.br", role: "comum",
      abas: { painel: true, faturamento: true, variaveis: true, fixas: true, fluxo: true, dre: false, historico: true, metas: true, invest: false },
      unidades: { "Anisan": true, "Anisanzinho": true, "Anisan Express": true } },
    { id: "u_caixa", nome: "João — Caixa", email: "joao@anisan.com.br", role: "comum",
      abas: { painel: true, faturamento: true, variaveis: true, fixas: false, fluxo: false, dre: false, historico: false, metas: false, invest: false },
      unidades: { "Anisan": true, "Anisanzinho": false, "Anisan Express": false } },
  ],
  unidades: {
    "Anisan":         { delivery: 38, atend: 312, ticket: 59.04 },
    "Anisanzinho":    { delivery: 52, atend: 208, ticket: 46.39 },
    "Anisan Express": { delivery: 71, atend: 174, ticket: 35.86 },
  },
  // Receitas (faturamento) lançadas como livro, classificadas pelo plano de
  // receitas (grupo 3). Espelha "compras" das despesas: cada lançamento tem
  // recebido (bool), conta de entrada e data de recebimento.
  receitas: {
    "Anisan": [
      { id: "ra1", name: "Vendas em dinheiro", value: 2100, plano: "3.1.1", prev: "25/06", recebido: true, conta: "Cash", recebimento: "2026-06-25" },
      { id: "ra2", name: "Cartões de débito", value: 3800, plano: "3.1.2", prev: "25/06", recebido: true, conta: "Stone", recebimento: "2026-06-25" },
      { id: "ra3", name: "Cartões de crédito", value: 6200, plano: "3.1.3", prev: "25/06", recebido: true, conta: "Unicred", recebimento: "2026-06-25" },
      { id: "ra4", name: "Cartão refeição", value: 1100, plano: "3.1.5", prev: "25/06", recebido: true, conta: "Stone", recebimento: "2026-06-25" },
      { id: "ra5", name: "Vendas via aplicativo", value: 2820.50, plano: "3.1.7", prev: "26/06", recebido: false, conta: "Unicred", recebimento: "" },
      { id: "ra6", name: "Transferência bancária", value: 1500, plano: "3.1.6", prev: "26/06", recebido: false, conta: "Inter", recebimento: "" },
    ],
    "Anisanzinho": [
      { id: "rb1", name: "Vendas em dinheiro", value: 1500, plano: "3.1.1", prev: "25/06", recebido: true, conta: "Cash", recebimento: "2026-06-25" },
      { id: "rb2", name: "Cartões de crédito", value: 3600, plano: "3.1.3", prev: "25/06", recebido: true, conta: "Unicred", recebimento: "2026-06-25" },
      { id: "rb3", name: "Vendas via aplicativo", value: 850, plano: "3.1.7", prev: "26/06", recebido: false, conta: "Unicred", recebimento: "" },
    ],
    "Anisan Express": [
      { id: "rc1", name: "Vendas via aplicativo", value: 3100.80, plano: "3.1.7", prev: "25/06", recebido: true, conta: "Unicred", recebimento: "2026-06-25" },
      { id: "rc2", name: "Cartões de débito", value: 800, plano: "3.1.2", prev: "26/06", recebido: false, conta: "Stone", recebimento: "" },
    ],
  },
  contas: {
    "Anisan": [
      { id: "a1", nome: "Unicred", s1: 7381.72, s2: 0, aplicacao: 0, reserva: 14992.71, ativo: true },
      { id: "a2", nome: "Stone", s1: 6323.72, s2: 0, aplicacao: 25000.00, reserva: 0, ativo: true },
      { id: "a3", nome: "Inter", s1: 25461.88, s2: 0, aplicacao: 0, reserva: 60000.00, ativo: true },
      { id: "a4", nome: "Cash", s1: 12200.00, s2: 0, aplicacao: 0, reserva: 27200.00, ativo: true },
      { id: "a5", nome: "iFood", s1: 130.45, s2: 0, aplicacao: 0, reserva: 0, ativo: true },
      { id: "a6", nome: "Safra", s1: 5702.23, s2: 0, aplicacao: 0, reserva: 0, ativo: false },
    ],
    "Anisanzinho": [
      { id: "b1", nome: "Unicred", s1: 5200.00, s2: 0, aplicacao: 0, reserva: 0, ativo: true },
      { id: "b2", nome: "Stone", s1: 3400.00, s2: 0, aplicacao: 0, reserva: 0, ativo: true },
      { id: "b3", nome: "Cash", s1: 8800.00, s2: 0, aplicacao: 0, reserva: 24200.00, ativo: true },
      { id: "b4", nome: "Inter", s1: 11000.00, s2: 0, aplicacao: 0, reserva: 30000.00, ativo: true },
    ],
    "Anisan Express": [
      { id: "c1", nome: "Stone", s1: 4250.00, s2: 0, aplicacao: 0, reserva: 0, ativo: true },
      { id: "c2", nome: "Cash", s1: 6000.00, s2: 0, aplicacao: 0, reserva: 7800.00, ativo: true },
      { id: "c3", nome: "iFood", s1: 4000.00, s2: 0, aplicacao: 0, reserva: 14000.00, ativo: true },
    ],
  },
  compras: {
    "Anisan": [
      { id: "cv_a1", name: "Proteínas (Mn Pescados)", value: 3038.10, plano: "4.2.1", venc: "20/06", paid: true, conta: "Inter", pagamento: "2026-06-20" },
      { id: "cv_a2", name: "Bebidas (Heineken)", value: 1064.70, plano: "4.2.3", venc: "22/06", paid: true, conta: "Stone", pagamento: "2026-06-22" },
      { id: "cv_a3", name: "Hortifruti", value: 640.30, plano: "4.2.4", venc: "24/06", paid: true, conta: "Cash", pagamento: "2026-06-24" },
      { id: "cv_a4", name: "Embalagens", value: 480.00, plano: "4.3.1", venc: "27/06", paid: false, conta: "", pagamento: "" },
    ],
    "Anisanzinho": [
      { id: "cv_b1", name: "Proteínas", value: 1820.00, plano: "4.2.1", venc: "20/06", paid: true, conta: "Inter", pagamento: "2026-06-20" },
      { id: "cv_b2", name: "Bebidas", value: 712.40, plano: "4.2.3", venc: "23/06", paid: true, conta: "Stone", pagamento: "2026-06-23" },
      { id: "cv_b3", name: "Hortifruti", value: 380.00, plano: "4.2.4", venc: "28/06", paid: false, conta: "", pagamento: "" },
    ],
    "Anisan Express": [
      { id: "cv_c1", name: "Embalagens delivery", value: 940.00, plano: "4.3.1", venc: "21/06", paid: true, conta: "Cash", pagamento: "2026-06-21" },
      { id: "cv_c2", name: "Taxas iFood", value: 1860.50, plano: "4.1.4", venc: "25/06", paid: true, conta: "iFood", pagamento: "2026-06-25" },
    ],
  },
  metas: {
    realizado: [412000, 438000, 405000, 470000, 521000, 410000, 0, 0, 0, 0, 0, 0],
    planejado: [400000, 420000, 430000, 450000, 500000, 640000, 480000, 500000, 470000, 520000, 560000, 600000],
  },
  metaMesUnidade: { "Anisan": 280000, "Anisanzinho": 150000, "Anisan Express": 95000 },
  // Histórico & BI — overrides manuais por ANO. histManual[ano][unidade][indicador]=[12].
  // Indicador ∈ {fat, atend, delivery, ticket, vert}. Vazio/0 = usa o sistema.
  histManual: {
    "2025": {
      "Anisan":         { fat: [210000, 224000, 218000, 246000, 272000, 230000, 240000, 250000, 236000, 258000, 285000, 312000], atend: [3100, 3260, 3180, 3520, 3880, 3300, 3420, 3560, 3360, 3680, 4020, 4400], delivery: [34, 35, 33, 36, 38, 35, 36, 37, 35, 37, 39, 41], ticket: [], vert: [] },
      "Anisanzinho":    { fat: [92000, 99000, 95000, 108000, 119000, 100000, 105000, 110000, 103000, 113000, 125000, 138000], atend: [1900, 2010, 1950, 2180, 2400, 2050, 2120, 2210, 2080, 2260, 2480, 2720], delivery: [48, 50, 47, 51, 53, 49, 50, 52, 49, 51, 54, 56], ticket: [], vert: [] },
      "Anisan Express": { fat: [38000, 43000, 39000, 44000, 50000, 42000, 43000, 42000, 40000, 47000, 52000, 60000], atend: [1080, 1180, 1090, 1230, 1390, 1170, 1190, 1170, 1110, 1300, 1440, 1660], delivery: [66, 68, 65, 67, 70, 67, 68, 67, 65, 69, 72, 74], ticket: [], vert: [] },
    },
  },
  // Modelos de despesa fixa por unidade (lançados automaticamente todo mês).
  despesasFixas: {
    "Anisan": [
      { id: "fx_a1", nome: "Aluguel e condomínio", plano: "5.2.4", conta: "Unicred", valor: 8500.00, diaVenc: 5, ativo: true },
      { id: "fx_a2", nome: "Energia elétrica", plano: "5.2.3", conta: "Unicred", valor: 10241.75, diaVenc: 18, ativo: true },
      { id: "fx_a3", nome: "Telefone e internet", plano: "5.2.1", conta: "Unicred", valor: 420.00, diaVenc: 10, ativo: true },
      { id: "fx_a4", nome: "Contador", plano: "5.2.11", conta: "Unicred", valor: 1200.00, diaVenc: 15, ativo: true },
      { id: "fx_a5", nome: "Salário de funcionários", plano: "5.3.1", conta: "Unicred", valor: 32000.00, diaVenc: 5, ativo: true },
      { id: "fx_a6", nome: "Mensalidade de softwares", plano: "5.2.12", conta: "Inter", valor: 380.00, diaVenc: 8, ativo: true },
    ],
    "Anisanzinho": [
      { id: "fx_b1", nome: "Aluguel e condomínio", plano: "5.2.4", conta: "Unicred", valor: 4200.00, diaVenc: 5, ativo: true },
      { id: "fx_b2", nome: "Energia elétrica", plano: "5.2.3", conta: "Unicred", valor: 3800.00, diaVenc: 18, ativo: true },
      { id: "fx_b3", nome: "Salário de funcionários", plano: "5.3.1", conta: "Unicred", valor: 14000.00, diaVenc: 5, ativo: true },
    ],
    "Anisan Express": [
      { id: "fx_c1", nome: "Aluguel e condomínio", plano: "5.2.4", conta: "Stone", valor: 2600.00, diaVenc: 5, ativo: true },
      { id: "fx_c2", nome: "Mensalidade de softwares", plano: "5.2.12", conta: "Inter", valor: 240.00, diaVenc: 8, ativo: true },
      { id: "fx_c3", nome: "Salário de funcionários", plano: "5.3.1", conta: "Stone", valor: 8400.00, diaVenc: 5, ativo: true },
    ],
  },
  // Instâncias lançadas por unidade e por mês ("yyyy-mm"). Snapshot imutável:
  // excluir um modelo não mexe nos meses já materializados.
  despesasFixasLancadas: {
    "Anisan": {
      "2026-05": [
        { id: "fxl_a1", fixaId: "fx_a1", name: "Aluguel e condomínio", plano: "5.2.4", conta: "Unicred", value: 8500.00, venc: "05/05", paid: true, pagamento: "2026-05-05" },
        { id: "fxl_a2", fixaId: "fx_a2", name: "Energia elétrica", plano: "5.2.3", conta: "Unicred", value: 9870.40, venc: "18/05", paid: true, pagamento: "2026-05-18" },
        { id: "fxl_a3", fixaId: "fx_a3", name: "Telefone e internet", plano: "5.2.1", conta: "Unicred", value: 420.00, venc: "10/05", paid: true, pagamento: "2026-05-10" },
        { id: "fxl_a4", fixaId: "fx_a4", name: "Contador", plano: "5.2.11", conta: "Unicred", value: 1200.00, venc: "15/05", paid: true, pagamento: "2026-05-15" },
        { id: "fxl_a5", fixaId: "fx_a5", name: "Salário de funcionários", plano: "5.3.1", conta: "Unicred", value: 31200.00, venc: "05/05", paid: true, pagamento: "2026-05-06" },
        { id: "fxl_a6", fixaId: "fx_a6", name: "Mensalidade de softwares", plano: "5.2.12", conta: "Inter", value: 380.00, venc: "08/05", paid: true, pagamento: "2026-05-08" },
      ],
    },
    "Anisanzinho": {},
    "Anisan Express": {},
  },
  despesasCadastradas: [
    { id: "d1", nome: "Pescados/Frutos do mar", plano: "4.2.1", conta: "Inter" },
    { id: "d2", nome: "Bebidas", plano: "4.2.3", conta: "Unicred" },
    { id: "d3", nome: "Insumos", plano: "4.2.2", conta: "Cash" },
    { id: "d4", nome: "Hortifruti", plano: "4.2.4", conta: "Cash" },
    { id: "d5", nome: "Compras CMV", plano: "4.2.5", conta: "Cash" },
    { id: "d6", nome: "Taxas de cartões", plano: "4.1.2", conta: "Stone" },
    { id: "d7", nome: "Taxas via aplicativo", plano: "4.1.4", conta: "iFood" },
    { id: "d8", nome: "Simples nacional - Icms", plano: "4.1.1", conta: "Inter" },
    { id: "d9", nome: "Custos com embalagens", plano: "4.3.1", conta: "Cash" },
    { id: "d10", nome: "Gás", plano: "4.6.1", conta: "Cash" },
    { id: "d11", nome: "Energia elétrica", plano: "5.2.3", conta: "Unicred" },
    { id: "d12", nome: "Água", plano: "5.2.5", conta: "Cash" },
    { id: "d13", nome: "Aluguel e condomínio", plano: "5.2.4", conta: "Unicred" },
    { id: "d14", nome: "Telefone e internet", plano: "5.2.1", conta: "Unicred" },
    { id: "d15", nome: "Contador", plano: "5.2.11", conta: "Unicred" },
    { id: "d16", nome: "Mensalidade de softwares", plano: "5.2.12", conta: "Unicred" },
    { id: "d17", nome: "Salário de funcionários", plano: "5.3.1", conta: "Unicred" },
    { id: "d18", nome: "FGTS", plano: "5.3.5", conta: "Inter" },
    { id: "d19", nome: "INSS - Federação - Sindicato - IR", plano: "5.3.6", conta: "Inter" },
    { id: "d20", nome: "Gasolina / Combustível", plano: "5.5.1", conta: "Cash" },
    { id: "d21", nome: "Mídias / Propaganda", plano: "6.1.3", conta: "Cash" },
    { id: "d22", nome: "Pagamento de empréstimos", plano: "7.2.1", conta: "Unicred" },
  ],
  investimentos: [
    { id: "i1", nome: "Financiamento — reforma salão", tipo: "Financiamento", banco: "Unicred", saldo: 142000.00, parcela: 4180.00, restantes: 36, juros: 1.18, prox: "10/07", c: "var(--brass)", aplicacao: false },
    { id: "i2", nome: "Empréstimo capital de giro", tipo: "Empréstimo", banco: "Inter", saldo: 68500.00, parcela: 3920.00, restantes: 18, juros: 1.42, prox: "05/07", c: "var(--clay)", aplicacao: false },
    { id: "i3", nome: "Equipamentos de cozinha", tipo: "Parcelamento", banco: "Stone", saldo: 23400.00, parcela: 1950.00, restantes: 12, juros: 0.00, prox: "20/06", c: "var(--slate)", aplicacao: false },
    { id: "i4", nome: "Frota delivery (3 motos)", tipo: "Consórcio", banco: "Safra", saldo: 31200.00, parcela: 1300.00, restantes: 24, juros: 0.00, prox: "15/07", c: "var(--sand)", aplicacao: false },
    { id: "i5", nome: "CDB reserva de emergência", tipo: "Investimento", banco: "Inter", saldo: 96000.00, parcela: 0, restantes: 0, juros: 0.92, prox: "—", c: "var(--jade)", aplicacao: true },
  ],
};

/* Soma de uma conta = Saldo 1 + Saldo 2 + Aplicação + Reserva. */
window.contaSoma = (c) => (Number(c.s1) || 0) + (Number(c.s2) || 0) + (Number(c.aplicacao) || 0) + (Number(c.reserva) || 0);

/* ---- Saldos por conta REGISTRADOS POR DIA ----
   data.contaSaldos[unidade][contaId][yyyy-mm-dd] = {s1,s2,aplicacao,reserva}.
   Resolução de um dia: usa o snapshot exato; senão repete o último dia <= alvo
   (carry-forward); senão cai nos valores-base da conta. */
window.contaDiaVals = (data, u, conta, dayISO) => {
  const snaps = (((data.contaSaldos || {})[u] || {})[conta.id]) || {};
  let bestK = null;
  for (const k in snaps) { if (k <= dayISO && (bestK === null || k > bestK)) bestK = k; }
  if (bestK !== null) return snaps[bestK];
  return { s1: Number(conta.s1) || 0, s2: Number(conta.s2) || 0, aplicacao: Number(conta.aplicacao) || 0, reserva: Number(conta.reserva) || 0 };
};
/* Conta enriquecida com os valores do dia (para a UI do Painel). */
window.contaComDia = (data, u, conta, dayISO) => ({ ...conta, ...window.contaDiaVals(data, u, conta, dayISO) });
/* Grava um campo do saldo no snapshot do dia (semeado por carry-forward). */
window.setContaDia = (d, u, contaId, dayISO, patch) => {
  if (!d.contaSaldos) d.contaSaldos = {};
  if (!d.contaSaldos[u]) d.contaSaldos[u] = {};
  if (!d.contaSaldos[u][contaId]) d.contaSaldos[u][contaId] = {};
  const conta = (d.contas[u] || []).find(x => x.id === contaId) || {};
  const base = d.contaSaldos[u][contaId][dayISO] || window.contaDiaVals(d, u, conta, dayISO);
  d.contaSaldos[u][contaId][dayISO] = { ...base, ...patch };
};

/* Migra contas antigas (saldo/bucket) para o novo formato de 4 campos. */
window.normalizeConta = (c) => {
  if (c.s1 === undefined && c.saldo !== undefined) {
    const v = Number(c.saldo) || 0;
    if (c.bucket === "reserva") { c.reserva = v; c.s1 = 0; }
    else { c.s1 = v; c.reserva = 0; }
    c.s2 = 0; c.aplicacao = 0;
    delete c.saldo; delete c.bucket;
  }
  if (c.s1 === undefined) c.s1 = 0;
  if (c.s2 === undefined) c.s2 = 0;
  if (c.aplicacao === undefined) c.aplicacao = 0;
  if (c.reserva === undefined) c.reserva = 0;
  return c;
};
window.normalizeContas = (data) => {
  if (!data || !data.contas) return;
  Object.keys(data.contas).forEach(u => (data.contas[u] || []).forEach(window.normalizeConta));
};

window.unitTotals = (data, unit, dayISO) => {
  const day = dayISO || window.todayISO();
  const contas = (data.contas[unit] || []).filter(c => c.ativo).map(c => window.contaComDia(data, unit, c, day));
  // Capital de giro = Saldo 1 + Saldo 2; Reserva = Aplicação + Reserva.
  const giro = contas.reduce((s, c) => s + (Number(c.s1) || 0) + (Number(c.s2) || 0), 0);
  const reserva = contas.reduce((s, c) => s + (Number(c.aplicacao) || 0) + (Number(c.reserva) || 0), 0);
  return { giro, reserva, total: giro + reserva };
};

/* responsive viewport hook — true on phone-width screens (<= bp px, default 760). */
window.useIsMobile = function (bp) {
  bp = bp || 760;
  const get = () => (typeof window !== "undefined" ? window.innerWidth <= bp : false);
  const [m, setM] = React.useState(get);
  React.useEffect(() => {
    const on = () => setM(get());
    window.addEventListener("resize", on);
    on();
    return () => window.removeEventListener("resize", on);
  }, [bp]);
  return m;
};

window.CONSOLIDADO = "Consolidado";
window.isCons = (unit) => unit === window.CONSOLIDADO;

/* ---- Check list REGISTRADO POR DIA ----
   Base estável: data.checklist[u][id] (config/última contagem). Overrides por dia:
   data.checklistDia[u][yyyy-mm-dd][id]. Resolução: snapshot exato do dia; senão
   o último dia <= alvo (carry-forward); senão a base. Igual aos saldos do Painel. */
window.clDiaVals = (data, u, id, dia) => {
  const dias = ((data.checklistDia || {})[u] || {});
  let bestK = null;
  for (const k in dias) { if (k <= dia && dias[k][id] && (bestK === null || k > bestK)) bestK = k; }
  if (bestK !== null) return dias[bestK][id];
  return ((data.checklist || {})[u] || {})[id] || { estoque: 0, cozinha: 0, ref: 0, total: 0 };
};
window.setClDia = (d, u, dia, id, patch) => {
  if (!d.checklistDia) d.checklistDia = {};
  if (!d.checklistDia[u]) d.checklistDia[u] = {};
  if (!d.checklistDia[u][dia]) d.checklistDia[u][dia] = {};
  const base = d.checklistDia[u][dia][id] || window.clDiaVals(d, u, id, dia);
  d.checklistDia[u][dia][id] = { ...base, ...patch };
};

/* ---- usuários & permissões de abas ---- */
// usuário por id (fallback: primeiro master, senão primeiro da lista).
window.getUsuario = (data, id) => {
  const us = (data.usuarios || []);
  return us.find((u) => u.id === id) || us.find((u) => u.role === "master") || us[0] || null;
};
// Master vê todas as abas; comum vê só as habilitadas (abas[key] === true).
window.podeVerAba = (user, key) => !user ? false : (user.role === "master" ? true : !!(user.abas && user.abas[key]));
// Abas visíveis (na ordem do registro) para um usuário.
window.abasVisiveis = (user) => (window.ANISAN_DATA.abas || []).filter((a) => window.podeVerAba(user, a.key));
// Master acessa todas as unidades; comum só as habilitadas (unidades[u] === true).
window.podeVerUnidade = (user, u) => !user ? false : (user.role === "master" ? true : !!(user.unidades && user.unidades[u]));
// Unidades visíveis (na ordem oficial) para um usuário.
window.unidadesVisiveis = (user) => (window.ANISAN_DATA.units || []).filter((u) => window.podeVerUnidade(user, u));
// Pode ver Consolidado? Só quando tem acesso a TODAS as unidades (evita vazar dados de unidade não permitida na soma).
window.podeVerConsolidado = (user) => !user ? false : (user.role === "master" ? true : window.unidadesVisiveis(user).length === (window.ANISAN_DATA.units || []).length);
// Unidade inicial coerente: Consolidado se permitido, senão a primeira unidade visível.
window.unidadePadrao = (user) => window.podeVerConsolidado(user) ? window.CONSOLIDADO : (window.unidadesVisiveis(user)[0] || window.CONSOLIDADO);

/* aggregate balances across every unit (no dia informado, ou hoje) */
window.consTotals = (data, dayISO) => window.ANISAN_DATA.units.reduce((acc, u) => {
  const t = window.unitTotals(data, u, dayISO);
  acc.giro += t.giro; acc.reserva += t.reserva; acc.total += t.total; return acc;
}, { giro: 0, reserva: 0, total: 0 });

/* receitas (livro de faturamento) achatadas com a unidade de origem */
window.allReceitas = (data) => window.ANISAN_DATA.units.flatMap(u => ((data.receitas || {})[u] || []).map(r => ({ ...r, _u: u })));

/* faturamento mensal REAL (recebido) de uma unidade (ou grupo) no ano de
   trabalho — array de 12 posições (Jan..Dez), derivado do livro de receitas. */
window.fatMensalReal = (data, unit) => {
  const out = Array(12).fill(0);
  const units = window.isCons(unit) ? window.ANISAN_DATA.units : [unit];
  units.forEach((u) => {
    ((data.receitas || {})[u] || []).forEach((r) => {
      if (!r.recebido) return;
      const iso = window.ddmmToISO(r.recebimento || r.prev);
      if (!iso || iso.slice(0, 4) !== String(window.YEAR)) return;
      const m = parseInt(iso.slice(5, 7), 10) - 1;
      if (m >= 0 && m < 12) out[m] += Number(r.value) || 0;
    });
  });
  return out;
};

/* faturamento recebido no ano (real) por unidade — para o ranking. */
window.fatAnoReal = (data, unit) => window.fatMensalReal(data, unit).reduce((a, b) => a + b, 0);

/* ──────────────────────────────────────────────────────────────────────────
   HISTÓRICO & BI — modelo de OVERRIDE manual por ANO (Histórico.jsx).
   data.histManual[ano][unidade][indicador] = array esparso de 12 (mês).
   indicador ∈ {fat, atend, delivery, ticket, vert}.
   Regra: célula preenchida (≠0) → vale o valor do usuário; vazia/0 → vale o
   valor do SISTEMA (derivado) — para qualquer ano.
   ────────────────────────────────────────────────────────────────────────── */
window.HIST_INDS = ["fat", "atend", "delivery", "ticket", "vert"];
window.histOv = (data, ano, unit, ind) => ((((data.histManual || {})[String(ano)] || {})[unit] || {})[ind]) || [];

/* valor do SISTEMA (12 meses) para UMA unidade (não-consolidado). */
window.histSys = (data, ano, unit, ind) => {
  const Y = Number(ano) === window.YEAR;
  if (ind === "fat") return Y ? window.fatMensalReal(data, unit) : Array(12).fill(0);
  if (ind === "vert") {
    if (!Y || !window.dreMensal) return Array(12).fill(0);
    const m = window.dreMensal(data, unit, Number(ano));
    return m.receita.map((r, i) => (r > 0 ? (m.operacional[i] / r) * 100 : 0));
  }
  return Array(12).fill(0); // atend, delivery: sem fonte automática; ticket é derivado
};

/* série EFETIVA (override-ou-sistema) de UMA unidade, todos os indicadores. */
window.histUnit = (data, ano, unit) => {
  const pick = (ind) => { const o = window.histOv(data, ano, unit, ind), s = window.histSys(data, ano, unit, ind); return s.map((sv, i) => { const m = Number(o[i]) || 0; return m !== 0 ? m : sv; }); };
  const fat = pick("fat"), atend = pick("atend"), delivery = pick("delivery"), vert = pick("vert");
  const ovT = window.histOv(data, ano, unit, "ticket");
  const ticket = fat.map((f, i) => { const m = Number(ovT[i]) || 0; if (m !== 0) return m; return atend[i] > 0 ? f / atend[i] : 0; });
  return { fat, atend, delivery, ticket, vert };
};

/* série EFETIVA com agregação para Consolidado. */
window.histSerie = (data, ano, unit) => {
  if (!window.isCons(unit)) return window.histUnit(data, ano, unit);
  const per = window.ANISAN_DATA.units.map((u) => window.histUnit(data, ano, u));
  const sum = (k) => per.reduce((acc, p) => acc.map((v, i) => v + p[k][i]), Array(12).fill(0));
  const fat = sum("fat"), atend = sum("atend");
  const delivery = Array(12).fill(0).map((_, i) => {
    const tw = per.reduce((s, p) => s + p.atend[i], 0);
    if (tw > 0) return per.reduce((s, p) => s + p.delivery[i] * p.atend[i], 0) / tw;
    const nz = per.map((p) => p.delivery[i]).filter((x) => x > 0);
    return nz.length ? nz.reduce((a, b) => a + b, 0) / nz.length : 0;
  });
  const ticket = fat.map((f, i) => (atend[i] > 0 ? f / atend[i] : 0));
  const vert = window.histSys(data, ano, window.CONSOLIDADO, "vert");
  return { fat, atend, delivery, ticket, vert };
};

/* true se a unidade tem QUALQUER override manual no ano (p/ rótulo "manual"). */
window.histTemManual = (data, ano, unit) => {
  const units = window.isCons(unit) ? window.ANISAN_DATA.units : [unit];
  return units.some((u) => window.HIST_INDS.some((ind) => window.histOv(data, ano, u, ind).some((x) => Number(x) > 0)));
};

/* ---- DRE: receitas RECEBIDAS e despesas PAGAS de um ano, por mês ----
   Retorna { rec:[{plano,value,m}], desp:[{plano,value,m}] } com m = 0..11.
   Inclui compras avulsas + despesas fixas lançadas, todas as pagas. */
window.dreEntries = (data, unit, year) => {
  const units = window.isCons(unit) ? window.ANISAN_DATA.units : [unit];
  const rec = [], desp = [];
  const Y = String(year);
  const monthOf = (v) => { const iso = window.ddmmToISO(v) || String(v || ""); return iso.slice(0, 4) === Y ? (parseInt(iso.slice(5, 7), 10) - 1) : -1; };
  units.forEach((u) => {
    ((data.receitas || {})[u] || []).forEach((r) => { if (r.recebido && r.recebimento) { const m = monthOf(r.recebimento); if (m >= 0) rec.push({ plano: r.plano || "", value: Number(r.value) || 0, m }); } });
    ((data.compras || {})[u] || []).forEach((r) => { if (r.paid && r.pagamento) { const m = monthOf(r.pagamento); if (m >= 0) desp.push({ plano: r.plano || "", value: Number(r.value) || 0, m }); } });
    const fx = (data.despesasFixasLancadas || {})[u] || {};
    Object.keys(fx).forEach((mk) => (fx[mk] || []).forEach((r) => { if (r.paid && r.pagamento) { const m = monthOf(r.pagamento); if (m >= 0) desp.push({ plano: r.plano || "", value: Number(r.value) || 0, m }); } }));
  });
  return { rec, desp };
};

/* {plano-folha: [12 meses]} a partir de uma lista de lançamentos */
window.dreLeafMonths = (entries) => {
  const m = {};
  entries.forEach((e) => { const k = e.plano || "—"; (m[k] = m[k] || new Array(12).fill(0))[e.m] += e.value; });
  return m;
};

/* faturamento de uma unidade = soma das receitas RECEBIDAS do livro */
window.unitFat = (data, unit) => {
  if (window.isCons(unit)) return window.ANISAN_DATA.units.reduce((s, u) => s + window.unitFat(data, u), 0);
  return ((data.receitas || {})[unit] || []).filter(r => r.recebido).reduce((s, r) => s + (Number(r.value) || 0), 0);
};

/* faturamento de uma unidade NUM DIA (ISO) = receitas com previsão naquele dia */
window.unitFatDia = (data, unit, dayISO) => {
  if (window.isCons(unit)) return window.ANISAN_DATA.units.reduce((s, u) => s + window.unitFatDia(data, u, dayISO), 0);
  return ((data.receitas || {})[unit] || []).filter(r => window.ddmmToISO(r.prev) === dayISO).reduce((s, r) => s + (Number(r.value) || 0), 0);
};

/* flatten contas/compras across units, tagging each with its owning unit (_u) */
window.allContas = (data) => window.ANISAN_DATA.units.flatMap(u => (data.contas[u] || []).map(c => ({ ...c, _u: u })));
window.allCompras = (data) => window.ANISAN_DATA.units.flatMap(u => (data.compras[u] || []).map(r => ({ ...r, _u: u })));

/* element-wise sum of every unit's weekday faturamento series */
window.consSemana = (D) => D.units.reduce((acc, u) => D.semana[u].map((v, i) => v + acc[i]), [0, 0, 0, 0, 0, 0, 0]);

/* Faturamento REAL por dia da semana (Seg..Dom) a partir do livro de receitas
   RECEBIDAS. offsetWeeks=0 → semana atual; -1 → semana anterior. Consolidado
   soma as unidades. Usado no gráfico "Faturamento" do Painel. */
window.fatSemanaReal = (data, unit, offsetWeeks) => {
  const out = [0, 0, 0, 0, 0, 0, 0];
  const today = new Date(window.todayISO() + "T00:00:00");
  const monday = new Date(today); monday.setDate(today.getDate() - ((today.getDay() + 6) % 7) + (offsetWeeks || 0) * 7);
  const start = window.todayISO ? (monday.getFullYear() + "-" + String(monday.getMonth() + 1).padStart(2, "0") + "-" + String(monday.getDate()).padStart(2, "0")) : "";
  const end = window.addDaysISO(start, 6);
  const units = window.isCons(unit) ? window.ANISAN_DATA.units : [unit];
  units.forEach((u) => {
    ((data.receitas || {})[u] || []).forEach((r) => {
      if (!r.recebido) return;
      const iso = window.ddmmToISO(r.recebimento || r.prev);
      if (!iso || iso < start || iso > end) return;
      const di = (new Date(iso + "T00:00:00").getDay() + 6) % 7; // 0=Seg
      out[di] += Number(r.value) || 0;
    });
  });
  return out;
};

/* ---- despesas fixas (lançamento automático mensal) ---- */
window.monthKey = (d) => { d = d || new Date(); return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0"); };
window.monthNomes = ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"];
window.monthLabel = (mk) => { const p = String(mk).split("-"); return (window.monthNomes[parseInt(p[1], 10) - 1] || p[1]) + " / " + p[0]; };
window.monthShift = (mk, delta) => {
  const p = String(mk).split("-"); let y = parseInt(p[0], 10), m = parseInt(p[1], 10) - 1 + delta;
  y += Math.floor(m / 12); m = ((m % 12) + 12) % 12;
  return y + "-" + String(m + 1).padStart(2, "0");
};

/* Garante que os modelos ativos estejam lançados no mês informado (para a
   unidade ou todas). Não recria instâncias já existentes (idempotente), então
   meses anteriores ficam intactos. Retorna true se algo foi criado. */
window.ensureFixasMes = (data, mk) => {
  if (!data.despesasFixas) return false;
  if (!data.despesasFixasLancadas) data.despesasFixasLancadas = {};
  const mm = String(mk).split("-")[1];
  let changed = false;
  window.ANISAN_DATA.units.forEach((u) => {
    if (!data.despesasFixasLancadas[u]) data.despesasFixasLancadas[u] = {};
    if (!data.despesasFixasLancadas[u][mk]) data.despesasFixasLancadas[u][mk] = [];
    const arr = data.despesasFixasLancadas[u][mk];
    (data.despesasFixas[u] || []).filter(f => f.ativo).forEach((f) => {
      if (!arr.some(x => x.fixaId === f.id)) {
        arr.push({ id: window.uid(), fixaId: f.id, name: f.nome, plano: f.plano, conta: "", value: Number(f.valor) || 0, venc: String(f.diaVenc).padStart(2, "0") + "/" + mm, paid: false, pagamento: "" });
        changed = true;
      }
    });
  });
  return changed;
};

/* Lança automaticamente o mês corrente E todos os meses passados que já têm
   modelos mas ainda não foram materializados (a partir do mês mais antigo com
   instâncias). Usado no carregamento. */
window.autoLancarFixas = (data) => {
  const atual = window.monthKey();
  let changed = window.ensureFixasMes(data, atual);
  return changed;
};

window.fixasDoMes = (data, unit, mk) => {
  const cons = window.isCons(unit);
  if (cons) return window.ANISAN_DATA.units.flatMap(u => ((data.despesasFixasLancadas[u] || {})[mk] || []).map(x => ({ ...x, _u: u })));
  return ((data.despesasFixasLancadas[unit] || {})[mk] || []).map(x => ({ ...x, _u: unit }));
};

/* Despesas fixas PENDENTES num intervalo [fromISO, toISO]. Usa as instâncias
   reais já lançadas (respeitando o status pago / snapshot do mês); para meses
   ainda não materializados, deriva instâncias VIRTUAIS dos modelos ativos.
   Cada item leva fixa:true e (quando aplicável) virtual:true + fixaId. */
window.fixasPendentesRange = (data, unit, fromISO, toISO) => {
  if (!data.despesasFixas) return [];
  const units = window.isCons(unit) ? window.ANISAN_DATA.units : [unit];
  const months = [];
  let cur = String(fromISO).slice(0, 7); const end = String(toISO).slice(0, 7);
  while (cur <= end) { months.push(cur); cur = window.monthShift(cur, 1); }
  const out = [];
  units.forEach((u) => {
    months.forEach((mk) => {
      const mm = mk.split("-")[1];
      const inst = (data.despesasFixasLancadas[u] || {})[mk];
      if (inst) {
        inst.forEach(x => { if (!x.paid) out.push({ ...x, _u: u, fixa: true }); });
      } else {
        (data.despesasFixas[u] || []).filter(f => f.ativo).forEach((f) => {
          out.push({ id: "v_" + u + "_" + mk + "_" + f.id, fixaId: f.id, name: f.nome, plano: f.plano, conta: "", value: Number(f.valor) || 0, venc: String(f.diaVenc).padStart(2, "0") + "/" + mm, paid: false, _u: u, fixa: true, virtual: true });
        });
      }
    });
  });
  return out;
};

/* ---- period (date-range) filtering ---- */
window.YEAR = 2026;
window.defaultPeriod = () => ({ from: "2026-01-01", to: "2026-06-30" });

/* Normaliza uma data para ISO "yyyy-mm-dd". Aceita ISO, "DD/MM" e
   "DD/MM/AAAA"; undated/"—"/vazio retorna null. */
window.ddmmToISO = (v) => {
  const s = String(v || "").trim();
  if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s;
  let m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec(s);
  if (m) return m[3] + "-" + m[2].padStart(2, "0") + "-" + m[1].padStart(2, "0");
  m = /^(\d{1,2})\/(\d{1,2})$/.exec(s);
  if (m) return window.YEAR + "-" + m[2].padStart(2, "0") + "-" + m[1].padStart(2, "0");
  return null;
};

/* ISO de hoje (data-local), para pré-preencher o dia do pagamento. */
window.todayISO = () => {
  const d = new Date();
  return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
};

/* Soma n dias a uma data ISO, retornando ISO. */
window.addDaysISO = (iso, n) => {
  const d = new Date(String(iso) + "T00:00:00");
  d.setDate(d.getDate() + n);
  return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
};

/* Exibe uma data (ISO ou DD/MM) como "DD/MM/AAAA"; vazio → "—". */
window.dateLabel = (v) => {
  const iso = window.ddmmToISO(v);
  if (!iso) return "—";
  const p = iso.split("-");
  return p[2] + "/" + p[1] + "/" + p[0];
};

/* is a DD/MM date within the period? undated records are kept (true). */
window.dateInRange = (ddmm, period) => {
  if (!period || !period.from || !period.to) return true;
  const iso = window.ddmmToISO(ddmm);
  if (!iso) return true;
  return iso >= period.from && iso <= period.to;
};

