/* global React, window */
// Ruticomidas — additional screens (config + remaining product screens).
const { useState, useEffect, useMemo, useCallback } = React;
const D = window.RC_DATA;

// ---- Reusable masthead (duplicated to keep file independent) ----
function MH({ kicker, title, deck, stats, actions }) {
  return (
    <header className="masthead">
      <div>
        {kicker && <div className="kicker">{kicker}</div>}
        <h1 className="t-display-2">{title}</h1>
        {deck && <div className="deck">{deck}</div>}
        {actions && <div className="row" style={{ marginTop: 18, flexWrap: 'wrap' }}>{actions}</div>}
      </div>
      {stats && <div className="masthead-stats">{stats}</div>}
    </header>
  );
}
function Stat({ label, value, delta, down }) {
  return (
    <div className="stat">
      <div className="label">{label}</div>
      <div className="value">{value}</div>
      {delta && <div className={'delta' + (down ? ' down' : '')}>{delta}</div>}
    </div>
  );
}
function SH({ title, sub, right }) {
  return (
    <div className="section-head">
      <div>
        <h2 className="t-headline-l">{title}</h2>
        {sub && <div className="sub" style={{ marginTop: 6 }}>{sub}</div>}
      </div>
      {right}
    </div>
  );
}

// =========================================================
// PLANTILLA — default weekly template
// =========================================================
function PlantillaScreen() {
  const { recipes } = window.RC_STORE.useStore();
  const MEAL_SLOTS = D.MEAL_SLOTS;
  const DAYS_ES = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'];

  const [template, setTemplate] = useState(null);
  const [loading, setLoading] = useState(true);
  const [applying, setApplying] = useState(false);

  const loadTemplate = useCallback(async () => {
    setLoading(true);
    try {
      const r = await window.api('/api/template');
      setTemplate(r);
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setLoading(false); }
  }, []);

  useEffect(() => { loadTemplate(); }, []);

  const deleteTemplateRecipe = async (trid) => {
    try {
      await window.api(`/api/template/recipes/${trid}`, 'DELETE');
      await loadTemplate();
    } catch (e) { window.showToast(e.message, 'error'); }
  };

  const openAddSlot = (dayIdx, mealType, dayLabel, slotLabel) => {
    window.rcOpen('template-slot', { dayIdx, mealType, dayLabel, slotLabel, onSave: loadTemplate });
  };

  const applyTemplate = async (onlyEmpty) => {
    setApplying(true);
    try {
      await window.api(`/api/template/apply${onlyEmpty ? '?only_empty=true' : ''}`, 'POST', {});
      window.showToast('Plantilla aplicada al planning de esta semana');
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setApplying(false); }
  };

  const recipeMap = useMemo(() => {
    const m = {};
    (recipes || []).forEach(r => { m[r.id] = r; });
    return m;
  }, [recipes]);

  const filledSlots = (template || []).reduce((s, d) =>
    s + Object.values(d.meals || {}).filter(m => (m.recipes || []).length > 0).length, 0);

  return (
    <>
      <MH
        kicker="01 · Comidas / Plantilla"
        title="Plantilla semanal"
        deck="La semana por defecto del hogar · aplicable a cualquier semana"
        stats={<Stat label="Slots definidos" value={filledSlots} delta={`DE ${7 * MEAL_SLOTS.length}`} />}
        actions={
          <>
            <button className="btn btn--primary" disabled={applying} onClick={() => applyTemplate(false)}>
              {applying ? '…' : 'Aplicar a esta semana'}
            </button>
            <button className="btn btn--ghost btn--sm" disabled={applying} onClick={() => applyTemplate(true)}>
              Solo huecos vacíos
            </button>
          </>
        }
      />
      <div className="screen">
        {loading && <div className="t-body dim">Cargando plantilla…</div>}
        {!loading && (
          <div className="planning-grid">
            <div></div>
            {DAYS_ES.map(d => (
              <div key={d} className="plan-day-head">
                <div className="dow">{d.slice(0, 3)}</div>
              </div>
            ))}
            {MEAL_SLOTS.map(slot => (
              <React.Fragment key={slot.id}>
                <div className="plan-row-head">{slot.label}</div>
                {(template || []).map((day) => {
                  const m = day.meals?.[slot.id] || { recipes: [] };
                  const recipesList = m.recipes || [];
                  const hasRecipes = recipesList.length > 0;
                  const tone = hasRecipes ? (recipeMap[recipesList[0]?.recipe_id]?.tone || 'mint') : null;
                  return (
                    <div key={day.day_index} className={'plan-cell' + (hasRecipes ? ' tone-' + tone : ' empty')} style={{ position: 'relative' }}>
                      {recipesList.map(r => (
                        <div key={r.id} style={{ position: 'relative', marginBottom: 4 }}>
                          <div className="meal-tag">{r.raciones} rac</div>
                          <div className="meal-title">{r.recipe_name}</div>
                          <button
                            style={{ position: 'absolute', top: 0, right: 0, background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: 'inherit', opacity: 0.6, lineHeight: 1 }}
                            onClick={() => deleteTemplateRecipe(r.id)}
                            title="Quitar">×</button>
                        </div>
                      ))}
                      <button
                        className="btn btn--ghost btn--sm"
                        style={{ width: '100%', marginTop: hasRecipes ? 4 : 0, fontSize: 11, color: 'var(--text-meta)', border: '1px dashed var(--frame)' }}
                        onClick={() => openAddSlot(day.day_index, slot.id, DAYS_ES[day.day_index] || '', slot.label)}>
                        + Receta
                      </button>
                    </div>
                  );
                })}
              </React.Fragment>
            ))}
          </div>
        )}
        {!loading && (
          <div className="card card--block card--mint" style={{ marginTop: 24 }}>
            <div className="t-mono-label invert">CÓMO FUNCIONA</div>
            <div className="t-body invert" style={{ marginTop: 8 }}>
              Define una semana tipo. Con "Aplicar a esta semana" se rellena el planning actual con estas recetas. "Solo huecos vacíos" no sobreescribe lo que ya tienes planificado.
            </div>
          </div>
        )}
      </div>
    </>
  );
}

