// GuestMaker CRM B2B · shared kit: tokens, icons, atoms, seed data.
// Loaded before every other b2b-*.jsx file.

const { useState, useEffect, useRef, useMemo } = React;

// ─── tokens ───
// This page was ported from a standalone Claude Design export, which shipped
// its own font stack and its own near-but-not-quite neutral/rule palette —
// invisible in isolation, but this is the one page on the site not actually
// drawing from styles.css's tokens, so it reads as a different product the
// moment you land on it from crm.html. FUCHSIA/PAPER/CREAM/SURFACE/PLUM
// already matched (or, for PLUM, matched the site's own established pattern
// of a bespoke near-black-plum for dramatic dark bands — see crm.html's
// .crm-agentic-section gradient) and are left as-is. SERIF/SANS/MONO and the
// INK/DIM/FAINT/RULE scale did not, and are the ones a reader actually
// notices: wrong headline serif, wrong body-copy grey, wrong card border.
// Referencing the CSS vars directly (not copying their current hex/stack)
// means this can't drift again the next time the palette moves.
const FUCHSIA      = "#ED4D86";
const FUCHSIA_DEEP = "#C93A6B";
const FUCHSIA_DARK = "#8E2148";
const FUCHSIA_SOFT = "#F8B7CF";
const FUCHSIA_PALE = "#FCE4EE";
const PLUM         = "#211A22";
const INK    = "var(--ink)";
const DIM    = "var(--ink-3)";
const FAINT  = "var(--ink-4)";
const RULE   = "var(--hairline-strong)";
const RULE_SOFT = "var(--hairline)";
const PAPER  = "#fbfaf6";
const CREAM  = "#f6f4ef";
const SURFACE = "#ffffff";
const GREEN  = "#3b8d5a";
const AMBER  = "#b06a2a";
const RED    = "#a23a3a";
const SLATE  = "#9A8E93";
const VIOLET = "#8E5BA8";
const MONO   = "var(--f-mono)";
const SERIF  = "var(--f-display)";
const SANS   = "var(--f-sans)";

// ─── icons ───
function Icon({ name, size = 14, color, strokeWidth = 1.8, style }) {
  const stroke = color || "currentColor";
  const props = {
    width: size, height: size, viewBox: "0 0 24 24", fill: "none",
    stroke, strokeWidth, strokeLinecap: "round", strokeLinejoin: "round",
    style: { display: "block", flexShrink: 0, ...style },
  };
  switch (name) {
    case "building":  return <svg {...props}><path d="M3 21V8l9-5 9 5v13"/><path d="M9 21V13h6v8M9 9h.01M15 9h.01"/></svg>;
    case "briefcase": return <svg {...props}><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2M3 12h18"/></svg>;
    case "users":     return <svg {...props}><circle cx="9" cy="8" r="3.2"/><path d="M3 20a6 6 0 0 1 12 0"/><path d="M16 5.5a3.2 3.2 0 0 1 0 6M17 20a6 6 0 0 0-2-4.4"/></svg>;
    case "user":      return <svg {...props}><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>;
    case "cal":       return <svg {...props}><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M16 3v4M8 3v4M3 11h18"/></svg>;
    case "clock":     return <svg {...props}><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2.5"/></svg>;
    case "mail":      return <svg {...props}><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>;
    case "phone":     return <svg {...props}><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6 19.8 19.8 0 0 1-3.1-8.7A2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .3 1.9.6 2.8a2 2 0 0 1-.5 2.1L8 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.5 2.8.6a2 2 0 0 1 1.8 2z"/></svg>;
    case "check":     return <svg {...props}><polyline points="4,12 10,18 20,6"/></svg>;
    case "x":         return <svg {...props}><path d="M18 6L6 18M6 6l12 12"/></svg>;
    case "dash":      return <svg {...props}><line x1="6" y1="12" x2="18" y2="12"/></svg>;
    case "plus":      return <svg {...props}><path d="M12 5v14M5 12h14"/></svg>;
    case "chevron-r": return <svg {...props}><polyline points="9,6 15,12 9,18"/></svg>;
    case "chevron-l": return <svg {...props}><polyline points="15,6 9,12 15,18"/></svg>;
    case "chevron-d": return <svg {...props}><polyline points="6,9 12,15 18,9"/></svg>;
    case "arrow-r":   return <svg {...props}><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12,5 19,12 12,19"/></svg>;
    case "arrow-u":   return <svg {...props}><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5,12 12,5 19,12"/></svg>;
    case "arrow-d":   return <svg {...props}><line x1="12" y1="5" x2="12" y2="19"/><polyline points="5,12 12,19 19,12"/></svg>;
    case "lock":      return <svg {...props}><rect x="4.5" y="10" width="15" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg>;
    case "shield":    return <svg {...props}><path d="M12 2 4 5v7c0 5 3.5 9 8 10 4.5-1 8-5 8-10V5l-8-3z"/><path d="M8.5 12.5l2.5 2.5L16 9.5"/></svg>;
    case "search":    return <svg {...props}><circle cx="11" cy="11" r="7"/><path d="M16.5 16.5L21 21"/></svg>;
    case "filter":    return <svg {...props}><path d="M3 5h18l-7 8v6l-4-2v-4z"/></svg>;
    case "sort":      return <svg {...props}><path d="M7 4v16M7 20l-3-3M7 4l3 3M17 20V4M17 4l3 3M17 20l-3-3"/></svg>;
    case "board":     return <svg {...props}><rect x="3" y="4" width="5" height="16" rx="1"/><rect x="10" y="4" width="5" height="11" rx="1"/><rect x="17" y="4" width="4" height="14" rx="1"/></svg>;
    case "list":      return <svg {...props}><path d="M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01"/></svg>;
    case "chart":     return <svg {...props}><path d="M4 20V10M10 20V4M16 20v-7M22 20H2"/></svg>;
    case "trophy":    return <svg {...props}><path d="M8 4h8v5a4 4 0 0 1-8 0z"/><path d="M8 5H5v2a3 3 0 0 0 3 3M16 5h3v2a3 3 0 0 1-3 3"/><path d="M12 13v4M9 21h6"/></svg>;
    case "thumb-d":   return <svg {...props}><path d="M17 14V3M3 12.5V5a2 2 0 0 1 2-2h8l2 1v10l-4 6a2 2 0 0 1-3-2l1-3H5a2 2 0 0 1-2-2z"/></svg>;
    case "pin":       return <svg {...props}><path d="M12 22V13M8 3h8l-1.5 5.5L18 12H6l3.5-3.5z"/></svg>;
    case "clip":      return <svg {...props}><path d="M20 11l-7.5 7.5a4.5 4.5 0 0 1-6.4-6.4L13 5.2a3 3 0 0 1 4.3 4.3l-7 7a1.5 1.5 0 0 1-2.2-2.2l6.4-6.4"/></svg>;
    case "pen":       return <svg {...props}><path d="M4 20h4l11-11a2.5 2.5 0 0 0-3.5-3.5L4.5 16.5z"/><path d="M14 6.5L17.5 10"/></svg>;
    case "sign":      return <svg {...props}><path d="M3 18c3 0 4-10 7-10s2 8 5 8 3-4 6-4"/><path d="M3 21h18"/></svg>;
    case "globe":     return <svg {...props}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>;
    case "lang":      return <svg {...props}><path d="M5 8h9M9.5 5v3M11 16c-3 0-5-2-5-5M6 16c3.5 0 6-3 6-8"/><path d="M13 20l4-10 4 10M14.5 17h5"/></svg>;
    case "alert":     return <svg {...props}><path d="M12 3 2.5 20.5h19L12 3zM12 9.5v5M12 18h.01"/></svg>;
    case "spark":     return <svg {...props}><path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z"/><path d="M19 16.5l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7z"/></svg>;
    case "file":      return <svg {...props}><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg>;
    case "download":  return <svg {...props}><path d="M12 3v12M7 11l5 5 5-5M4 21h16"/></svg>;
    case "upload":    return <svg {...props}><path d="M12 16V4M7 8l5-5 5 5M4 21h16"/></svg>;
    case "refresh":   return <svg {...props}><path d="M20 8A8 8 0 0 0 5.5 6.5L4 8"/><path d="M4 4v4h4"/><path d="M4 16a8 8 0 0 0 14.5 1.5L20 16"/><path d="M20 20v-4h-4"/></svg>;
    case "bed":       return <svg {...props}><path d="M3 18V7M3 12h13a4 4 0 0 1 4 4v2M3 18h18"/><circle cx="7.5" cy="9.5" r="1.6"/></svg>;
    case "euro":      return <svg {...props}><path d="M17 6.5A6.5 6.5 0 1 0 17 17.5"/><path d="M4 10h9M4 14h9"/></svg>;
    case "percent":   return <svg {...props}><circle cx="7" cy="7" r="2.5"/><circle cx="17" cy="17" r="2.5"/><path d="M19 5L5 19"/></svg>;
    case "link":      return <svg {...props}><path d="M10 13a5 5 0 0 0 7 0l2-2a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-2 2a5 5 0 0 0 7 7l1-1"/></svg>;
    case "eye":       return <svg {...props}><path d="M2 12s3.6-6.5 10-6.5S22 12 22 12s-3.6 6.5-10 6.5S2 12 2 12z"/><circle cx="12" cy="12" r="2.6"/></svg>;
    case "send":      return <svg {...props}><path d="M21 3L3 10.5l7 3 3 7z"/><path d="M21 3l-8 11"/></svg>;
    case "grip":      return <svg {...props}><circle cx="9" cy="6" r="1.3" fill={stroke}/><circle cx="15" cy="6" r="1.3" fill={stroke}/><circle cx="9" cy="12" r="1.3" fill={stroke}/><circle cx="15" cy="12" r="1.3" fill={stroke}/><circle cx="9" cy="18" r="1.3" fill={stroke}/><circle cx="15" cy="18" r="1.3" fill={stroke}/></svg>;
    case "trash":     return <svg {...props}><path d="M4 7h16M9 7V4h6v3M6 7l1 14h10l1-14"/></svg>;
    case "at":        return <svg {...props}><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 5 2 9 9 0 1 0-3.5 4.5"/></svg>;
    case "settings":  return <svg {...props}><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/></svg>;
    case "bell":      return <svg {...props}><path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6z"/><path d="M10 19a2 2 0 0 0 4 0"/></svg>;
    case "flow":      return <svg {...props}><rect x="3" y="3" width="6" height="5" rx="1"/><rect x="15" y="16" width="6" height="5" rx="1"/><path d="M6 8v6a4 4 0 0 0 4 4h5"/></svg>;
    case "layers":    return <svg {...props}><path d="M12 3l9 5-9 5-9-5z"/><path d="M3 13l9 5 9-5"/></svg>;
    case "database":  return <svg {...props}><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>;
    case "key":       return <svg {...props}><circle cx="8" cy="14" r="4"/><path d="M11 11l9-9M17 5l2 2M14 8l2 2"/></svg>;
    case "wilt":      return <svg {...props}><path d="M12 21V11"/><path d="M12 11c-4 0-6-2.5-6-6 3.5 0 6 2 6 6z"/><path d="M12 14c3 0 5 1.5 6 4-3 .5-5-.5-6-4z"/></svg>;
    default: return null;
  }
}

// ─── atoms ───
function Mono({ children, size = 10.5, color = DIM, style }) {
  return <span style={{ fontFamily: MONO, fontSize: size, color, letterSpacing: "0.04em", ...style }}>{children}</span>;
}
function Eyebrow({ children, color = FUCHSIA, style }) {
  return <div style={{ fontFamily: MONO, fontSize: 10.5, color, fontWeight: 700, letterSpacing: "0.2em", textTransform: "uppercase", ...style }}>{children}</div>;
}
function Dot({ color, size = 6, pulse = false, style }) {
  return <span style={{
    display: "inline-block", width: size, height: size, borderRadius: "50%",
    background: color, flexShrink: 0,
    animation: pulse ? "gm-dot-pulse 1.6s ease-in-out infinite" : "none", ...style,
  }} />;
}
function Pill({ children, tone = "now", style }) {
  const map = {
    now:  { bg: "#E9F3EC", fg: "#256B45", bd: "#BFDDC9" },
    soon: { bg: "#FBF0FD", fg: FUCHSIA_DARK, bd: FUCHSIA_PALE },
    tbc:  { bg: "#F3F0E8", fg: "#6C6350", bd: RULE },
  }[tone];
  return <span style={{
    display: "inline-flex", alignItems: "center", gap: 5, fontFamily: MONO, fontSize: 9.5,
    fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase",
    padding: "3px 7px", borderRadius: 4, background: map.bg, color: map.fg,
    border: `1px solid ${map.bd}`, whiteSpace: "nowrap", ...style,
  }}>{children}</span>;
}
function Avatar({ who, size = 20, ring }) {
  const u = USER_BY_ID[who] || { initials: "?", tint: FAINT };
  return <span title={u.name} style={{
    width: size, height: size, borderRadius: "50%", background: u.tint + "22",
    border: `1px solid ${u.tint}66`, color: u.tint, fontFamily: MONO, fontSize: size * 0.42,
    fontWeight: 700, display: "inline-flex", alignItems: "center", justifyContent: "center",
    flexShrink: 0, boxShadow: ring ? `0 0 0 2px ${SURFACE}` : "none", letterSpacing: 0,
  }}>{u.initials}</span>;
}
function AccountBadge({ id, size = 22 }) {
  const a = ACC_BY_ID[id];
  if (!a) return null;
  return <span style={{
    width: size, height: size, borderRadius: 5, background: a.tint + "1f",
    border: `1px solid ${a.tint}55`, color: a.tint, fontFamily: MONO,
    fontSize: size * 0.36, fontWeight: 700, display: "inline-flex",
    alignItems: "center", justifyContent: "center", flexShrink: 0,
  }}>{a.initials}</span>;
}