// =========================================================
// ARTÍCULOS — inventory
// =========================================================
function ArticulosScreen() {
  const store = window.RC_STORE.useStore();
  const { ingredients, reload } = store;
  const [filter, setFilter] = useState('Todos');
  const [search, setSearch] = useState('');
  const [editingStockId, setEditingStockId] = useState(null);
  const [editingStockVal, setEditingStockVal] = useState('');
  const filters = ['Todos', 'Bajo mínimo', 'Marcados', 'Mercadona', 'Lidl', 'Carrefour'];

  const quickStock = async (a, delta) => {
    try {
      await window.api(`/api/ingredients/${a.id}/stock`, 'PATCH', { stock: (a.stock || 0) + delta });
      await reload.ingredients();
    } catch (e) { window.showToast(e.message, 'error'); }
  };

  const startEditStock = (e, a) => {
    e.stopPropagation();
    setEditingStockId(a.id);
    setEditingStockVal(String(a.stock ?? 0));
  };

  const commitEditStock = async (a) => {
    const val = parseFloat(editingStockVal);
    if (!isNaN(val) && val !== a.stock) {
      try {
        await window.api(`/api/ingredients/${a.id}/stock`, 'PATCH', { stock: val });
        await reload.ingredients();
      } catch (e) { window.showToast(e.message, 'error'); }
    }
    setEditingStockId(null);
  };

  const toggleManualShop = async (a) => {
    if (a.manual_shop) {
      try {
        await window.api(`/api/ingredients/${a.id}/manual-shop`, 'PATCH', { manual_shop: false, extra_packs: 0 });
        await reload.ingredients();
      } catch (e) { window.showToast(e.message, 'error'); }
    } else {
      window.rcOpen('qty', {
        title: `¿Cuántos envases de ${a.name}?`,
        defaultValue: 1,
        onConfirm: async (qty) => {
          try {
            await window.api(`/api/ingredients/${a.id}/manual-shop`, 'PATCH', { manual_shop: true, extra_packs: Math.max(0, qty - 1) });
            await reload.ingredients();
          } catch (e) { window.showToast(e.message, 'error'); }
        },
      });
    }
  };

  const deleteArticle = async (aid) => {
    try {
      await window.api(`/api/ingredients/${aid}`, 'DELETE');
      await reload.ingredients();
      window.showToast('Artículo eliminado');
    } catch (e) { window.showToast(e.message, 'error'); }
  };

  const data = (ingredients || []).filter(a => {
    if (search && !(a.name || '').toLowerCase().includes(search.toLowerCase())) return false;
    if (filter === 'Todos') return true;
    if (filter === 'Bajo mínimo') return a.stock < a.min_stock;
    if (filter === 'Marcados') return a.manual_shop;
    return (a.supermarket || '').toLowerCase() === filter.toLowerCase();
  });

  const lowCount = (ingredients || []).filter(a => a.stock < a.min_stock).length;
  const markedCount = (ingredients || []).filter(a => a.manual_shop).length;

  const openArticle = (a) => window.rcOpen('article', { article: a, onSave: reload.ingredients });

  return (
    <>
      <MH
        kicker="01 · Comidas / Artículos"
        title="Inventario"
        deck={`${(ingredients || []).length} artículos · ${lowCount} bajo mínimo`}
        stats={
          <>
            <Stat label="Artículos" value={(ingredients || []).length} />
            <Stat label="Bajo mínimo" value={lowCount} delta={lowCount > 0 ? `${lowCount} → COMPRA` : null} />
            <Stat label="Marcados" value={markedCount} />
          </>
        }
        actions={<><button className="btn btn--primary" onClick={() => openArticle({ id: null, name: '', supermarket: 'Mercadona', unit: 'unidad', envase_qty: 1, stock: 0, min_stock: 1, manual_shop: false, obs: '' })}>+ Nuevo artículo</button></>}
      />
      <div className="screen">
        <div className="recipe-filters">
          {filters.map(f => (
            <button key={f} className={'chip' + (filter === f ? ' active' : '')} onClick={() => setFilter(f)}>{f}</button>
          ))}
          <input
            className="input"
            placeholder="Buscar artículo…"
            value={search}
            onChange={e => setSearch(e.target.value)}
            style={{ marginLeft: 'auto', width: 200, padding: '4px 10px', fontSize: 13 }}
          />
        </div>

        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
          <table className="dt">
            <thead>
              <tr>
                <th>Artículo</th>
                <th>Supermercado</th>
                <th>Unidad</th>
                <th>Envase</th>
                <th style={{ textAlign: 'right' }}>Stock</th>
                <th style={{ textAlign: 'right' }}>Mín</th>
                <th></th>
              </tr>
            </thead>
            <tbody>
              {data.map(a => {
                const low = a.stock < a.min_stock;
                return (
                  <tr key={a.id} className={low ? 'low' : ''} onClick={() => openArticle(a)} style={{ cursor: 'pointer' }}>
                    <td>
                      <div className="t-body-bold" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                        {a.name}
                        {!a.canonical_name && (
                          <span title="Sin producto de precios vinculado — ábrelo para vincularlo"
                                style={{ fontSize: 10, fontFamily: 'var(--f-mono)', color: 'var(--tile-pink)', border: '1px solid var(--tile-pink)', borderRadius: 6, padding: '1px 5px', whiteSpace: 'nowrap' }}>
                            SIN PRECIO
                          </span>
                        )}
                      </div>
                    </td>
                    <td><span className="t-mono-micro">{(a.supermarket || '').toUpperCase()}</span></td>
                    <td><span className="t-mono-micro dim">{a.unit}</span></td>
                    <td><span className="t-mono-micro dim">{a.envase_qty} {a.unit}</span></td>
                    <td style={{ textAlign: 'right' }} onClick={(ev) => ev.stopPropagation()}>
                      {editingStockId === a.id ? (
                        <input
                          type="number"
                          value={editingStockVal}
                          autoFocus
                          onChange={e => setEditingStockVal(e.target.value)}
                          onBlur={() => commitEditStock(a)}
                          onKeyDown={e => {
                            if (e.key === 'Enter') commitEditStock(a);
                            if (e.key === 'Escape') setEditingStockId(null);
                          }}
                          style={{ width: 56, textAlign: 'right', background: 'var(--slate)', border: '1px solid var(--mint)', borderRadius: 4, color: 'var(--text)', fontFamily: 'var(--f-display)', fontSize: 18, padding: '2px 4px' }}
                        />
                      ) : (
                        <span
                          className="t-headline-s"
                          title="Clic para editar"
                          style={{ color: low ? 'var(--tile-pink)' : 'var(--text)', cursor: 'text', borderBottom: '1px dashed var(--frame)' }}
                          onClick={e => startEditStock(e, a)}
                        >{a.stock}</span>
                      )}
                    </td>
                    <td style={{ textAlign: 'right' }}><span className="t-mono-micro dim">{a.min_stock}</span></td>
                    <td onClick={(ev) => ev.stopPropagation()}>
                      <div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
                        <button className="btn btn--ghost btn--sm" style={{ padding: '2px 8px' }}
                          onClick={() => quickStock(a, -1)}>−</button>
                        <button className="btn btn--ghost btn--sm" style={{ padding: '2px 8px' }}
                          onClick={() => quickStock(a, 1)}>＋</button>
                        <button
                          className={'btn btn--ghost btn--sm' + (a.manual_shop ? ' btn--ultra' : '')}
                          style={{ fontSize: 11, padding: '2px 6px' }}
                          onClick={() => toggleManualShop(a)}
                        >{a.manual_shop ? '✓ En lista' : '🛒 A la lista'}</button>
                        <button className="btn btn--ghost btn--sm" onClick={() => openArticle(a)}>Editar</button>
                        <button className="btn btn--ghost btn--sm" style={{ color: 'var(--tile-pink)' }}
                          onClick={() => deleteArticle(a.id)}>✕</button>
                      </div>
                    </td>
                  </tr>
                );
              })}
              {data.length === 0 && (ingredients || []).length > 0 && (
                <tr><td colSpan="7" style={{ textAlign: 'center', color: 'var(--text-meta)', padding: 24 }}>Sin artículos con ese filtro.</td></tr>
              )}
              {(ingredients || []).length === 0 && (
                <tr><td colSpan="7" style={{ textAlign: 'center', color: 'var(--text-meta)', padding: 24 }}>Sin artículos. Crea el primero.</td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
}

// =========================================================
// ENTRADAS — expense list with month navigation
// =========================================================
function EntradasScreen() {
  const { reload: storeReload } = window.RC_STORE.useStore();
  const [monthOffset, setMonthOffset] = useState(0); // 0 = current month
  const [expenses, setExpenses] = useState([]);
  const [loading, setLoading] = useState(true);

  const getMonthKey = (offset) => {
    const d = new Date();
    d.setDate(1);
    d.setMonth(d.getMonth() + offset);
    return d.toISOString().slice(0, 7); // YYYY-MM
  };

  const monthKey = getMonthKey(monthOffset);

  const monthLabel = useMemo(() => {
    const [y, m] = monthKey.split('-').map(Number);
    return new Date(y, m - 1, 1).toLocaleString('es-ES', { month: 'long', year: 'numeric' });
  }, [monthKey]);

  const loadExpenses = useCallback(async (mk) => {
    setLoading(true);
    try {
      const r = await window.api(`/api/expenses?month=${mk}`);
      setExpenses(r || []);
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setLoading(false); }
  }, []);

  useEffect(() => { loadExpenses(monthKey); }, [monthKey]);

  const total = expenses.reduce((s, e) => s + (e.amount || 0), 0);

  const openExpense = (e) => {
    window.dispatchEvent(new CustomEvent('rc:modal', {
      detail: { name: 'expense', props: { expense: e, onSave: () => loadExpenses(monthKey) } },
    }));
  };

  const deleteExpense = async (id) => {
    try {
      await window.api(`/api/expenses/${id}`, 'DELETE');
      setExpenses(prev => prev.filter(e => e.id !== id));
      await storeReload.expenses();
      window.showToast('Gasto eliminado');
    } catch (e) { window.showToast(e.message, 'error'); }
  };

  return (
    <>
      <MH
        kicker="02 · Gastos / Entradas"
        title={monthLabel.charAt(0).toUpperCase() + monthLabel.slice(1)}
        deck={`${expenses.length} entradas · ${total.toFixed(2)}€`}
        stats={
          <>
            <Stat label="Entradas" value={expenses.length} />
            <Stat label="Total" value={`${total.toFixed(2)}€`} />
          </>
        }
        actions={null}
      />
      <div className="screen">
        <div className="week-toolbar" style={{ marginBottom: 16 }}>
          <div className="week-nav">
            <button onClick={() => setMonthOffset(v => v - 1)} aria-label="mes anterior">‹</button>
            <span className="label">{monthLabel.charAt(0).toUpperCase() + monthLabel.slice(1)}</span>
            <button onClick={() => setMonthOffset(v => v + 1)} aria-label="mes siguiente" disabled={monthOffset >= 0}>›</button>
          </div>
          <button className="btn btn--ghost btn--sm" onClick={() => setMonthOffset(0)} disabled={monthOffset === 0}>Hoy</button>
        </div>

        {loading && <div className="t-body dim">Cargando…</div>}
        {!loading && (
          <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
            <table className="dt">
              <thead>
                <tr>
                  <th>Fecha</th>
                  <th>Lugar</th>
                  <th>Categoría</th>
                  <th>Pago</th>
                  <th>Notas</th>
                  <th style={{ textAlign: 'right' }}>Importe</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {expenses.map((e) => (
                  <tr key={e.id} onClick={() => openExpense(e)} style={{ cursor: 'pointer' }}>
                    <td><span className="t-mono-micro dim">{(e.expense_date || '').toUpperCase()}</span></td>
                    <td><div className="t-body-bold">{e.place_name || '—'}</div></td>
                    <td>
                      {e.category_icon && <span style={{ marginRight: 4 }}>{e.category_icon}</span>}
                      <span className="tag tag--outline">{e.category_name || '—'}</span>
                    </td>
                    <td><span className="t-mono-micro">{(e.payment || '').toUpperCase()}</span></td>
                    <td><span className="t-body dim" style={{ fontSize: 13 }}>{e.details || ''}</span></td>
                    <td style={{ textAlign: 'right' }}>
                      <span className="t-display-3" style={{ fontSize: 22 }}>{(e.amount || 0).toFixed(2)}€</span>
                    </td>
                    <td style={{ whiteSpace: 'nowrap' }} onClick={(ev) => ev.stopPropagation()}>
                      <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end', alignItems: 'center' }}>
                        {e.matching_ticket_id ? (
                          <button className="btn btn--ghost btn--sm"
                            onClick={() => window.rcOpen('ticket', { ticketId: e.matching_ticket_id, onSave: () => loadExpenses(monthKey) })}>
                            🎫
                          </button>
                        ) : (
                          <span style={{ display: 'inline-block', width: 32 }} />
                        )}
                        <button className="btn btn--ghost btn--sm"
                          onClick={() => openExpense(e)}>Editar</button>
                        <button className="btn btn--ghost btn--sm" style={{ color: 'var(--tile-pink)' }}
                          onClick={() => deleteExpense(e.id)}>✕</button>
                      </div>
                    </td>
                  </tr>
                ))}
                {expenses.length === 0 && (
                  <tr><td colSpan="7" style={{ textAlign: 'center', color: 'var(--text-meta)', padding: 24 }}>Sin gastos en {monthLabel}.</td></tr>
                )}
              </tbody>
              <tfoot>
                <tr>
                  <td colSpan="5" style={{ textAlign: 'right' }}>
                    <span className="t-mono-label dim">TOTAL</span>
                  </td>
                  <td style={{ textAlign: 'right' }}>
                    <span className="t-display-3" style={{ fontSize: 28, color: 'var(--mint)' }}>{total.toFixed(2)}€</span>
                  </td>
                  <td></td>
                </tr>
              </tfoot>
            </table>
          </div>
        )}
      </div>
    </>
  );
}

// =========================================================
// MESES — year view
// =========================================================
const MONTH_NAMES_ES = ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'];

function MesesScreen() {
  const { categories } = window.RC_STORE.useStore();
  const [apiData, setApiData] = useState(null);
  const [year, setYear] = useState(new Date().getFullYear().toString());

  useEffect(() => {
    window.api(`/api/expenses/months?year=${year}`)
      .then(r => setApiData(r))
      .catch(e => window.showToast(e.message, 'error'));
  }, [year]);

  const currentYM = new Date().toISOString().slice(0, 7);
  const rawMonths = (apiData?.months || []);
  const years = apiData?.years || [year];
  const yearTotal = rawMonths.reduce((s, m) => s + m.total, 0);
  const realMonths = rawMonths.filter(m => m.total > 0);
  const avg = realMonths.length > 0 ? yearTotal / realMonths.length : 0;

  const catColorMap = useMemo(() => {
    const m = {};
    (categories || []).forEach((c, i) => { m[c.name] = CAT_PALETTE[i % CAT_PALETTE.length]; });
    return m;
  }, [categories]);

  // Item 15: descending order, no future months
  const allMonths = useMemo(() => {
    const result = [];
    for (let idx = 11; idx >= 0; idx--) {
      const mk = `${year}-${String(idx + 1).padStart(2, '0')}`;
      if (mk > currentYM) continue;
      const m = rawMonths.find(x => x.month === mk);
      result.push({
        mes: MONTH_NAMES_ES[idx], mk, total: m?.total || 0,
        top_categories: m?.top_categories || [],
        topCat: m?.top_categories?.[0]?.name || '—',
        topPlace: m?.top_places?.[0]?.name || '—',
        current: mk === currentYM,
      });
    }
    return result;
  }, [rawMonths, year, currentYM]);

  const max = Math.max(...allMonths.map(m => m.total), 1);

  return (
    <>
      <MH
        kicker="02 · Gastos / Meses"
        title={year}
        deck={`${realMonths.length} meses con datos · promedio ${avg.toFixed(0)}€/mes`}
        stats={
          <>
            <Stat label="YTD" value={`${yearTotal.toFixed(0)}€`} />
            <Stat label="Promedio" value={`${avg.toFixed(0)}€`} />
            <Stat label="Proyección año" value={`${Math.round(avg * 12)}€`} delta="EST" />
          </>
        }
        actions={
          <>
            {[...years].reverse().map(y => (
              <button key={y} className={'btn btn--sm' + (y === year ? ' btn--primary' : ' btn--ghost')} onClick={() => setYear(y)}>{y}</button>
            ))}
          </>
        }
      />
      <div className="screen">
        <div className="card">
          <SH title="Evolución mensual" sub={`€ por mes · ${year}`} />
          <div className="bars" style={{ marginTop: 28, height: 220 }}>
            {allMonths.map(m => (
              <div className="bar" key={m.mes}>
                {/* Item 16: stacked bars by category */}
                <div style={{
                  height: m.total ? `${(m.total / max) * 100}%` : '4%',
                  display: 'flex', flexDirection: 'column-reverse', width: '100%',
                  position: 'relative', borderRadius: '6px 6px 0 0', overflow: 'hidden',
                }}>
                  {m.top_categories.length > 0
                    ? m.top_categories.map((cat, ci) => (
                        <div key={cat.name} style={{
                          flex: `0 0 ${m.total > 0 ? ((cat.total / m.total) * 100).toFixed(1) : 0}%`,
                          background: catColorMap[cat.name] || CAT_PALETTE[ci % CAT_PALETTE.length],
                          minHeight: m.total > 0 ? 2 : 0,
                        }} />
                      ))
                    : <div style={{ flex: 1, background: m.current ? 'var(--ultraviolet)' : 'var(--mint)' }} />
                  }
                  {m.total > 0 && (
                    <span style={{ position: 'absolute', top: -18, left: 0, right: 0, textAlign: 'center', fontSize: 9, color: 'var(--text-meta)', fontFamily: 'var(--f-mono)' }}>
                      {m.total.toFixed(0)}€
                    </span>
                  )}
                </div>
                <span className="lbl" style={{ color: m.current ? 'var(--mint)' : undefined }}>{m.mes}</span>
              </div>
            ))}
          </div>
        </div>

        <SH title="Mes a mes" sub="Total · top categoría · top lugar" />
        <div className="cols-3">
          {allMonths.map((m, idx) => {
            const CARD_PALETTE = [
              { bg: '#3cffd0', fg: '#000' },
              { bg: '#5200ff', fg: '#fff' },
              { bg: '#ffd166', fg: '#000' },
              { bg: '#ff7ad9', fg: '#000' },
              { bg: '#ff6a1a', fg: '#000' },
            ];
            const pal = CARD_PALETTE[idx % CARD_PALETTE.length];
            if (m.total === 0 && !m.current) return (
              <div className="card" key={m.mes} style={{ borderStyle: 'dashed', opacity: 0.4 }}>
                <div className="t-mono-label dim">SIN DATOS</div>
                <div className="t-display-3 dim" style={{ marginTop: 8, fontSize: 36 }}>{m.mes}</div>
              </div>
            );
            const metaColor = pal.fg === '#fff' ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,0.55)';
            return (
              <div
                className="card card--block"
                key={m.mes}
                style={{ background: pal.bg, color: pal.fg, borderColor: pal.bg }}
              >
                <div className="t-mono-label" style={{ color: metaColor }}>
                  {m.current ? 'MES ACTUAL' : 'CERRADO'}
                </div>
                <div className="t-display-3" style={{ marginTop: 8, fontSize: 36 }}>{m.mes} {m.total.toFixed(0)}€</div>
                <hr className="hr" style={{ margin: '14px 0', borderColor: metaColor }} />
                <div className="stack-2">
                  <div className="row-between">
                    <span className="t-mono-micro" style={{ color: metaColor }}>TOP CATEGORÍA</span>
                    <span className="t-body-bold">{m.topCat}</span>
                  </div>
                  <div className="row-between">
                    <span className="t-mono-micro" style={{ color: metaColor }}>TOP LUGAR</span>
                    <span className="t-body-bold">{m.topPlace}</span>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}

// =========================================================
// ALIASES — product aliases
// =========================================================
function NewAliasModal({ onClose, onSaved }) {
  const [rawName, setRawName] = useState('');
  const [canonicalName, setCanonicalName] = useState('');
  const [saving, setSaving] = useState(false);

  const doSave = async () => {
    if (!rawName.trim() || !canonicalName.trim()) {
      window.showToast('Rellena ambos campos', 'error'); return;
    }
    setSaving(true);
    try {
      await window.api('/api/ticket-aliases', 'POST', {
        raw_name: rawName.trim(),
        canonical_name: canonicalName.trim(),
      });
      window.showToast('Alias creado');
      onSaved();
      onClose();
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setSaving(false); }
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.6)', zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <div style={{ background: 'var(--slate)', border: '1px solid var(--frame)', borderRadius: 16, padding: 28, width: 420, maxWidth: '90vw' }}>
        <div style={{ fontWeight: 700, fontSize: 16, marginBottom: 20 }}>Nuevo alias</div>
        <div style={{ marginBottom: 12 }}>
          <label className="t-mono-micro dim" style={{ display: 'block', marginBottom: 4 }}>TEXTO DEL TICKET (raw)</label>
          <input className="input" value={rawName} onChange={e => setRawName(e.target.value)} placeholder="ej. POLLO FRESCO 1KG" style={{ width: '100%' }} />
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
          <span style={{ color: 'var(--mint)', fontFamily: 'var(--f-mono)', fontWeight: 700, fontSize: 18 }}>→</span>
        </div>
        <div style={{ marginBottom: 20 }}>
          <label className="t-mono-micro dim" style={{ display: 'block', marginBottom: 4 }}>NOMBRE CANÓNICO</label>
          <input className="input" value={canonicalName} onChange={e => setCanonicalName(e.target.value)} placeholder="ej. pechuga de pollo" style={{ width: '100%' }} />
        </div>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button className="btn btn--ghost btn--sm" onClick={onClose}>Cancelar</button>
          <button className="btn btn--primary btn--sm" onClick={doSave} disabled={saving}>{saving ? '…' : 'Crear alias'}</button>
        </div>
      </div>
    </div>
  );
}

function AliasesScreen() {
  const auth = window.RC_Auth?.useAuth?.() ?? null;
  const isAdmin = auth?.user?.is_admin ?? false;

  const [filter, setFilter] = useState('Todos');
  const [aliases, setAliases] = useState([]);
  const [loading, setLoading] = useState(true);
  const [showNewModal, setShowNewModal] = useState(false);
  const [editingId, setEditingId] = useState(null);
  const [editingVal, setEditingVal] = useState('');
  const [allHouseholds, setAllHouseholds] = useState(false);
  const filters = ['Todos', 'Canonical', 'Propuestos'];

  // Merge suggestions state
  const [suggestions, setSuggestions] = useState([]);
  const [suggLoading, setSuggLoading] = useState(false);
  const [suggLoaded, setSuggLoaded] = useState(false);
  const [survivorMap, setSurvivorMap] = useState({});

  const loadAliases = useCallback(async () => {
    setLoading(true);
    try {
      const url = allHouseholds
        ? '/api/ticket-aliases?all_households=true&limit=500'
        : '/api/ticket-aliases';
      const r = await window.api(url);
      setAliases(r || []);
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setLoading(false); }
  }, [allHouseholds]);

  const loadSuggestions = async () => {
    setSuggLoading(true);
    try {
      const r = await window.api('/api/admin/merge-suggestions?type=alias');
      setSuggestions(r || []);
      const m = {};
      (r || []).forEach((g, i) => { m[i] = g.suggested_survivor_id; });
      setSurvivorMap(m);
      setSuggLoaded(true);
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setSuggLoading(false); }
  };

  const doMerge = async (groupIdx, group) => {
    const survivorId = survivorMap[groupIdx] ?? group.suggested_survivor_id;
    const loserIds = group.members.filter(m => m.id !== survivorId).map(m => m.id);
    if (loserIds.length === 0) { window.showToast('Selecciona un superviviente diferente', 'error'); return; }
    window.showConfirm(
      `¿Fusionar ${group.members.length} aliases en uno? Esta acción no se puede deshacer.`,
      async () => {
        window.closeConfirm();
        try {
          await window.api('/api/admin/merge', 'POST', { type: 'alias', survivor_id: survivorId, loser_ids: loserIds });
          window.showToast(`Fusionados ${loserIds.length} aliases`);
          await loadAliases();
          await loadSuggestions();
        } catch (e) { window.showToast(e.message, 'error'); }
      }
    );
  };

  useEffect(() => { loadAliases(); }, [loadAliases]);

  const data = aliases.filter(a => {
    if (filter === 'Canonical') return a.status === 'canonical';
    if (filter === 'Propuestos') return a.status === 'proposed';
    return true;
  });
  const counts = {
    canonical: aliases.filter(a => a.status === 'canonical').length,
    proposed: aliases.filter(a => a.status === 'proposed').length,
  };

  const doConfirm = async (id) => {
    try {
      await window.api(`/api/ticket-aliases/${id}/confirm`, 'POST', {});
      await loadAliases();
      window.showToast('Alias confirmado');
    } catch (e) { window.showToast(e.message, 'error'); }
  };
  const doPromote = async (id) => {
    try {
      await window.api(`/api/admin/aliases/${id}/promote`, 'POST', {});
      await loadAliases();
      window.showToast('Alias promovido a canonical');
    } catch (e) { window.showToast(e.message, 'error'); }
  };
  const doRevert = async (id) => {
    try {
      await window.api(`/api/admin/aliases/${id}/revert`, 'POST', {});
      await loadAliases();
      window.showToast('Alias revertido a propuesto');
    } catch (e) { window.showToast(e.message, 'error'); }
  };
  const startEdit = (a) => { setEditingId(a.id); setEditingVal(a.canonical_name); };
  const cancelEdit = () => setEditingId(null);
  const commitEdit = async (a) => {
    if (!editingVal.trim()) { window.showToast('El canónico no puede estar vacío', 'error'); return; }
    try {
      await window.api(`/api/ticket-aliases/${a.id}`, 'PUT', { canonical_name: editingVal.trim() });
      await loadAliases();
      setEditingId(null);
      window.showToast('Alias actualizado');
    } catch (e) { window.showToast(e.message, 'error'); }
  };

  return (
    <>
      {showNewModal && (
        <NewAliasModal onClose={() => setShowNewModal(false)} onSaved={loadAliases} />
      )}
      <MH
        kicker="03 · Config / Aliases"
        title="Aliases de productos"
        deck="Nombre del ticket → nombre canónico"
        stats={
          <>
            <Stat label="Canonical" value={counts.canonical} delta="GLOBALES" />
            <Stat label="Propuestos" value={counts.proposed} delta={counts.proposed > 0 ? `${counts.proposed} → REVISIÓN` : null} />
            <Stat label="Total" value={aliases.length} />
          </>
        }
        actions={
          <>
            {isAdmin && (
              <button
                className={'btn btn--sm ' + (allHouseholds ? 'btn--primary' : 'btn--ghost')}
                onClick={() => setAllHouseholds(v => !v)}
                title={allHouseholds ? 'Mostrando todos los hogares' : 'Mostrar todos los hogares'}
              >
                {allHouseholds ? '🌐 Todos' : '🏠 Mi hogar'}
              </button>
            )}
            <button className="btn btn--ghost btn--sm" onClick={loadAliases}>↻ Actualizar</button>
            <button className="btn btn--primary btn--sm" onClick={() => setShowNewModal(true)}>+ Nuevo alias</button>
          </>
        }
      />
      <div className="screen">
        <div className="recipe-filters">
          {filters.map(f => (
            <button key={f} className={'chip' + (filter === f ? ' active' : '')} onClick={() => setFilter(f)}>{f}</button>
          ))}
        </div>

        {loading && <div className="t-body dim" style={{ marginTop: 16 }}>Cargando aliases…</div>}
        {!loading && (
          <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
            <table className="dt">
              <thead>
                <tr>
                  <th>Texto del ticket</th>
                  <th></th>
                  <th>Canónico</th>
                  <th>Supermercado</th>
                  {allHouseholds && <th>Hogar</th>}
                  <th>Estado</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {data.map(a => (
                  <tr key={a.id}>
                    <td><span className="t-mono-label" style={{ color: 'var(--text-meta)' }}>{a.raw_name}</span></td>
                    <td><span style={{ color: 'var(--mint)', fontFamily: 'var(--f-mono)', fontWeight: 700 }}>→</span></td>
                    <td>
                      {editingId === a.id ? (
                        <div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
                          <input
                            className="input"
                            value={editingVal}
                            autoFocus
                            onChange={e => setEditingVal(e.target.value)}
                            onKeyDown={e => { if (e.key === 'Enter') commitEdit(a); if (e.key === 'Escape') cancelEdit(); }}
                            style={{ width: 160, padding: '2px 6px', fontSize: 13 }}
                          />
                          <button className="btn btn--outline btn--sm" onClick={() => commitEdit(a)}>✓</button>
                          <button className="btn btn--ghost btn--sm" onClick={cancelEdit}>✕</button>
                        </div>
                      ) : (
                        <span className="t-body-bold">{a.canonical_name}</span>
                      )}
                    </td>
                    <td><span className="t-mono-micro">{(a.supermarket_name || '—').toUpperCase()}</span></td>
                    {allHouseholds && (
                      <td><span className="t-mono-micro" style={{ color: 'var(--text-meta)' }}>{(a.proposing_household_name || '—').toUpperCase()}</span></td>
                    )}
                    <td>
                      {a.status === 'canonical'
                        ? <span className="tag tag--mint">CANONICAL</span>
                        : <span className="tag tag--ultra">PROPUESTO</span>}
                    </td>
                    <td>
                      <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}>
                        <button className="btn btn--ghost btn--sm" onClick={() => startEdit(a)}>Editar</button>
                        {a.status === 'proposed' ? (
                          <>
                            <button className="btn btn--outline btn--sm" onClick={() => doConfirm(a.id)}>Confirmar</button>
                            <button className="btn btn--ghost btn--sm" onClick={() => doPromote(a.id)}>Promover</button>
                          </>
                        ) : (
                          <button className="btn btn--ghost btn--sm" onClick={() => doRevert(a.id)}>Revertir</button>
                        )}
                      </div>
                    </td>
                  </tr>
                ))}
                {data.length === 0 && (
                  <tr><td colSpan={allHouseholds ? 7 : 6} style={{ textAlign: 'center', color: 'var(--text-meta)', padding: 24 }}>Sin aliases.</td></tr>
                )}
              </tbody>
            </table>
          </div>
        )}

        {/* ── Sugerencias de fusión ── */}
        <div style={{ marginTop: 16 }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
            <div className="t-mono-label" style={{ color: 'var(--text-meta)' }}>SUGERENCIAS DE FUSIÓN</div>
            <button className="btn btn--ghost btn--sm" onClick={loadSuggestions} disabled={suggLoading}>
              {suggLoading ? 'Analizando…' : suggLoaded ? '↻ Actualizar' : 'Analizar duplicados'}
            </button>
          </div>
          {suggLoaded && suggestions.length === 0 && (
            <div className="card" style={{ padding: '12px 16px' }}>
              <span className="t-body dim">No se detectaron duplicados similares.</span>
            </div>
          )}
          {suggestions.map((group, gIdx) => (
            <div key={gIdx} className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: 8 }}>
              <table className="dt">
                <thead>
                  <tr>
                    <th style={{ width: 24 }}>✓</th>
                    <th>Texto del ticket</th>
                    <th>Canónico</th>
                    <th>Supermercado</th>
                    <th>Estado</th>
                  </tr>
                </thead>
                <tbody>
                  {group.members.map(m => {
                    const isSurvivor = (survivorMap[gIdx] ?? group.suggested_survivor_id) === m.id;
                    return (
                      <tr key={m.id} style={{ background: isSurvivor ? 'rgba(60,255,208,0.06)' : undefined }}>
                        <td>
                          <input
                            type="radio"
                            name={`survivor-${gIdx}`}
                            checked={isSurvivor}
                            onChange={() => setSurvivorMap(prev => ({ ...prev, [gIdx]: m.id }))}
                          />
                        </td>
                        <td><span className="t-mono-label" style={{ color: 'var(--text-meta)' }}>{m.raw_name}</span></td>
                        <td><span className="t-body-bold">{m.canonical_name}</span></td>
                        <td><span className="t-mono-micro">{(m.supermarket_name || '—').toUpperCase()}</span></td>
                        <td>
                          {m.status === 'canonical'
                            ? <span className="tag tag--mint">CANONICAL</span>
                            : <span className="tag tag--ultra">PROPUESTO</span>}
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              <div style={{ padding: '8px 12px', borderTop: '1px solid var(--frame)', display: 'flex', justifyContent: 'flex-end' }}>
                <button className="btn btn--outline btn--sm" onClick={() => doMerge(gIdx, group)}>
                  Fusionar ({group.members.length})
                </button>
              </div>
            </div>
          ))}
        </div>

        <div className="card card--block card--yellow">
          <div className="t-mono-label invert">CÓMO FUNCIONA</div>
          <div className="t-headline-m invert" style={{ marginTop: 8 }}>3 hogares = canonical</div>
          <div className="t-body invert" style={{ marginTop: 8 }}>
            Cualquier hogar puede proponer un alias. Cuando 3 hogares distintos confirman la misma equivalencia, el alias se promueve automáticamente a canonical. El admin puede promover o revertir manualmente.
          </div>
        </div>
      </div>
    </>
  );
}

// =========================================================
// CATEGORÍAS Y LUGARES (items 20/21/22 — split into two screens)
// =========================================================
const CAT_PALETTE = ['#3cffd0','#5200ff','#ffd166','#ef476f','#06d6a0','#118ab2','#ff9f1c','#e63946'];

function CategoriasScreen() {
  const { categories, budgets, reload } = window.RC_STORE.useStore();
  const budgetByName = useMemo(() => {
    const m = {};
    budgets.forEach(b => { m[b.name] = b; });
    return m;
  }, [budgets]);
  const totalBudget = budgets.reduce((s, b) => s + (b.monthly_limit || 0), 0);

  const onSave = async () => {
    await reload.expenses();
    await reload.static();
  };

  return (
    <>
      <MH
        kicker="03 · Config / Categorías"
        title="Categorías"
        deck={`${categories.length} activas · presupuesto total ${totalBudget.toFixed(0)}€/mes`}
        actions={
          <button className="btn btn--primary" onClick={() => window.rcOpen('category', { onSave })}>+ Nueva categoría</button>
        }
      />
      <div className="screen">
        <div className="cols-3">
          {categories.map((c, i) => {
            const color = CAT_PALETTE[i % CAT_PALETTE.length];
            const bud = budgetByName[c.name];
            return (
              <div
                className="card"
                key={c.id}
                style={{ borderColor: color, cursor: 'pointer' }}
                onClick={() => window.rcOpen('category', { category: { ...c, monthly_limit: bud?.monthly_limit }, onSave })}
              >
                <div className="row" style={{ gap: 12 }}>
                  <div style={{ width: 40, height: 40, borderRadius: 10, background: color, color: '#000', display: 'grid', placeItems: 'center', fontFamily: 'var(--f-mono)', fontWeight: 700, fontSize: 18 }}>{c.icon || '📦'}</div>
                  <div className="stack-2">
                    <div className="t-headline-s">{c.name}</div>
                    <div className="t-mono-micro dim">CATEGORÍA · #{c.id}</div>
                  </div>
                </div>
                <hr className="hr" style={{ margin: '16px 0' }} />
                <div className="row-between">
                  <div className="t-mono-micro dim">PRESUPUESTO MENSUAL</div>
                  <div className="t-display-3" style={{ fontSize: 28 }}>{(bud?.monthly_limit || 0).toFixed(0)}€</div>
                </div>
              </div>
            );
          })}
          {categories.length === 0 && <div className="t-body dim">Sin categorías.</div>}
        </div>
      </div>
    </>
  );
}

function LugaresScreen() {
  const { places, categories, reload } = window.RC_STORE.useStore();
  const auth = window.RC_Auth?.useAuth?.() ?? null;
  const isAdmin = auth?.user?.is_admin ?? false;

  const onSave = async () => { await reload.static(); };

  const [allHouseholds, setAllHouseholds] = useState(false);
  const [allPlaces, setAllPlaces]         = useState([]);
  const [allLoading, setAllLoading]       = useState(false);

  const loadAllPlaces = useCallback(async () => {
    setAllLoading(true);
    try {
      const r = await window.api('/api/admin/places');
      setAllPlaces(r || []);
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setAllLoading(false); }
  }, []);

  useEffect(() => {
    if (allHouseholds) loadAllPlaces();
    else setAllPlaces([]);
  }, [allHouseholds, loadAllPlaces]);

  // Merge suggestions state
  const [suggestions, setSuggestions] = useState([]);
  const [suggLoading, setSuggLoading] = useState(false);
  const [suggLoaded, setSuggLoaded] = useState(false);
  const [survivorMap, setSurvivorMap] = useState({});

  const loadSuggestions = async () => {
    setSuggLoading(true);
    try {
      const r = await window.api('/api/admin/merge-suggestions?type=place');
      setSuggestions(r || []);
      const m = {};
      (r || []).forEach((g, i) => { m[i] = g.suggested_survivor_id; });
      setSurvivorMap(m);
      setSuggLoaded(true);
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setSuggLoading(false); }
  };

  const doMerge = async (groupIdx, group) => {
    const survivorId = survivorMap[groupIdx] ?? group.suggested_survivor_id;
    const loserIds = group.members.filter(m => m.id !== survivorId).map(m => m.id);
    if (loserIds.length === 0) { window.showToast('Selecciona un superviviente diferente', 'error'); return; }
    window.showConfirm(
      `¿Fusionar ${group.members.length} lugares en uno? Esta acción no se puede deshacer.`,
      async () => {
        window.closeConfirm();
        try {
          await window.api('/api/admin/merge', 'POST', { type: 'place', survivor_id: survivorId, loser_ids: loserIds });
          window.showToast(`Fusionados ${loserIds.length} lugares`);
          await reload.static();
          await loadSuggestions();
        } catch (e) { window.showToast(e.message, 'error'); }
      }
    );
  };

  return (
    <>
      <MH
        kicker="03 · Config / Lugares"
        title="Lugares"
        deck={allHouseholds ? `${allPlaces.length} total` : `${places.length} guardados`}
        actions={
          <>
            {isAdmin && (
              <button
                className={'btn btn--sm ' + (allHouseholds ? 'btn--primary' : 'btn--ghost')}
                onClick={() => setAllHouseholds(v => !v)}
                title={allHouseholds ? 'Mostrando todos los hogares' : 'Mostrar todos los hogares'}
              >
                {allHouseholds ? '🌐 Todos' : '🏠 Mi hogar'}
              </button>
            )}
            <button className="btn btn--primary" onClick={() => window.rcOpen('place', { onSave })}>+ Nuevo lugar</button>
          </>
        }
      />
      <div className="screen">
        {allLoading && <div className="t-body dim" style={{ marginTop: 16 }}>Cargando todos los lugares…</div>}
        {!allLoading && (
        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
          <table className="dt">
            <thead>
              <tr>
                <th>Lugar</th>
                {allHouseholds && <th>Hogar</th>}
                <th>Categoría por defecto</th>
                <th>Pago por defecto</th>
                {!allHouseholds && <th></th>}
              </tr>
            </thead>
            <tbody>
              {allHouseholds ? allPlaces.map(p => (
                <tr key={p.id}>
                  <td><span className="t-body-bold">{p.name}</span></td>
                  <td><span className="t-mono-micro" style={{ color: 'var(--text-meta)' }}>{(p.household_name || '—').toUpperCase()}</span></td>
                  <td><span className="tag tag--outline">{p.category_name || '—'}</span></td>
                  <td><span className="t-mono-micro">{(p.default_payment || '—').toUpperCase()}</span></td>
                </tr>
              )) : places.map(p => (
                <tr key={p.id}>
                  <td><span className="t-body-bold">{p.name}</span></td>
                  <td><span className="tag tag--outline">{p.category_name || '—'}</span></td>
                  <td><span className="t-mono-micro">{(p.default_payment || '—').toUpperCase()}</span></td>
                  <td>
                    <button className="btn btn--ghost btn--sm"
                      onClick={() => window.rcOpen('place', { place: p, onSave })}>
                      Editar
                    </button>
                  </td>
                </tr>
              ))}
              {!allHouseholds && places.length === 0 && (
                <tr><td colSpan="4" style={{ textAlign: 'center', color: 'var(--text-meta)', padding: 24 }}>Sin lugares.</td></tr>
              )}
            </tbody>
          </table>
        </div>
        )}

        {/* ── Sugerencias de fusión ── */}
        <div style={{ marginTop: 16 }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
            <div className="t-mono-label" style={{ color: 'var(--text-meta)' }}>SUGERENCIAS DE FUSIÓN</div>
            <button className="btn btn--ghost btn--sm" onClick={loadSuggestions} disabled={suggLoading}>
              {suggLoading ? 'Analizando…' : suggLoaded ? '↻ Actualizar' : 'Analizar duplicados'}
            </button>
          </div>
          {suggLoaded && suggestions.length === 0 && (
            <div className="card" style={{ padding: '12px 16px' }}>
              <span className="t-body dim">No se detectaron duplicados similares.</span>
            </div>
          )}
          {suggestions.map((group, gIdx) => (
            <div key={gIdx} className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: 8 }}>
              <table className="dt">
                <thead>
                  <tr>
                    <th style={{ width: 24 }}>✓</th>
                    <th>Nombre del lugar</th>
                    <th>Hogar</th>
                  </tr>
                </thead>
                <tbody>
                  {group.members.map(m => {
                    const isSurvivor = (survivorMap[gIdx] ?? group.suggested_survivor_id) === m.id;
                    return (
                      <tr key={m.id} style={{ background: isSurvivor ? 'rgba(60,255,208,0.06)' : undefined }}>
                        <td>
                          <input
                            type="radio"
                            name={`survivor-place-${gIdx}`}
                            checked={isSurvivor}
                            onChange={() => setSurvivorMap(prev => ({ ...prev, [gIdx]: m.id }))}
                          />
                        </td>
                        <td><span className="t-body-bold">{m.name}</span></td>
                        <td><span className="t-mono-micro">{(m.household_name || '—').toUpperCase()}</span></td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              <div style={{ padding: '8px 12px', borderTop: '1px solid var(--frame)', display: 'flex', justifyContent: 'flex-end' }}>
                <button className="btn btn--outline btn--sm" onClick={() => doMerge(gIdx, group)}>
                  Fusionar ({group.members.length})
                </button>
              </div>
            </div>
          ))}
        </div>
      </div>
    </>
  );
}

// =========================================================
// UNIDADES — product unit mappings
// =========================================================
function UnidadesScreen() {
  const [unitMaps, setUnitMaps] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    window.api('/api/product-unit-mappings')
      .then(r => setUnitMaps(r || []))
      .catch(e => window.showToast(e.message, 'error'))
      .finally(() => setLoading(false));
  }, []);

  return (
    <>
      <MH
        kicker="03 · Config / Unidades"
        title="Mapeos de unidad"
        deck="Cómo convertir lo que aparece en el ticket a lo que usa la receta"
        stats={
          <>
            <Stat label="Mapeos" value={unitMaps.length} />
          </>
        }
        actions={<button className="btn btn--primary">+ Nuevo mapeo</button>}
      />
      <div className="screen">
        <div className="card card--block card--mint" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
          <div>
            <div className="t-mono-label invert">EJEMPLO</div>
            <div className="t-display-3 invert" style={{ marginTop: 8, fontSize: 36 }}>1L aceite = 1.000 ml</div>
            <div className="t-body invert" style={{ marginTop: 8, maxWidth: 460 }}>
              Sin este mapeo, al subir un ticket con "1L AOVE" el stock no se descontaría correctamente al ejecutar una receta que pide "20ml de aceite".
            </div>
          </div>
        </div>

        {loading && <div className="t-body dim">Cargando mapeos…</div>}
        {!loading && (
          <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
            <table className="dt">
              <thead>
                <tr>
                  <th>Producto</th>
                  <th>Unidad en ticket</th>
                  <th>Pack qty</th>
                  <th></th>
                  <th>Unidad en receta</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {unitMaps.map(m => (
                  <tr key={m.id}>
                    <td><span className="t-body-bold">{m.product_name}</span></td>
                    <td><span className="tag tag--outline-mint">{m.ticket_unit}</span></td>
                    <td><span className="t-mono-label">× {m.pack_qty}</span></td>
                    <td><span style={{ color: 'var(--mint)', fontFamily: 'var(--f-mono)', fontWeight: 700 }}>→</span></td>
                    <td><span className="tag tag--outline">{m.recipe_unit}</span></td>
                    <td>
                      <button className="btn btn--ghost btn--sm">Editar</button>
                    </td>
                  </tr>
                ))}
                {unitMaps.length === 0 && (
                  <tr><td colSpan="6" style={{ textAlign: 'center', color: 'var(--text-meta)', padding: 24 }}>Sin mapeos.</td></tr>
                )}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </>
  );
}

// =========================================================
// INVITACIONES — admin
// =========================================================
function InvitesScreen() {
  const [invites, setInvites] = useState([]);
  const [gemini, setGemini] = useState(null);
  const [invEmail, setInvEmail] = useState('');
  const [invHours, setInvHours] = useState(72);
  const [busy, setBusy] = useState(false);

  const loadData = useCallback(async () => {
    try {
      const [invs, gem] = await Promise.all([
        window.api('/api/admin/invites'),
        window.api('/api/admin/gemini-usage/summary'),
      ]);
      setInvites(invs || []);
      setGemini(gem || null);
    } catch (e) { window.showToast(e.message, 'error'); }
  }, []);

  useEffect(() => { loadData(); }, []);

  const inviteStatus = (inv) => {
    const now = new Date();
    if (inv.used_at) return 'used';
    if (new Date(inv.expires_at) < now) return 'expired';
    return 'active';
  };

  const doCreate = async () => {
    setBusy(true);
    try {
      const r = await window.api('/api/admin/invites', 'POST', {
        email: invEmail.trim() || null,
        hours: invHours,
        new_household: false,
      });
      window.showToast(`Invitación creada: ${r.code}`);
      setInvEmail('');
      await loadData();
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setBusy(false); }
  };

  const copyLink = (code) => {
    const url = `${window.location.origin}/#invite=${code}`;
    navigator.clipboard.writeText(url).then(() => window.showToast('Link copiado'));
  };

  const counts = {
    active: invites.filter(i => inviteStatus(i) === 'active').length,
    used: invites.filter(i => inviteStatus(i) === 'used').length,
    expired: invites.filter(i => inviteStatus(i) === 'expired').length,
  };
  const g = gemini;

  return (
    <>
      <MH
        kicker="04 · Admin / Invitaciones"
        title="Invitaciones"
        deck="Registro privado · solo con código válido"
        stats={
          <>
            <Stat label="Activas" value={counts.active} delta={counts.active > 0 ? `${counts.active} CÓDIGOS` : null} />
            <Stat label="Usadas" value={counts.used} />
            <Stat label="Caducadas" value={counts.expired} down={counts.expired > 0} delta={counts.expired > 0 ? `${counts.expired} EXPIRADAS` : null} />
          </>
        }
        actions={<button className="btn btn--ghost btn--sm" onClick={loadData}>↻ Actualizar</button>}
      />
      <div className="screen">
        <div className="cols-l-r">
          <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
            <table className="dt">
              <thead>
                <tr>
                  <th>Código</th>
                  <th>Email</th>
                  <th>Creada</th>
                  <th>Caduca</th>
                  <th>Estado</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {invites.map(inv => {
                  const status = inviteStatus(inv);
                  return (
                    <tr key={inv.id}>
                      <td><span className="t-mono-label">{inv.id.slice(0, 12)}…</span></td>
                      <td><span className="t-body">{inv.email || '—'}</span></td>
                      <td><span className="t-mono-micro dim">{(inv.created_at || '').slice(0, 10).toUpperCase()}</span></td>
                      <td><span className="t-mono-micro dim">{(inv.expires_at || '').slice(0, 10).toUpperCase()}</span></td>
                      <td>
                        {status === 'active'  && <span className="tag tag--mint">ACTIVA</span>}
                        {status === 'used'    && <span className="tag tag--outline">USADA</span>}
                        {status === 'expired' && <span className="tag tag--pink">CADUCADA</span>}
                      </td>
                      <td>
                        {status === 'active' && (
                          <button className="btn btn--ghost btn--sm" onClick={() => copyLink(inv.id)}>Copiar link</button>
                        )}
                      </td>
                    </tr>
                  );
                })}
                {invites.length === 0 && (
                  <tr><td colSpan="6" style={{ textAlign: 'center', color: 'var(--text-meta)', padding: 24 }}>Sin invitaciones.</td></tr>
                )}
              </tbody>
            </table>
          </div>

          <div className="stack-4">
            <div className="card card--block card--ultra">
              <div className="t-mono-label" style={{ color: 'rgba(255,255,255,0.7)' }}>NUEVA INVITACIÓN</div>
              <div className="t-display-3" style={{ marginTop: 8, fontSize: 32 }}>Crear código</div>
              <div className="stack-3" style={{ marginTop: 16 }}>
                <div className="field">
                  <label style={{ color: 'rgba(255,255,255,0.7)' }}>EMAIL (OPCIONAL)</label>
                  <input className="input" value={invEmail} onChange={e => setInvEmail(e.target.value)} placeholder="usuario@example.com" style={{ background: 'rgba(0,0,0,0.2)', borderColor: 'rgba(255,255,255,0.25)', color: '#fff' }} />
                </div>
                <div className="field">
                  <label style={{ color: 'rgba(255,255,255,0.7)' }}>VALIDEZ (HORAS)</label>
                  <input className="input" type="number" min="1" max="720" value={invHours} onChange={e => setInvHours(+e.target.value)} style={{ background: 'rgba(0,0,0,0.2)', borderColor: 'rgba(255,255,255,0.25)', color: '#fff' }} />
                </div>
                <button className="btn btn--primary" style={{ alignSelf: 'flex-start' }} onClick={doCreate} disabled={busy}>{busy ? '…' : 'Generar código'}</button>
              </div>
            </div>

            {g && (
              <div className="card">
                <div className="t-mono-label dim">USO DE GEMINI · {g.month}</div>
                <div className="t-display-3" style={{ marginTop: 8, fontSize: 32, color: 'var(--mint)' }}>{(g.total_estimated_eur || 0).toFixed(2)}€</div>
                <div className="t-mono-micro dim" style={{ marginTop: 4 }}>ESTIMADO ESTE MES</div>
                <hr className="hr" style={{ margin: '14px 0' }} />
                <div className="stack-2">
                  <div className="row-between"><span className="t-mono-micro dim">LLAMADAS</span><span className="t-body-bold">{g.total_calls}</span></div>
                  <div className="row-between"><span className="t-mono-micro dim">TOKENS IN</span><span className="t-body-bold">{(g.total_input_tokens || 0).toLocaleString('es-ES')}</span></div>
                  <div className="row-between"><span className="t-mono-micro dim">TOKENS OUT</span><span className="t-body-bold">{(g.total_output_tokens || 0).toLocaleString('es-ES')}</span></div>
                  <div className="row-between"><span className="t-mono-micro dim">HOGARES</span><span className="t-body-bold">{(g.by_household || []).length}</span></div>
                </div>
                {(g.by_household || []).length > 0 && (
                  <div style={{ marginTop: 12 }}>
                    <div className="t-mono-label dim" style={{ marginBottom: 8 }}>POR HOGAR</div>
                    <div className="stack-2">
                      {g.by_household.map(h => (
                        <div key={h.household_id} style={{ background: 'var(--slate)', borderRadius: 8, padding: '8px 12px' }}>
                          <div className="row-between" style={{ marginBottom: 4 }}>
                            <span style={{ fontWeight: 600, fontSize: 13 }}>{h.household_name || 'Hogar ' + h.household_id}</span>
                            <span style={{ color: 'var(--mint)', fontWeight: 700, fontSize: 13 }}>{(h.estimated_eur || 0).toFixed(4)}€</span>
                          </div>
                          <div className="t-mono-micro dim">
                            {h.calls} llamadas · {(h.input_tokens || 0).toLocaleString('es-ES')} in · {(h.output_tokens || 0).toLocaleString('es-ES')} out
                          </div>
                        </div>
                      ))}
                    </div>
                  </div>
                )}
              </div>
            )}
          </div>
        </div>
      </div>
    </>
  );
}

// =========================================================
// DASHBOARD — admin usage analytics
// =========================================================
function DashboardScreen() {
  const [data, setData]       = useState(null);
  const [days, setDays]       = useState(30);
  const [loading, setLoading] = useState(true);

  const loadData = useCallback(async (d) => {
    setLoading(true);
    try {
      const r = await window.api(`/api/admin/usage-dashboard?days=${d}`);
      setData(r);
    } catch (e) { window.showToast(e.message, 'error'); }
    finally { setLoading(false); }
  }, []);

  useEffect(() => { loadData(days); }, [days]);

  if (loading && !data) return <div className="screen"><div style={{ padding: 40, color: 'var(--text-meta)' }}>Cargando…</div></div>;

  const t   = data?.totals || {};
  const lbd = data?.logins_by_day  || [];
  const lbh = data?.logins_by_hour || [];
  const rl  = data?.recent_logins  || [];
  const bh  = data?.by_household   || [];
  const tkt = data?.tickets || { by_day: [], queue: {} };

  const lbdMax = Math.max(1, ...lbd.map(d => d.count));
  const lbhMax = Math.max(1, ...lbh.map(d => d.count));
  const tbdMax = Math.max(1, ...(tkt.by_day || []).map(d => d.count));

  const RangeBtn = ({ d }) => (
    <button
      className={'btn btn--sm' + (days === d ? ' btn--primary' : ' btn--ghost')}
      onClick={() => setDays(d)}
      style={{ minWidth: 44 }}
    >
      {d}d
    </button>
  );

  return (
    <>
      <MH
        kicker="ADMIN · ANALYTICS"
        title="Dashboard"
        deck={`Últimos ${days} días · ${data?.generated_at?.slice(0, 10) || ''}`}
        stats={<>
          <Stat label="HOGARES"      value={t.households  || 0} />
          <Stat label="USUARIOS"     value={t.users       || 0} />
          <Stat label="RECETAS"      value={t.recipes     || 0} />
          <Stat label="TICKETS TOT." value={t.tickets     || 0} />
        </>}
        actions={<>
          <RangeBtn d={7} /><RangeBtn d={30} /><RangeBtn d={90} />
          {loading && <span className="t-mono-micro dim" style={{ marginLeft: 12 }}>Actualizando…</span>}
        </>}
      />
      <div className="screen">

        <div className="kpi-grid">
          <div className="kpi kpi--mint invert">
            <div className="label">ACCESOS</div>
            <div className="v">{t.logins_period || 0}</div>
            <div className="sub">ÚLTIMOS {days} DÍAS</div>
          </div>
          <div className="kpi kpi--ultra">
            <div className="label">USUARIOS ACTIVOS</div>
            <div className="v">{t.active_users_period || 0}</div>
            <div className="sub">DISTINTOS EN PERIODO</div>
          </div>
          <div className="kpi kpi--canvas">
            <div className="label">INGREDIENTES</div>
            <div className="v">{t.ingredients || 0}</div>
            <div className="sub">TOTAL GLOBAL</div>
          </div>
          <div className="kpi kpi--yellow invert">
            <div className="label">COLA OCR</div>
            <div className="v">{(tkt.queue.pending || 0) + (tkt.queue.processing || 0)}</div>
            <div className="sub">{tkt.queue.error || 0} CON ERROR</div>
          </div>
        </div>

        <div className="cols-l-r">
          <div className="card">
            <SH title="Accesos por día" sub={`Últimos ${days} días`} />
            <div className="bars" style={{ marginTop: 24 }}>
              {lbd.length === 0
                ? <div style={{ color: 'var(--text-meta)', fontSize: 13 }}>Sin datos en el rango</div>
                : lbd.map(d => (
                    <div className="bar" key={d.day}>
                      <div className="col col--mint" style={{ height: `${(d.count / lbdMax) * 100}%` }}>
                        <span className="v">{d.count}</span>
                      </div>
                      <span className="lbl">{d.day.slice(5)}</span>
                    </div>
                  ))}
            </div>
          </div>

          <div className="card">
            <SH title="Distribución horaria" sub="Hora del día (UTC)" />
            <div className="bars" style={{ marginTop: 24 }}>
              {lbh.length === 0
                ? <div style={{ color: 'var(--text-meta)', fontSize: 13 }}>Sin datos en el rango</div>
                : lbh.map(d => (
                    <div className="bar" key={d.hour}>
                      <div className="col col--ultra" style={{ height: `${(d.count / lbhMax) * 100}%` }}>
                        <span className="v">{d.count}</span>
                      </div>
                      <span className="lbl">{String(d.hour).padStart(2, '0')}</span>
                    </div>
                  ))}
            </div>
          </div>
        </div>

        <div className="card">
          <SH title="Accesos recientes" sub="Últimos 25 · email + hogar" />
          <table className="dt" style={{ marginTop: 12 }}>
            <thead>
              <tr><th>Email</th><th>Hogar</th><th>Fecha</th></tr>
            </thead>
            <tbody>
              {rl.length === 0 && <tr><td colSpan={3} style={{ color: 'var(--text-meta)' }}>Sin datos</td></tr>}
              {rl.map((r, i) => (
                <tr key={i}>
                  <td>{r.email}</td>
                  <td>{r.household_name || String(r.household_id)}</td>
                  <td className="t-mono-micro">{r.created_at?.slice(0, 16).replace('T', ' ')}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        <div className="card">
          <SH title="Por hogar" sub="Conteos actuales + actividad en el periodo" />
          <div style={{ overflowX: 'auto' }}>
            <table className="dt" style={{ marginTop: 12, minWidth: 700 }}>
              <thead>
                <tr>
                  <th>Hogar</th>
                  <th style={{ textAlign: 'right' }}>Usuarios</th>
                  <th style={{ textAlign: 'right' }}>Recetas</th>
                  <th style={{ textAlign: 'right' }}>Ingred.</th>
                  <th style={{ textAlign: 'right' }}>Tickets</th>
                  <th style={{ textAlign: 'right' }}>Accesos</th>
                  <th>Último acceso</th>
                  <th style={{ textAlign: 'right' }}>Gemini €</th>
                </tr>
              </thead>
              <tbody>
                {bh.length === 0 && <tr><td colSpan={8} style={{ color: 'var(--text-meta)' }}>Sin datos</td></tr>}
                {bh.map(h => (
                  <tr key={h.household_id}>
                    <td style={{ fontWeight: 600 }}>{h.household_name || '—'}</td>
                    <td style={{ textAlign: 'right' }}>{h.users}</td>
                    <td style={{ textAlign: 'right' }}>{h.recipes}</td>
                    <td style={{ textAlign: 'right' }}>{h.ingredients}</td>
                    <td style={{ textAlign: 'right' }}>{h.tickets}</td>
                    <td style={{ textAlign: 'right' }}>{h.logins_period}</td>
                    <td className="t-mono-micro dim">{h.last_login_at?.slice(0, 16) || '—'}</td>
                    <td style={{ textAlign: 'right', color: 'var(--mint)' }}>{(h.gemini_eur || 0).toFixed(4)}€</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>

        <div className="cols-l-r">
          <div className="card">
            <SH title="Tickets procesados / día" sub="Creados en el rango" />
            <div className="bars" style={{ marginTop: 24 }}>
              {tkt.by_day.length === 0
                ? <div style={{ color: 'var(--text-meta)', fontSize: 13 }}>Sin datos en el rango</div>
                : tkt.by_day.map(d => (
                    <div className="bar" key={d.day}>
                      <div className="col col--mint" style={{ height: `${(d.count / tbdMax) * 100}%` }}>
                        <span className="v">{d.count}</span>
                      </div>
                      <span className="lbl">{d.day.slice(5)}</span>
                    </div>
                  ))}
            </div>
          </div>

          <div className="card">
            <SH title="Estado cola OCR" sub="Todos los registros de la cola" />
            <div className="stack-2" style={{ marginTop: 16 }}>
              {Object.entries(tkt.queue).map(([status, count]) => (
                <div className="row-between" key={status}>
                  <span className={'tag tag--outline' + (status === 'error' ? ' tag--pink' : status === 'done' ? ' tag--mint' : '')}>
                    {status.toUpperCase()}
                  </span>
                  <span className="t-body-bold">{count}</span>
                </div>
              ))}
            </div>
          </div>
        </div>

      </div>
    </>
  );
}

// =========================================================
// CUENTAS DE PAGO
// =========================================================
function CuentasPagoScreen() {
  const { paymentAccounts, reload } = window.RC_STORE.useStore();
  const onSave = async () => { await reload.static(); };

  return (
    <>
      <MH
        kicker="03 · Config / Cuentas de pago"
        title="Cuentas de pago"
        deck={`${paymentAccounts.length} cuentas`}
        actions={
          <button className="btn btn--primary" onClick={() => window.rcOpen('payment-account', { onSave })}>+ Nueva cuenta</button>
        }
      />
      <div className="screen">
        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
          <table className="dt">
            <thead>
              <tr>
                <th>Nombre</th>
                <th></th>
              </tr>
            </thead>
            <tbody>
              {paymentAccounts.map(pa => (
                <tr key={pa.id}>
                  <td><span className="t-body-bold">{pa.name}</span></td>
                  <td>
                    <button className="btn btn--ghost btn--sm"
                      onClick={() => window.rcOpen('payment-account', { account: pa, onSave })}>
                      Editar
                    </button>
                  </td>
                </tr>
              ))}
              {paymentAccounts.length === 0 && (
                <tr><td colSpan="2" style={{ textAlign: 'center', color: 'var(--text-meta)', padding: 24 }}>Sin cuentas.</td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
}

Object.assign(window.RC_SCREENS, {
  plantilla:    () => <PlantillaScreen />,
  articulos:    () => <ArticulosScreen />,
  entradas:     () => <EntradasScreen />,
  meses:        () => <MesesScreen />,
  aliases:      () => <AliasesScreen />,
  categorias:   () => <CategoriasScreen />,
  lugares:      () => <LugaresScreen />,
  unidades:     () => <UnidadesScreen />,
  invites:      () => <InvitesScreen />,
  dashboard:    () => <DashboardScreen />,
  cuentaspago:  () => <CuentasPagoScreen />,
});