// ─── formatting ───
function eur(n, opts = {}) {
  if (n == null) return ", ";
  const { k = true } = opts;
  if (k && Math.abs(n) >= 1000) {
    const v = n / 1000;
    return "€" + (Math.abs(v) >= 100 ? Math.round(v) : (Math.round(v * 10) / 10)).toLocaleString("en-US") + "k";
  }
  return "€" + Math.round(n).toLocaleString("en-US");
}
function eurFull(n) { return "€" + Math.round(n).toLocaleString("en-US"); }
function num(n) { return (n ?? 0).toLocaleString("en-US"); }
function pct(n, d = 1) { return (Math.round(n * 10 ** d) / 10 ** d).toFixed(d) + "%"; }
const MONTHS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
function shortDate(iso) {
  if (!iso) return ", ";
  const [y, m, d] = iso.split("-").map(Number);
  return `${d} ${MONTHS[m - 1]}`;
}
function longDate(iso) {
  if (!iso) return ", ";
  const [y, m, d] = iso.split("-").map(Number);
  return `${d} ${MONTHS[m - 1]} ${y}`;
}
// deterministic pseudo-random for generated seed rows
function lcg(seed) { let s = seed % 2147483647; if (s <= 0) s += 2147483646; return () => (s = (s * 16807) % 2147483647) / 2147483647; }

// ─── team ───
const USERS = [
  { id: "ko", name: "Adriana Vestri",  email: "a.vestri@mareahotels.example", initials: "AV", role: "Owner",  tint: FUCHSIA_DEEP, crm: "Both" },
  { id: "ml", name: "Rubén Calbet",    email: "r.calbet@mareahotels.example",  initials: "RC", role: "Admin",  tint: PLUM,   crm: "Both" },
  { id: "yf", name: "Noelia Ferrán", email: "n.ferran@mareahotels.example", initials: "NF", role: "Member", tint: "#8E5BA8", crm: "B2B only" },
  { id: "me", name: "Tomás Orvieto",  email: "t.orvieto@mareahotels.example", initials: "TO", role: "Member", tint: "#B08D57",  crm: "B2B only" },
  { id: "da", name: "Damià Solans",  email: "d.solans@mareahotels.example", initials: "DS", role: "Member", tint: FUCHSIA_DARK,  crm: "Both" },
];
const USER_BY_ID = Object.fromEntries(USERS.map(u => [u.id, u]));

// ─── accounts ───
const ACC_TYPES = ["Travel Agency", "Tour Operator", "DMC", "Corporate Booking", "Event Organizer", "Incentive Agency", "Wholesaler", "Other"];
const ACCOUNTS = [
  { id: "tuig", name: "Nordwind Travel Group", legal: "Nordwind Travel Group PLC", initials: "NW", tint: PLUM, type: "Tour Operator", profile: "CORP",
    manager: "ko", status: "Active", cif: ", ", iata: "78200000", cm: "NORDGRP", crs: "NORD-MASTER", engineId: "eng_nord_master",
    promo: ["NORDMASTER27"], commission: 12, settlement: "Monthly · net 30", credit: 250000, payDays: 30, cancellation: "14 days · 20% penalty",
    contract: { from: "2025-01-01", to: "2026-12-31", notice: 60 }, rnCommitment: 9000, children: ["tuies"], parent: null,
    contacts: ["c-mh", "c-np"], openDeals: 1, contactCount: 6,
    kpi: { openPipeline: 148000, wonYTD: 216000, rnYTD: 5240, commissionAccrued: 51400, conversion: 46 },
    prod: { res: 612, rn: 4120, revenue: 179200, adr: 118, cancelRate: 6.4, leadTime: 41, commissionCost: 21504 } },
  { id: "vec", name: "Viajes Altamar", legal: "Altamar Viajes SA", initials: "VA", tint: FUCHSIA_DEEP, type: "Travel Agency", profile: "MICE",
    manager: "ko", status: "Active", cif: "A28229813", iata: "78200011", cm: "ALTA", crs: "ALTA-ES", engineId: "eng_veci_01",
    promo: ["ALTA2026", "ALTAMICE"], commission: 10, settlement: "Monthly · net 45", credit: 180000, payDays: 45, cancellation: "21 days · 15% penalty",
    contract: { from: "2025-03-01", to: "2027-02-28", notice: 60 }, rnCommitment: 3200, children: [], parent: null,
    contacts: ["c-pm", "c-ja"], openDeals: 2, contactCount: 5,
    kpi: { openPipeline: 138000, wonYTD: 96000, rnYTD: 2860, commissionAccrued: 13850, conversion: 52 },
    prod: { res: 438, rn: 2860, revenue: 138500, adr: 112, cancelRate: 7.8, leadTime: 34, commissionCost: 13850 } },
  { id: "cwt", name: "Meridian Business Travel", legal: "Meridian Business Travel Iberia SLU", initials: "MB", tint: "#B08D57", type: "Travel Agency", profile: "CORP",
    manager: "da", status: "Active", cif: "B84239117", iata: "78201188", cm: "MERIB", crs: "MER-IB", engineId: "eng_mer_ib",
    promo: ["MERCORP26"], commission: 8, settlement: "Quarterly · net 30", credit: 120000, payDays: 30, cancellation: "7 days · no penalty",
    contract: { from: "2024-06-01", to: "2026-09-30", notice: 90 }, rnCommitment: 2000, children: [], parent: null,
    contacts: ["c-mf"], openDeals: 1, contactCount: 3,
    kpi: { openPipeline: 60000, wonYTD: 54000, rnYTD: 1490, commissionAccrued: 8912, conversion: 38 },
    prod: { res: 349, rn: 1490, revenue: 111400, adr: 128, cancelRate: 5.1, leadTime: 22, commissionCost: 8912 } },
  { id: "nord", name: "Boreal Incentives", legal: "Boreal Incentives AB", initials: "BI", tint: "#8E5BA8", type: "Incentive Agency", profile: "MICE",
    manager: "ml", status: "Prospect", cif: "SE5566123401", iata: ", ", cm: "BOREALINC", crs: ", ", engineId: "eng_nord_01",
    promo: ["BOREAL26"], commission: 11, settlement: "Per event · net 15", credit: 60000, payDays: 15, cancellation: "30 days · 25% penalty",
    contract: { from: "2026-02-01", to: "2027-01-31", notice: 45 }, rnCommitment: 900, children: [], parent: null,
    contacts: ["c-el", "c-sb"], openDeals: 3, contactCount: 4,
    kpi: { openPipeline: 92000, wonYTD: 42000, rnYTD: 610, commissionAccrued: 5940, conversion: 31 },
    prod: { res: 84, rn: 610, revenue: 54000, adr: 152, cancelRate: 9.2, leadTime: 63, commissionCost: 5940 } },
  { id: "pfz", name: "Helvia Congresses", legal: "Helvia Events & Congresses", initials: "HC", tint: FUCHSIA_DARK, type: "Event Organizer", profile: "MICE",
    manager: "yf", status: "Active", cif: "A28123456", iata: ", ", cm: "HELEV", crs: ", ", engineId: "eng_hel_01",
    promo: ["HELCONG26"], commission: 0, settlement: "Per event · net 30", credit: 200000, payDays: 30, cancellation: "45 days · 30% penalty",
    contract: { from: "2025-09-01", to: "2027-08-31", notice: 60 }, rnCommitment: 1600, children: [], parent: null,
    contacts: ["c-cr", "c-jv"], openDeals: 2, contactCount: 4,
    kpi: { openPipeline: 79500, wonYTD: 52000, rnYTD: 1180, commissionAccrued: 0, conversion: 44 },
    prod: { res: 142, rn: 1180, revenue: 96400, adr: 164, cancelRate: 4.2, leadTime: 78, commissionCost: 0 } },
  { id: "amex", name: "Continental Corporate", legal: "Continental Corporate Travel Group", initials: "CC", tint: FUCHSIA, type: "Travel Agency", profile: "CORP",
    manager: "ml", status: "Active", cif: "B87654321", iata: "78200455", cm: "CONTCT", crs: "CONT-CT", engineId: "eng_cont_01",
    promo: ["CONTCORP27"], commission: 9, settlement: "Monthly · net 30", credit: 300000, payDays: 30, cancellation: "14 days · 10% penalty",
    contract: { from: "2024-01-01", to: "2026-12-31", notice: 90 }, rnCommitment: 4200, children: [], parent: null,
    contacts: ["c-hw"], openDeals: 2, contactCount: 7,
    kpi: { openPipeline: 138000, wonYTD: 124000, rnYTD: 2410, commissionAccrued: 17820, conversion: 49 },
    prod: { res: 402, rn: 2410, revenue: 198000, adr: 132, cancelRate: 6.9, leadTime: 18, commissionCost: 17820 } },
  { id: "kuoni", name: "Talora DMC Madrid", legal: "Talora Destination Management SL", initials: "TD", tint: "#9B3F6E", type: "DMC", profile: "MICE",
    manager: "yf", status: "Active", cif: "B81234567", iata: "78203344", cm: "TALORAMAD", crs: "TAL-MAD", engineId: "eng_tal_01",
    promo: ["TALWIN27"], commission: 13, settlement: "Monthly · net 60", credit: 90000, payDays: 60, cancellation: "21 days · 20% penalty",
    contract: { from: "2025-11-01", to: "2027-10-31", notice: 60 }, rnCommitment: 1400, children: [], parent: null,
    contacts: ["c-rd"], openDeals: 1, contactCount: 3,
    kpi: { openPipeline: 34500, wonYTD: 52000, rnYTD: 980, commissionAccrued: 9620, conversion: 41 },
    prod: { res: 168, rn: 980, revenue: 74000, adr: 138, cancelRate: 8.1, leadTime: 52, commissionCost: 9620 } },
  { id: "iber", name: "Vega Air Corporate", legal: "Vega Air Corporate Travel SA", initials: "VC", tint: "#A8794A", type: "Corporate Booking", profile: "CORP",
    manager: "ko", status: "Active", cif: "A85105396", iata: "78200077", cm: "VEGACORP", crs: "VEGA-CORP", engineId: "eng_vega_01",
    promo: ["VEGACREW26"], commission: 6, settlement: "Monthly · net 30", credit: 140000, payDays: 30, cancellation: "48h · no penalty",
    contract: { from: "2025-01-01", to: "2026-12-31", notice: 60 }, rnCommitment: 5600, children: [], parent: null,
    contacts: ["c-an"], openDeals: 2, contactCount: 4,
    kpi: { openPipeline: 48000, wonYTD: 42000, rnYTD: 3980, commissionAccrued: 6240, conversion: 57 },
    prod: { res: 720, rn: 3980, revenue: 104000, adr: 92, cancelRate: 3.4, leadTime: 9, commissionCost: 6240 } },
  { id: "tuies", name: "Nordwind España", legal: "Nordwind España Turismo SAU", initials: "NE", tint: PLUM, type: "Tour Operator", profile: "MICE",
    manager: "ml", status: "Active", cif: "B08123456", iata: "78200012", cm: "NORDES", crs: "NORD-ES", engineId: "eng_nord_es",
    promo: ["NORDES26"], commission: null, settlement: "Inherited from Nordwind Travel Group", credit: null, payDays: null, cancellation: "Inherited from Nordwind Travel Group",
    contract: { from: "2025-01-01", to: "2026-12-31", notice: 60 }, rnCommitment: 2400, children: [], parent: "tuig",
    contacts: ["c-np"], openDeals: 1, contactCount: 4,
    kpi: { openPipeline: 28000, wonYTD: 66000, rnYTD: 1120, commissionAccrued: 12960, conversion: 43 },
    prod: { res: 214, rn: 1120, revenue: 108000, adr: 96, cancelRate: 7.2, leadTime: 36, commissionCost: 12960 } },
];
const ACC_BY_ID = Object.fromEntries(ACCOUNTS.map(a => [a.id, a]));

// ─── contacts (B2B contacts are ordinary platform contacts with type = b2b) ───
const CONTACTS = [
  { id: "c-pm", name: "Amaia Torrellas",    email: "a.torrellas@altamar.example", phone: "+34 600 112 233", role: "Head of Groups",         acc: "vec",   primary: true,  lastAct: "2 Aug", emails: 34, openPipeline: 138000, langs: "ES · EN" },
  { id: "c-ja", name: "Guiu Terrats",    email: "g.terrats@altamar.example", phone: "+34 600 445 611", role: "MICE Coordinator",       acc: "vec",   primary: false, lastAct: "28 Jul", emails: 12, openPipeline: 62000, langs: "ES · CA" },
  { id: "c-mf", name: "Sílvia Roldós",    email: "s.roldos@meridianbt.example",       phone: "+34 913 552 100", role: "Account Director",       acc: "cwt",   primary: true,  lastAct: "5 Aug", emails: 21, openPipeline: 60000, langs: "ES · EN" },
  { id: "c-el", name: "Ansgar Melby",  email: "a.melby@borealincentives.example", phone: "+46 8 551 220", role: "Programme Director", acc: "nord", primary: true, lastAct: "1 Aug", emails: 18, openPipeline: 92000, langs: "SV · EN" },
  { id: "c-sb", name: "Linnea Falk",    email: "l.falk@borealincentives.example", phone: "+46 8 551 244", role: "Event Producer",    acc: "nord",  primary: false, lastAct: "22 Jul", emails: 6, openPipeline: 64000, langs: "SV · EN" },
  { id: "c-cr", name: "Ornella Baldi",   email: "o.baldi@helvia.example", phone: "+39 02 445 100",  role: "Congress Lead",         acc: "pfz",   primary: true,  lastAct: "6 Aug", emails: 41, openPipeline: 79500, langs: "IT · EN" },
  { id: "c-jv", name: "Ismael Duarte",    email: "i.duarte@helvia.example", phone: "+34 917 220 331", role: "Procurement",           acc: "pfz",   primary: false, lastAct: "30 Jul", emails: 9, openPipeline: 42000, langs: "ES" },
  { id: "c-hw", name: "Rowena Ashcombe", email: "r.ashcombe@continentalct.example", phone: "+44 20 7101 4400", role: "Global Hotel Sourcing", acc: "amex",  primary: true,  lastAct: "7 Aug", emails: 27, openPipeline: 138000, langs: "EN" },
  { id: "c-rd", name: "Marisol Ibarrena",   email: "m.ibarrena@talora.example",     phone: "+34 915 330 780", role: "Groups Manager",         acc: "kuoni", primary: true,  lastAct: "29 Jul", emails: 15, openPipeline: 34500, langs: "ES · EN · DE" },
  { id: "c-an", name: "Ainhoa Sedano",  email: "a.sedano@vegaair.example",    phone: "+34 902 400 500", role: "Crew Logistics",         acc: "iber",  primary: true,  lastAct: "4 Aug", emails: 11, openPipeline: 48000, langs: "ES · EN" },
  { id: "c-np", name: "Ilse Vandeput",      email: "i.vandeput@nordwind.example",          phone: "+34 971 300 220", role: "Product Manager Spain",  acc: "tuies", primary: true,  lastAct: "3 Aug", emails: 23, openPipeline: 28000, langs: "ES · DE · EN" },
  { id: "c-mh", name: "Lennart Brauwe",   email: "l.brauwe@nordwind.example",        phone: "+49 511 566 100", role: "VP Hotel Sourcing",      acc: "tuig",  primary: true,  lastAct: "31 Jul", emails: 30, openPipeline: 148000, langs: "DE · EN" },
];
const CT_BY_ID = Object.fromEntries(CONTACTS.map(c => [c.id, c]));

// ─── pipelines & stages ───
// cat: qualification | proposal | negotiation | won | lost — gates and weighted forecast hang off the category.
const PIPELINES = [
  {
    id: "corp", label: "Corporate / FIT", icon: "briefcase",
    stages: [
      { id: "qual",  name: "Qualification",       cat: "qualification", color: SLATE,   prob: 10,  rot: 14, approval: null },
      { id: "prop",  name: "Proposal Sent",       cat: "proposal",      color: VIOLET,  prob: 35,  rot: 21, approval: null },
      { id: "nego",  name: "Negotiation",         cat: "negotiation",   color: FUCHSIA, prob: 60,  rot: 30, approval: "ml" },
      { id: "won",   name: "Won, Contract Signed", cat: "won",         color: GREEN,   prob: 100, rot: null, approval: null },
      { id: "lost",  name: "Lost",                cat: "lost",          color: RED,     prob: 0,   rot: null, approval: null },
    ],
  },
  {
    id: "mice", label: "Groups & Events (MICE)", icon: "cal",
    stages: [
      { id: "rfp",   name: "RFP Received",  cat: "qualification", color: SLATE,   prob: 15,  rot: 10, approval: null },
      { id: "quote", name: "Quote Sent",    cat: "proposal",      color: VIOLET,  prob: 40,  rot: 14, approval: null },
      { id: "mnego", name: "Negotiation",   cat: "negotiation",   color: FUCHSIA, prob: 65,  rot: 21, approval: "ml" },
      { id: "mwon",  name: "Won",           cat: "won",           color: GREEN,   prob: 100, rot: null, approval: null },
      { id: "mlost", name: "Lost",          cat: "lost",          color: RED,     prob: 0,   rot: null, approval: null },
    ],
  },
];
const PIPE_BY_ID = Object.fromEntries(PIPELINES.map(p => [p.id, p]));
function stageOf(pid, sid) { return PIPE_BY_ID[pid].stages.find(s => s.id === sid); }

const LOSS_REASONS = ["Price", "Availability", "Event cancelled", "Competitor", "No response", "Other"];
const RATE_TYPES = ["Negotiated corporate (BAR-minus)", "Static net rate", "Dynamic % discount", "Group series rate"];
const HOTELS = ["Marea Nerja Beach", "Marea Sierra Blanca", "Marea Club Marbella", "Marea Jardines del Sur", "Marea Cala Blava", "Marea Bahía Norte"];

// ─── deals ───
// Corporate deals carry rate/allotment/promo fields; MICE deals carry event fields.
const DEALS = [
  { id: "d1", pid: "corp", stage: "qual", title: "Continental Corporate, 2027 corporate rate agreement", acc: "amex", value: 96000,
    owner: "ml", contact: "c-hw", created: "2026-07-12", close: "2026-10-30", inStage: 12, lastAct: 3,
    next: { type: "Call", date: "2026-08-14", state: "planned" },
    f: { rateType: RATE_TYPES[0], roomNights: 1800, adr: 118, allotment: 24, release: 14, hotels: ["Marea Sierra Blanca", "Marea Club Marbella"], promo: "", payTerms: false },
    tags: ["Multi-property", "RFP season"] },
  { id: "d2", pid: "corp", stage: "qual", title: "Vega Air Corporate, crew & staff programme", acc: "iber", value: 48000,
    owner: "ko", contact: "c-an", created: "2026-07-04", close: "2026-11-15", inStage: 21, lastAct: 19,
    next: null,
    f: { rateType: RATE_TYPES[1], roomNights: 2600, adr: 74, allotment: 12, release: 2, hotels: ["Marea Bahía Norte"], promo: "", payTerms: false },
    tags: ["Renewal"] },
  { id: "d3", pid: "corp", stage: "qual", title: "Meridian Business Travel, Q4 corporate top-up", acc: "cwt", value: 28000,
    owner: "da", contact: "c-mf", created: "2026-08-01", close: "2026-09-30", inStage: 5, lastAct: 1,
    next: { type: "Task", date: "2026-08-13", state: "today" },
    f: { rateType: RATE_TYPES[2], roomNights: 420, adr: 128, allotment: 8, release: 7, hotels: ["Marea Nerja Beach"], promo: "", payTerms: false },
    tags: [] },
  { id: "d4", pid: "corp", stage: "prop", title: "Viajes Altamar, leisure series 2027", acc: "vec", value: 62000,
    owner: "ko", contact: "c-pm", created: "2026-06-18", close: "2026-09-15", inStage: 9, lastAct: 2,
    next: { type: "Proposal prep", date: "2026-08-18", state: "planned" },
    f: { rateType: RATE_TYPES[3], roomNights: 1400, adr: 96, allotment: 30, release: 21, hotels: ["Marea Cala Blava", "Marea Jardines del Sur"], promo: "", payTerms: false },
    tags: ["Series"] },
  { id: "d5", pid: "corp", stage: "prop", title: "Talora DMC Madrid, inbound allotment winter", acc: "kuoni", value: 34500,
    owner: "yf", contact: "c-rd", created: "2026-06-30", close: "2026-10-10", inStage: 16, lastAct: 6,
    next: { type: "Virtual meeting", date: "2026-08-12", state: "overdue" },
    f: { rateType: RATE_TYPES[1], roomNights: 780, adr: 88, allotment: 16, release: 14, hotels: ["Marea Sierra Blanca"], promo: "", payTerms: false },
    tags: ["Inbound"] },
  { id: "d6", pid: "corp", stage: "prop", title: "Boreal Incentives, corporate allotment pilot", acc: "nord", value: 28000,
    owner: "ml", contact: "c-el", created: "2026-07-21", close: "2026-10-01", inStage: 7, lastAct: 4,
    next: { type: "Call", date: "2026-08-15", state: "planned" },
    f: { rateType: RATE_TYPES[2], roomNights: 360, adr: 142, allotment: 10, release: 30, hotels: ["Marea Club Marbella"], promo: "", payTerms: false },
    tags: ["Pilot"] },
  { id: "d7", pid: "corp", stage: "nego", title: "Nordwind Travel Group, 2027 master contract", acc: "tuig", value: 148000,
    owner: "ko", contact: "c-mh", created: "2026-05-06", close: "2026-09-30", inStage: 18, lastAct: 2,
    next: { type: "Contract signature", date: "2026-08-20", state: "planned" },
    approval: { state: "pending", by: "ml", requested: "2026-08-10", note: "Rate 4% below floor on 3 properties, Revenue sign-off needed." },
    f: { rateType: RATE_TYPES[0], roomNights: 9000, adr: 104, allotment: 60, release: 21, hotels: HOTELS.slice(0, 5), promo: "NORDMASTER27", payTerms: true },
    tags: ["Master contract", "Revenue review"] },
  { id: "d8", pid: "corp", stage: "nego", title: "Viajes Altamar, Balearics allotment", acc: "vec", value: 76000,
    owner: "ko", contact: "c-pm", created: "2026-05-28", close: "2026-09-12", inStage: 11, lastAct: 5,
    next: { type: "Credit review", date: "2026-08-19", state: "planned" },
    f: { rateType: RATE_TYPES[3], roomNights: 1600, adr: 108, allotment: 34, release: 14, hotels: ["Marea Cala Blava", "Marea Bahía Norte"], promo: "ALTA2026", payTerms: true },
    tags: [] },
  { id: "d9", pid: "corp", stage: "nego", title: "Continental Corporate, Madrid corporate rate", acc: "amex", value: 42000,
    owner: "ml", contact: "c-hw", created: "2026-06-11", close: "2026-08-29", inStage: 34, lastAct: 33,
    next: null,
    f: { rateType: RATE_TYPES[0], roomNights: 610, adr: 136, allotment: 10, release: 7, hotels: ["Marea Sierra Blanca"], promo: "", payTerms: false },
    tags: [] },
  { id: "d10", pid: "corp", stage: "nego", title: "Meridian Business Travel, 2027 renewal", acc: "cwt", value: 32000,
    owner: "da", contact: "c-mf", created: "2026-07-01", close: "2026-09-20", inStage: 8, lastAct: 3,
    next: { type: "Call", date: "2026-08-18", state: "planned" },
    f: { rateType: RATE_TYPES[2], roomNights: 540, adr: 124, allotment: 12, release: 7, hotels: ["Marea Nerja Beach"], promo: "MERCORP26", payTerms: true },
    tags: ["Renewal"] },
  { id: "d11", pid: "corp", stage: "won", title: "Nordwind España, winter series 2026", acc: "tuies", value: 66000,
    owner: "ko", contact: "c-np", created: "2026-02-14", close: "2026-06-30", inStage: 42, lastAct: 42, won: "2026-06-30",
    next: null,
    f: { rateType: RATE_TYPES[3], roomNights: 1120, adr: 96, allotment: 26, release: 21, hotels: ["Marea Cala Blava"], promo: "NORDES26", payTerms: true },
    tags: ["Signed"] },
  { id: "d12", pid: "corp", stage: "won", title: "Vega Air Corporate, 2026 rate agreement", acc: "iber", value: 42000,
    owner: "da", contact: "c-an", created: "2026-01-20", close: "2026-04-15", inStage: 118, lastAct: 61, won: "2026-04-15",
    next: null,
    f: { rateType: RATE_TYPES[1], roomNights: 3980, adr: 92, allotment: 14, release: 2, hotels: ["Marea Bahía Norte"], promo: "VEGACREW26", payTerms: true },
    tags: ["Signed"] },
  { id: "d13", pid: "corp", stage: "lost", title: "Helvia Congresses, Barcelona incentive weekend", acc: "pfz", value: 18000,
    owner: "yf", contact: "c-jv", created: "2026-03-02", close: "2026-05-20", inStage: 84, lastAct: 84,
    lost: { reason: "Price", note: "Competitor bid 12% under on a 4-night package.", date: "2026-05-20", penalty: 0 },
    next: null,
    f: { rateType: RATE_TYPES[2], roomNights: 180, adr: 148, allotment: 0, release: 0, hotels: ["Marea Club Marbella"], promo: "", payTerms: false },
    tags: [] },
  // ── MICE ──
  { id: "m1", pid: "mice", stage: "rfp", title: "Helvia Medical Congress Oct 2026", acc: "pfz", value: 42000,
    owner: "yf", contact: "c-cr", created: "2026-08-15", close: "2026-09-20", inStage: 48, lastAct: 48,
    next: { type: "Plan", date: "2026-08-15", state: "today" },
    f: { from: "2026-10-06", to: "2026-10-09", pax: 320, rooms: 180, board: "Half board", space: "Auditorium + 4 breakouts",
         accBudget: 34000, meetBudget: 8000, locator: "", signed: false, altDates: "13–16 Oct 2026" },
    tags: ["Congress", "Wilting"] },
  { id: "m2", pid: "mice", stage: "rfp", title: "Nordwind España, product roadshow 2027", acc: "tuies", value: 28000,
    owner: "yf", contact: "c-np", created: "2026-10-10", close: "2026-12-15", inStage: 4, lastAct: 2,
    next: { type: "Site visit", date: "2026-07-29", state: "planned" },
    f: { from: "2027-02-03", to: "2027-02-05", pax: 140, rooms: 70, board: "Full board", space: "Ballroom + 2 breakouts",
         accBudget: 21000, meetBudget: 7000, locator: "", signed: false, altDates: "10–12 Feb 2027" },
    tags: ["Roadshow"] },
  { id: "m3", pid: "mice", stage: "quote", title: "Helvia Iberia, regional managers offsite", acc: "pfz", value: 37500,
    owner: "me", contact: "c-cr", created: "2026-09-08", close: "2026-10-04", inStage: 9, lastAct: 4,
    next: { type: "Call", date: "2026-08-04", state: "planned" },
    f: { from: "2026-11-12", to: "2026-11-14", pax: 90, rooms: 62, board: "Half board", space: "2 meeting rooms + gala dinner",
         accBudget: 27500, meetBudget: 10000, locator: "", signed: false, altDates: "19–21 Nov 2026" },
    tags: ["Offsite"] },
  { id: "m4", pid: "mice", stage: "mnego", title: "Boreal Incentives, leadership retreat", acc: "nord", value: 64000,
    owner: "ml", contact: "c-el", created: "2026-09-25", close: "2026-10-18", inStage: 12, lastAct: 1,
    next: { type: "Virtual meeting", date: "2026-08-01", state: "planned" },
    f: { from: "2026-10-20", to: "2026-10-23", pax: 60, rooms: 48, board: "All inclusive", space: "Boardroom + beach venue",
         accBudget: 46000, meetBudget: 18000, locator: "", signed: false, altDates: ", " },
    tags: ["Incentive", "Exclusive use"] },
  { id: "m5", pid: "mice", stage: "mwon", title: "Talora DMC, Madrid gala dinner series", acc: "kuoni", value: 52000,
    owner: "yf", contact: "c-rd", created: "2026-04-02", close: "2026-06-12", inStage: 61, lastAct: 61, won: "2026-06-12",
    next: null,
    f: { from: "2026-09-18", to: "2026-09-20", pax: 240, rooms: 120, board: "Bed & breakfast", space: "Gala hall",
         accBudget: 38000, meetBudget: 14000, locator: "PMS-4471982", signed: true, altDates: ", " },
    tags: ["Signed"] },
  { id: "m6", pid: "mice", stage: "mwon", title: "Boreal Incentives, Q1 sales kickoff", acc: "nord", value: 42000,
    owner: "ml", contact: "c-sb", created: "2026-01-18", close: "2026-03-28", inStage: 74, lastAct: 74, won: "2026-03-28",
    next: null,
    f: { from: "2026-02-24", to: "2026-02-27", pax: 110, rooms: 66, board: "Full board", space: "Conference room + 3 breakouts",
         accBudget: 31000, meetBudget: 11000, locator: "PMS-4390115", signed: true, altDates: ", " },
    tags: ["Signed"] },
];

// ─── per-deal detail (timeline, emails, docs, proposal) ───
const DEAL_NOTES = {
  d7: [
    { who: "ko", when: "2 days ago", pinned: true, text: "Lennart wants the master rate to cover 5 properties from Jan. @ml needs to sign off on the 4% below-floor on Marbella." },
    { who: "ml", when: "2 days ago", pinned: false, text: "Reviewing. Floor is defensible if allotment lands at 60 rooms and release moves to 21 days." },
    { who: "ko", when: "6 days ago", pinned: false, text: "Legal sent the redlines back, cancellation window agreed at 14 days with 20% penalty." },
  ],
  m1: [
    { who: "yf", when: "48 days ago", pinned: true, text: "RFP arrived through the groups mailbox and was auto-drafted. Confirmed pax 320, two date options." },
    { who: "yf", when: "45 days ago", pinned: false, text: "Waiting on the congress agenda before we can price the breakouts. No reply since, deal is wilting." },
  ],
  m4: [
    { who: "ml", when: "yesterday", pinned: true, text: "Ansgar asked for exclusive use of the beach venue on the 21st. Quoted +€6k, awaiting board approval on their side." },
  ],
};
const DEAL_EMAILS = {
  d7: [
    { dir: "out", who: "ko", subj: "Nordwind Travel Group, 2027 master contract, revised rate grid", when: "10 Aug · 09:12", opens: 4, clicks: 2, state: "Replied" },
    { dir: "in",  who: "c-mh", subj: "RE: Nordwind Travel Group, 2027 master contract, revised rate grid", when: "10 Aug · 14:40", opens: null, clicks: null, state: "Received" },
    { dir: "out", who: "ko", subj: "Proposal · Nordwind Travel Group master agreement 2027 (DE)", when: "4 Aug · 17:03", opens: 9, clicks: 3, state: "Viewed 9×" },
  ],
  m1: [
    { dir: "in",  who: "c-cr", subj: "RFP · Medical Congress Barcelona October 2026", when: "28 Jun · 08:22", opens: null, clicks: null, state: "Auto-drafted deal" },
    { dir: "out", who: "yf", subj: "Helvia Congress, availability and first figures", when: "29 Jun · 11:05", opens: 3, clicks: 1, state: "Opened" },
    { dir: "out", who: "yf", subj: "Following up · congress agenda for breakout pricing", when: "12 Jul · 10:00", opens: 0, clicks: 0, state: "Not opened" },
  ],
  m4: [
    { dir: "out", who: "ml", subj: "Leadership retreat · exclusive beach venue option", when: "11 Aug · 16:20", opens: 2, clicks: 1, state: "Opened" },
  ],
};
const DEAL_DOCS = {
  d7: [
    { name: "TUI-master-2027-redlines-v4.pdf", size: "412 KB", by: "ko", when: "6 Aug", kind: "Contract" },
    { name: "rate-grid-2027-5-properties.xlsx", size: "88 KB", by: "ml", when: "4 Aug", kind: "Rates" },
  ],
  m1: [{ name: "helvia-congress-RFP.pdf", size: "1.2 MB", by: "yf", when: "28 Jun", kind: "RFP" }],
  m4: [{ name: "boreal-retreat-quote-v2.pdf", size: "640 KB", by: "ml", when: "9 Aug", kind: "Proposal" }],
};
const DEAL_PROPOSAL = {
  d7: { status: "Accepted", ref: "PR-2026-0184", views: 9, langs: ["EN", "DE"], sent: "4 Aug", accepted: "9 Aug", signer: "Lennart Brauwe", template: "Negotiated corporate rate" },
  m4: { status: "Viewed", ref: "PR-2026-0219", views: 3, langs: ["EN", "SV"], sent: "9 Aug", accepted: null, signer: null, template: "Incentive trip" },
  m1: { status: "Draft", ref: "PR-2026-0231", views: 0, langs: ["EN", "IT"], sent: null, accepted: null, signer: null, template: "Congress & gala dinner" },
  m3: { status: "Sent", ref: "PR-2026-0226", views: 1, langs: ["EN", "ES"], sent: "6 Aug", accepted: null, signer: null, template: "Congress & gala dinner" },
};
const DEAL_HISTORY = {
  d7: [
    { when: "10 Aug 2026 · 09:40", who: "ko", what: "Approval requested to enter Negotiation", tone: FUCHSIA },
    { when: "24 Jul 2026 · 12:18", who: "ko", what: "Stage · Proposal Sent → Negotiation", tone: VIOLET },
    { when: "24 Jul 2026 · 12:17", who: "ko", what: "Promo code set · NORDMASTER27", tone: DIM },
    { when: "2 Jul 2026 · 10:02",  who: "ml", what: "Stage · Qualification → Proposal Sent", tone: SLATE },
    { when: "6 May 2026 · 16:44",  who: "ko", what: "Deal created from account Nordwind Travel Group", tone: DIM },
  ],
  m1: [
    { when: "29 Jun 2026 · 11:02", who: "yf", what: "Draft deal confirmed by owner", tone: SLATE },
    { when: "28 Jun 2026 · 08:24", who: ", ",  what: "Draft created from inbox routing · groups@", tone: FUCHSIA },
  ],
};

// ─── activities ───
const ACTIVITY_TYPES = ["Call", "Email", "Site visit", "Virtual meeting", "Business lunch", "Proposal prep", "Credit review", "Contract signature", "Task", "Note"];
const ACTIVITIES = [
  { id: "a1", type: "Call", title: "Continental Corporate, walk through 2027 rate grid", when: "Today · 10:30", who: "ml", deal: "d1", acc: "amex", state: "today" },
  { id: "a2", type: "Task", title: "Meridian, send Q4 top-up figures", when: "Today · 16:00", who: "da", deal: "d3", acc: "cwt", state: "today" },
  { id: "a3", type: "Virtual meeting", title: "Talora, winter allotment review", when: "12 Aug · 09:00", who: "yf", deal: "d5", acc: "kuoni", state: "overdue" },
  { id: "a4", type: "Proposal prep", title: "ALTA leisure series, build proposal", when: "18 Aug · 11:00", who: "ko", deal: "d4", acc: "vec", state: "planned" },
  { id: "a5", type: "Contract signature", title: "Nordwind Travel Group, master contract signature", when: "20 Aug · 12:00", who: "ko", deal: "d7", acc: "tuig", state: "planned" },
  { id: "a6", type: "Site visit", title: "Helvia congress, venue inspection", when: "9 Aug · 09:30", who: "yf", deal: "m1", acc: "pfz", state: "overdue" },
];

// ─── attributed production / commissions (generated deterministically per account) ───
const MATCH_REASONS = [
  { k: "P1", label: "Agency code · IATA", color: GREEN },
  { k: "P2", label: "Promo code · rate plan", color: FUCHSIA },
  { k: "P3", label: "Booking engine agency id", color: VIOLET },
  { k: "P4", label: "Email domain · suggested", color: AMBER },
];
function bookingsFor(accId, n = 8) {
  const a = ACC_BY_ID[accId];
  const rnd = lcg(accId.split("").reduce((s, c) => s + c.charCodeAt(0), 7) * 131);
  const rows = [];
  for (let i = 0; i < n; i++) {
    const rn = 2 + Math.floor(rnd() * 9);
    const adr = Math.round(a.prod.adr * (0.82 + rnd() * 0.4));
    const p = rnd();
    const reason = p < 0.52 ? MATCH_REASONS[0] : p < 0.8 ? MATCH_REASONS[1] : p < 0.93 ? MATCH_REASONS[2] : MATCH_REASONS[3];
    const m = 1 + Math.floor(rnd() * 7);
    rows.push({
      loc: "PMS-" + (4200000 + Math.floor(rnd() * 99999)),
      hotel: HOTELS[Math.floor(rnd() * HOTELS.length)],
      guest: ["S. Wagner", "M. Johnson", "L. Brenner", "A. Duarte", "K. Nyström", "P. Ricci", "H. Okafor", "T. Muñoz", "J. Salgado", "R. Vermeer"][Math.floor(rnd() * 10)],
      checkin: `2026-0${m}-${String(3 + Math.floor(rnd() * 24)).padStart(2, "0")}`,
      rn, adr, revenue: rn * adr, reason,
      status: rnd() > 0.9 ? "Cancelled" : "Checked out",
    });
  }
  return rows.sort((x, y) => (x.checkin < y.checkin ? 1 : -1));
}
function commissionsFor(accId) {
  const a = ACC_BY_ID[accId];
  const rate = a.commission ?? (a.parent ? ACC_BY_ID[a.parent].commission : 0);
  return bookingsFor(accId, 7).map((b, i) => ({
    loc: b.loc, base: b.revenue, rate, amount: Math.round(b.revenue * rate / 100),
    state: b.status === "Cancelled" ? "Cancelled" : i < 2 ? "Accrued" : i < 4 ? "Invoiced" : "Settled",
    when: b.checkin, inherited: a.commission == null,
  }));
}

Object.assign(window, {
  useState, useEffect, useRef, useMemo,
  FUCHSIA, FUCHSIA_DEEP, FUCHSIA_DARK, FUCHSIA_SOFT, FUCHSIA_PALE, PLUM,
  INK, DIM, FAINT, RULE, RULE_SOFT, PAPER, CREAM, SURFACE, GREEN, AMBER, RED, SLATE, VIOLET,
  MONO, SERIF, SANS, Icon, Mono, Eyebrow, Dot, Pill, Avatar, AccountBadge,
  eur, eurFull, num, pct, shortDate, longDate, MONTHS, lcg,
  USERS, USER_BY_ID, ACCOUNTS, ACC_BY_ID, ACC_TYPES, CONTACTS, CT_BY_ID,
  PIPELINES, PIPE_BY_ID, stageOf, LOSS_REASONS, RATE_TYPES, HOTELS, DEALS,
  DEAL_NOTES, DEAL_EMAILS, DEAL_DOCS, DEAL_PROPOSAL, DEAL_HISTORY,
  ACTIVITY_TYPES, ACTIVITIES, MATCH_REASONS, bookingsFor, commissionsFor,
});
