/* BUSINESS & AI — Roadmap: Gantt multi-track */

// ─── Timeline ────────────────────────────────────────────────
const TL_S  = new Date(2026, 3, 1);   // 1 Abril 2026
const TL_E  = new Date(2026, 11, 31); // 31 Dezembro 2026
const TL_D  = (TL_E - TL_S) / 864e5;
const TODAY = new Date();
const MARCO = new Date(2026, 6, 20);  // entrega Walter F5

function pct(d)   { return ((d - TL_S) / 864e5 / TL_D * 100).toFixed(2) + '%'; }
function spn(s,e) { return ((e - s) / 864e5 / TL_D * 100).toFixed(2) + '%'; }

const MONTHS_TL = [
  { l:'Abr', s:new Date(2026,3,1),  e:new Date(2026,3,30)  },
  { l:'Mai', s:new Date(2026,4,1),  e:new Date(2026,4,31)  },
  { l:'Jun', s:new Date(2026,5,1),  e:new Date(2026,5,30)  },
  { l:'Jul', s:new Date(2026,6,1),  e:new Date(2026,6,31)  },
  { l:'Ago', s:new Date(2026,7,1),  e:new Date(2026,7,31)  },
  { l:'Set', s:new Date(2026,8,1),  e:new Date(2026,8,30)  },
  { l:'Out', s:new Date(2026,9,1),  e:new Date(2026,9,31)  },
  { l:'Nov', s:new Date(2026,10,1), e:new Date(2026,10,30) },
  { l:'Dez', s:new Date(2026,11,1), e:new Date(2026,11,31) },
];

// ─── Week grid lines (every Monday) ──────────────────────────
const WEEKS = [];
(function() {
  var d = new Date(TL_S);
  // advance to first Monday
  var dow = d.getDay();
  if (dow !== 1) d = new Date(d.getTime() + ((dow === 0 ? 1 : 8 - dow) * 864e5));
  while (d <= TL_E) {
    WEEKS.push(new Date(d));
    d = new Date(d.getTime() + 7 * 864e5);
  }
})();

const RESP_COLORS_RM = { JP:'#6366f1', FC:'#0ea5e9', WN:'#10b981', MF:'#f59e0b' };

// Runtime: lê localStorage se disponível, cai para hardcoded
const TRACKS_DATA_DEFAULT = [
  {
    id: 'sprint', label: 'Sprint Marketing MVP', color: '#f43f5e',
    phases: [
      {id:'s01', sh:'S·1', n:'Criar Campanha', d:'Adicionar botao Criar Campanha no briefing aprovado · link para nova campanha pré-preenchida', resp:'FC', prior:'P1', s:new Date(2026,5,3), e:new Date(2026,5,3), cor:'#f43f5e', marco:true},
      {id:'s02', sh:'S·2', n:'Modal campanha + navegacao', d:'Modal pré-preenchido com marca e briefing · navegacao automatica para ecrã Campanhas', resp:'FC', prior:'P1', s:new Date(2026,5,3), e:new Date(2026,5,4), cor:'#f43f5e', xdep:'s01'},
      {id:'s03', sh:'S·3', n:'BD local — tabelas campanha', d:'Criar tabelas campanhas · conceitos · copy_items · criativos · publicacoes em PostgreSQL marketing_local', resp:'FC', prior:'P1', s:new Date(2026,5,4), e:new Date(2026,5,5), cor:'#f43f5e'},
      {id:'s04', sh:'S·4', n:'Conceito + copy + prompt via Claude', d:'Integrar Claude API real · gerar conceito · headline · copy por canal · prompt de imagem', resp:'FC', prior:'P1', s:new Date(2026,5,5), e:new Date(2026,5,9), cor:'#f43f5e', xdep:'s03', marco:true},
      {id:'s05', sh:'S·5', n:'Canvas conteúdos com campanha real', d:'Canvas recebe campanha seleccionada · mostra conceito e copy reais · navegacao por canal', resp:'FC', prior:'P1', s:new Date(2026,5,9), e:new Date(2026,5,9), cor:'#f43f5e', xdep:'s04'},
      {id:'s06', sh:'S·6', n:'Gerar imagem via FLUX 1.1 Pro', d:'Integrar fal.ai FLUX 1.1 Pro · gerar imagem com prompt aprovado · preview + aprovar/regenerar', resp:'FC', prior:'P1', s:new Date(2026,5,10), e:new Date(2026,5,12), cor:'#f43f5e', xdep:'s05', marco:true},
      {id:'s07', sh:'S·7', n:'Aprovacao final criativo', d:'Botao aprovacao final de criativo · actualizar estado para aprovado · pronto para activacao', resp:'FC', prior:'P1', s:new Date(2026,5,11), e:new Date(2026,5,13), cor:'#f43f5e', xdep:'s06'},
      {id:'s08', sh:'S·8', n:'Activacao — fila e agendamento', d:'Fila de aprovados · agendamento por canal · marcar publicado · histórico de publicacoes', resp:'FC', prior:'P1', s:new Date(2026,5,11), e:new Date(2026,5,13), cor:'#f43f5e', xdep:'s07'},
      {id:'sfc', sh:'S·FC', n:'Entrega para deploy', d:'Fabio Costa finaliza todos os ecrãs screen_marketing_* e entrega ao Joao para deploy em producao', resp:'FC', prior:'P1', s:new Date(2026,5,12), e:new Date(2026,5,13), cor:'#f43f5e', marco:true},
      {id:'sjp', sh:'S·JP', n:'Implementacao e deploy sistema (JP)', d:'Joao recebe entrega do FC · implementa no sistema · integracoes backend · deploy em producao', resp:'JP', prior:'P1', s:new Date(2026,5,17), e:new Date(2026,5,18), cor:'#e11d48', xdep:'sfc', marco:true},
      {id:'ph_aiqp46', sh:'S JP', n:'Testes e correções', d:'', resp:'JP', prior:'P1', s:new Date(2026,5,18), e:new Date(2026,5,20), cor:''},
    ],
  },
  {
    id: 'marketing', label: 'Módulo Marketing', color: '#8b5cf6',
    phases: [
      {id:'ph_ik0q2p', sh:'NEW', n:'Fábio Atualizar', d:'', resp:'FC', prior:'P2', s:new Date(2026,5,16), e:new Date(2026,5,17), cor:'', marco:true},
      {id:'mfk01', sh:'MK·FC1', n:'FRAME-KPI', d:'Framework de KPIs · estrutura de semáforos · desvios · formato padrão por ecrã', resp:'FC', prior:'P1', s:new Date(2026,5,9), e:new Date(2026,5,15), cor:'#8b5cf6'},
      {id:'mfk02', sh:'MK·FC2', n:'FRAME-ADS', d:'Framework de Ads · estrutura campanha · budget · KPIs · criativos · targeting', resp:'FC', prior:'P1', s:new Date(2026,5,15), e:new Date(2026,5,18), cor:'#8b5cf6'},
      {id:'mfk03', sh:'MK·FC3', n:'FRAME-EDITORIAL', d:'Framework editorial · calendário · canais · badge de estado · frequência por marca', resp:'FC', prior:'P1', s:new Date(2026,5,19), e:new Date(2026,5,23), cor:'#8b5cf6'},
      {id:'mfk04', sh:'MK·FC4', n:'FRAMES CONTENT + EMAIL + SOCIAL', d:'3 frameworks de producao de conteúdo · FRAME-CONTENT · FRAME-EMAIL · FRAME-SOCIAL', resp:'FC', prior:'P1', s:new Date(2026,5,24), e:new Date(2026,6,6), cor:'#8b5cf6'},
      {id:'mfk05', sh:'MK·FC5', n:'18 Sub-agentes — System Prompts', d:'Redaccao e validacao de 18 system prompts especializados · um por funcao e departamento · Sales · SAT · Ops · MKT · Finance · HR', resp:'FC', prior:'P2', s:new Date(2026,6,1), e:new Date(2026,8,30), cor:'#7e22ce', xdep:'mfk01'},
      {id:'mj01', sh:'MK·1', n:'Ecrã Início MKT', d:'Dashboard boas-vindas personalizado · tarefas pendentes + aprovacoes · chat DigiAI sobre módulo MKT · hub role-based · role cliente NetScreen restrito', resp:'JP', prior:'P1', s:new Date(2026,5,9), e:new Date(2026,5,15), cor:'#8b5cf6'},
      {id:'mj02', sh:'MK·2', n:'Ecrã Objectivos', d:'KPIs semáforo · desvios vs BP · budget Mimaki vs consumido · stock equipamentos · campanhas de preco activas · acoes preditivas · CTA criar briefing a partir de alerta', resp:'JP', prior:'P1', s:new Date(2026,5,15), e:new Date(2026,5,22), cor:'#8b5cf6', xdep:'mfk01'},
      {id:'mj03', sh:'MK·3', n:'Ecrã Resultados', d:'Performance 360 closed-loop · resultados campanhas + plano comunicacao · métricas DOOH NetScreen · Sub-agente ANALYTICS', resp:'JP', prior:'P1', s:new Date(2026,5,22), e:new Date(2026,5,26), cor:'#8b5cf6'},
      {id:'mj04', sh:'MK·4', n:'Ecrã Marcas', d:'CRUD por marca · geração AI de ICP e personas · tom de voz · restricoes · análise concorrentes · base estratégica para Plano Comunicacao', resp:'JP', prior:'P1', s:new Date(2026,5,29), e:new Date(2026,6,2), cor:'#8b5cf6'},
      {id:'mj05', sh:'MK·5', n:'Ecrã Plano de Comunicacao', d:'Plano trimestral AI com Extended thinking Opus · calendário editorial por canal · badges estado (em producao/aprovado/no ar) · link directo para Producao', resp:'JP', prior:'P1', s:new Date(2026,6,3), e:new Date(2026,6,10), cor:'#8b5cf6', xdep:'mfk03'},
      {id:'mj06', sh:'MK·6', n:'Ecrã Briefings — ajustes', d:'Versionamento + histórico de iteracoes por briefing (req. Rui Leitao) · briefing simplificado foto+vídeo+objectivo para modo cliente NetScreen', resp:'JP', prior:'P1', s:new Date(2026,5,9), e:new Date(2026,5,11), cor:'#8b5cf6'},
      {id:'mj07', sh:'MK·7', n:'Ecrã Campanhas', d:'Kanban CONCEITO→COPY→PRODUCAO→APROVACAO→PUBLICADO · budget · timeline · KPIs · badge assets (em revisao/aprovado/no ar) · link Producao', resp:'JP', prior:'P1', s:new Date(2026,5,12), e:new Date(2026,5,18), cor:'#8b5cf6', xdep:'mfk02'},
      {id:'mj08', sh:'MK·8', n:'Instalar 12 Skills MKT', d:'copywriting · social-content · content-strategy · launch-strategy · email-sequence · image · blog-post · social-media-marketing · executing-marketing-campaigns · seo-content-writer · content-quality-auditor · content-gap-analysis', resp:'JP', prior:'P1', s:new Date(2026,5,19), e:new Date(2026,5,23), cor:'#8b5cf6'},
      {id:'mj09', sh:'MK·9', n:'Ecrã Activacao', d:'Fila de aprovacao centralizada (Rui/Bruno) · badge pendentes + data prevista · publicacao agendada/real-time · calendário multi-canal multi-marca · Meta · LinkedIn · Email Brevo · Blog · Muppi LED · DOOH · NovaStar', resp:'JP', prior:'P1', s:new Date(2026,6,13), e:new Date(2026,6,20), cor:'#8b5cf6'},
      {id:'mj10', sh:'MK·10', n:'Ecrã SDR', d:'Monitorizacao qualificacao de leads · nurturing oportunidades · supervisao actividade agentes AI · arquitectura multi-agente expansível · Sub-agente DISTRIBUTOR', resp:'JP', prior:'P1', s:new Date(2026,6,20), e:new Date(2026,6,27), cor:'#8b5cf6'},
      {id:'mj11', sh:'MK·11', n:'Ecrã Performance Ads', d:'Métricas de campanhas · attribution por canal · métricas DOOH NetScreen · Sub-agente ADS · Meta · Google · LinkedIn · Branddigital', resp:'JP', prior:'P1', s:new Date(2026,6,27), e:new Date(2026,6,31), cor:'#8b5cf6'},
      {id:'m2j01', sh:'MK·12', n:'GA4 + Looker — integracao', d:'Configurar credenciais GA4 · ligar dados ao ecrã Resultados e ecrã Performance Ads · tracking por marca', resp:'JP', prior:'P2', s:new Date(2026,7,17), e:new Date(2026,7,21), cor:'#7c3aed'},
      {id:'m2j02', sh:'MK·13', n:'Sub-agente ANALYTICS', d:'Construir sub-agente de análise de resultados · integrar FRAME-KPI · ligar a ecrã Resultados · análise preditiva', resp:'JP', prior:'P2', s:new Date(2026,7,24), e:new Date(2026,7,27), cor:'#7c3aed', xdep:'mj03'},
      {id:'m2j03', sh:'MK·14', n:'Sub-agente ADS', d:'Construir sub-agente de análise de ads · integrar FRAME-ADS · ligar a ecrã Performance Ads', resp:'JP', prior:'P2', s:new Date(2026,7,17), e:new Date(2026,7,24), cor:'#7c3aed', xdep:'mj11'},
      {id:'m2j04', sh:'MK·15', n:'Sub-agente DISTRIBUTOR', d:'Construir sub-agente SDR DISTRIBUTOR · qualificacao e routing de leads · ligar a ecrã SDR', resp:'JP', prior:'P2', s:new Date(2026,7,28), e:new Date(2026,8,3), cor:'#7c3aed', xdep:'mj10'},
      {id:'m2j05', sh:'MK·16', n:'Gestor CRM — integracao MKT', d:'Integrar Gestor CRM via API Walter · objectivos · oportunidades · dados comerciais no módulo MKT', resp:'JP', prior:'P2', s:new Date(2026,7,17), e:new Date(2026,7,24), cor:'#7c3aed', xdep:'wf5'},
      {id:'m2j06', sh:'MK·17', n:'Claude Analista autonomo', d:'Agente Claude autónomo de análise preditiva · ecrã Objectivos · acoes preditivas AI para cumprir BP (Fase 2)', resp:'JP', prior:'P3', s:new Date(2026,8,1), e:new Date(2026,8,14), cor:'#7c3aed', xdep:'mj02'},
      {id:'m2mf01', sh:'MK·MF1', n:'Meta Graph API — publicacao e métricas', d:'Integracao Meta Graph API · publicacao Facebook e Instagram via ecrã Activacao · métricas em tempo real', resp:'MF', prior:'P2', s:new Date(2026,7,3), e:new Date(2026,7,10), cor:'#6d28d9'},
      {id:'m2mf02', sh:'MK·MF2', n:'LinkedIn API — publicacao e métricas', d:'Integracao LinkedIn API · publicacao de conteúdo via Activacao · métricas de engagement', resp:'MF', prior:'P2', s:new Date(2026,7,10), e:new Date(2026,7,17), cor:'#6d28d9'},
      {id:'m2mf03', sh:'MK·MF3', n:'Brevo API — email marketing', d:'Integracao Brevo API · envio de email marketing via Activacao · tracking abertura e clique', resp:'MF', prior:'P2', s:new Date(2026,7,17), e:new Date(2026,7,20), cor:'#6d28d9'},
      {id:'m2mf04', sh:'MK·MF4', n:'Branddigital API — DOOH NetScreen', d:'Integracao Branddigital API para circuitos DOOH NetScreen · ecrã Activacao + ecrã Performance Ads', resp:'MF', prior:'P2', s:new Date(2026,7,21), e:new Date(2026,7,28), cor:'#6d28d9'},
      {id:'m2mf05', sh:'MK·MF5', n:'NovaStar CMS API — painéis LED', d:'Integracao NovaStar CMS API para painéis LED NetScreen · publicacao de conteúdo via ecrã Activacao', resp:'MF', prior:'P2', s:new Date(2026,8,1), e:new Date(2026,8,2), cor:'#6d28d9'},
    ],
  },
  {
    id: 'comercial', label: 'Módulo Comercial', color: '#0ea5e9',
    phases: [
      {id:'c01', sh:'CO·1', n:'BP Definicao Objectivos', d:'Ecrã de definicao de objectivos individual e equipa · Mimaki qtd+valor · Decal/Biond/Sensek por família · desvios vs actual em tempo real', resp:'JP', prior:'P2', s:new Date(2026,8,1), e:new Date(2026,8,9), cor:'#0ea5e9', marco:true},
      {id:'c02', sh:'CO·2', n:'Módulo Media Sales — ecrã', d:'Ecrã com clientes e produtos com margens negativas · tabela priorizada · filtros por comercial e marca · acoes directas', resp:'JP', prior:'P2', s:new Date(2026,8,10), e:new Date(2026,8,15), cor:'#0ea5e9', marco:true},
      {id:'c03', sh:'CO·2.1', n:'AI Open OP stage 20', d:'AI abre oportunidade em stage 20 (reuniao) via Gestor CRM · trigger automático por cliente com margem negativa', resp:'JP', prior:'P2', s:new Date(2026,8,16), e:new Date(2026,8,21), cor:'#0ea5e9', xdep:'wf5'},
      {id:'c04', sh:'CO·2.2', n:'AI Take Notes + NextStep', d:'AI regista notas de reuniao · atribui NextStep automático em agenda · registo em Gestor CRM', resp:'JP', prior:'P2', s:new Date(2026,8,21), e:new Date(2026,8,25), cor:'#0ea5e9', xdep:'c03'},
      {id:'c05', sh:'CO·2.3', n:'AI sugestao estratégias margem', d:'AI analisa cliente e produto · sugere estratégias de melhoria de margem ao comercial · prioriza acoes', resp:'JP', prior:'P2', s:new Date(2026,8,16), e:new Date(2026,8,21), cor:'#0ea5e9'},
      {id:'c06', sh:'CO·2.4', n:'AI atribuicao e controlo NextStep', d:'Atribuicao automática de NextSteps em agenda · controlo de execucao · alertas de incumprimento', resp:'JP', prior:'P2', s:new Date(2026,8,28), e:new Date(2026,8,29), cor:'#0ea5e9', xdep:'c04'},
      {id:'c07', sh:'CO·2.5', n:'AI automation reminders Media Sales', d:'Envio automático de informacao e reminders ao cliente e comercial · aprovacao do comercial obrigatória', resp:'JP', prior:'P2', s:new Date(2026,8,30), e:new Date(2026,9,6), cor:'#0ea5e9', xdep:'c06'},
      {id:'c08', sh:'CO·3', n:'Ecrã Gestao Oportunidades', d:'Introducao de nova OP mobile e desktop · kanban por stage · filtros por comercial · marca · estado · histórico', resp:'JP', prior:'P2', s:new Date(2026,9,7), e:new Date(2026,9,12), cor:'#0ea5e9', xdep:'wf4', marco:true},
      {id:'c09', sh:'CO·9', n:'AI Open OP + Notes + NextStep', d:'AI abre oportunidade · regista notas da reuniao · atribui NextStep automático · integra com Gestor CRM', resp:'JP', prior:'P2', s:new Date(2026,9,13), e:new Date(2026,9,19), cor:'#0ea5e9', xdep:'c08'},
      {id:'c10', sh:'CO·10', n:'AI Nurturing preditivo', d:'AI analisa stage e histórico · sugere estratégias preditivas ao comercial para subir OP até fecho · by stage', resp:'JP', prior:'P2', s:new Date(2026,9,19), e:new Date(2026,9,26), cor:'#0ea5e9', xdep:'c09'},
      {id:'c11', sh:'CO·11', n:'SDR AI — Open OP stages', d:'SDR AI abre oportunidades nos stages 20 (reuniao) · 50 (evento) · 60 (demo) com base na qualificacao do agente', resp:'JP', prior:'P2', s:new Date(2026,9,27), e:new Date(2026,10,2), cor:'#0ea5e9', xdep:'c09'},
      {id:'c12', sh:'CO·12', n:'AI automation reminders OP', d:'Envio automático de reminders ao comercial e cliente após aprovacao do comercial · tracking de resposta', resp:'JP', prior:'P2', s:new Date(2026,10,2), e:new Date(2026,10,6), cor:'#0ea5e9', xdep:'c10'},
      {id:'c13', sh:'CO·13', n:'Nurturing AI', d:'AI regista notas após cada reuniao · atribui NextStep em agenda · actualiza stage automaticamente com base na análise', resp:'JP', prior:'P3', s:new Date(2026,10,9), e:new Date(2026,10,11), cor:'#0369a1', xdep:'c09', marco:true},
      {id:'c14', sh:'CO·14', n:'AI reclassificacao de stages', d:'AI reclassifica stage da oportunidade com base na análise da reuniao · sinais de compra · histórico', resp:'JP', prior:'P3', s:new Date(2026,10,12), e:new Date(2026,10,16), cor:'#0369a1', xdep:'c13'},
      {id:'c15', sh:'CO·15', n:'AI reminders campanhas em OP', d:'AI identifica campanhas em vigor e atribui clientes em oportunidades aplicáveis · reminders automáticos', resp:'JP', prior:'P3', s:new Date(2026,10,17), e:new Date(2026,10,23), cor:'#0369a1', xdep:'c14'},
      {id:'c16', sh:'CO·16', n:'AI automation reminders nurturing', d:'Envio automático de informacao e reminders ao cliente e comercial durante nurturing', resp:'JP', prior:'P3', s:new Date(2026,10,23), e:new Date(2026,10,26), cor:'#0369a1', xdep:'c15'},
      {id:'c17', sh:'CO·17', n:'Dashboard pipeline — stages', d:'Dashboard de pipeline por stages · qtd e valor por comercial e equipa · actualizacao em tempo real', resp:'JP', prior:'P3', s:new Date(2026,10,27), e:new Date(2026,11,2), cor:'#0369a1', xdep:'wf4'},
      {id:'c18', sh:'CO·18', n:'Preditividade facturacao mês', d:'Predicao de facturacao do mês com drilldown por oportunidade · base histórica + stage actual', resp:'JP', prior:'P3', s:new Date(2026,11,3), e:new Date(2026,11,9), cor:'#0369a1', xdep:'c17'},
      {id:'c19', sh:'CO·19', n:'Rácios conversao vs KPI', d:'Rácios de conversao por comercial · comparativo com KPI · comparativo com melhor do grupo', resp:'JP', prior:'P3', s:new Date(2026,11,10), e:new Date(2026,11,14), cor:'#0369a1', xdep:'c17'},
      {id:'c20', sh:'CO·20', n:'Objectivos BP e desvios', d:'Objectivos BP vs actual em tempo real · semáforo de desvio · por comercial e equipa', resp:'JP', prior:'P3', s:new Date(2026,11,15), e:new Date(2026,11,21), cor:'#0369a1', xdep:'c01'},
      {id:'c21', sh:'CO·21', n:'AI sugestoes acoes melhoria', d:'Extended thinking Opus analisa pipeline e KPIs · sugere acoes de melhoria por comercial e equipa', resp:'JP', prior:'P3', s:new Date(2026,11,21), e:new Date(2026,11,31), cor:'#0369a1', xdep:'c20'},
      {id:'c_clireal', sh:'CO·A', n:'Clientes — dados reais', d:'Integração com Primavera e Gestor CRM · histórico de compras real · alertas de inactividade · análise AI por cliente com dados actualizados', resp:'JP', prior:'P2', s:new Date(2026,8,22), e:new Date(2026,8,26), cor:'#0ea5e9', xdep:'wf5'},
      {id:'c_cartreal', sh:'CO·B', n:'Carteira — dados reais', d:'Análise de carteira com dados reais Primavera · potencial · risco · oportunidades de upsell por comercial', resp:'JP', prior:'P2', s:new Date(2026,8,26), e:new Date(2026,8,29), cor:'#0ea5e9', xdep:'c_clireal'},
      {id:'c_wsreal', sh:'CO·C', n:'Workspace Digi — dados reais', d:'Assistente AI comercial com contexto real de cliente · produto · histórico ERP · respostas personalizadas por comercial', resp:'JP', prior:'P2', s:new Date(2026,8,29), e:new Date(2026,9,2), cor:'#0ea5e9'},
      {id:'c_prospreal', sh:'CO·D', n:'Prospecção — dados reais', d:'Leads integrados com Gestor CRM · qualificação AI com dados de mercado e histórico de compras actualizados', resp:'JP', prior:'P2', s:new Date(2026,9,2), e:new Date(2026,9,6), cor:'#0ea5e9', xdep:'wf5'},
      {id:'c_simreal', sh:'CO·E', n:'Simulador — dados reais', d:'Preços e margens reais do Primavera · cálculo de impacto real por produto · cliente e desconto antes de enviar proposta', resp:'JP', prior:'P2', s:new Date(2026,9,6), e:new Date(2026,9,7), cor:'#0ea5e9', xdep:'c_clireal'},
      {id:'c_dashreal', sh:'CO·F', n:'Dashboard — dados reais', d:'KPIs reais Primavera + BP + CRM · facturação · pipeline · semáforos de desvio · alertas por comercial em tempo real', resp:'JP', prior:'P3', s:new Date(2026,10,3), e:new Date(2026,10,7), cor:'#0369a1', xdep:'c17'},
    ],
  },
  {
    id: 'financeira', label: 'Módulo Financeira', color: '#10b981',
    phases: [
      {id:'fi01', sh:'FN·1', n:'Levantamento requisitos Financeira', d:'Levantamento detalhado de requisitos com MF e equipa Finance · fluxos · dados · KPIs · medidas correctivas esperadas', resp:'JP', prior:'P2', s:new Date(2026,6,1), e:new Date(2026,6,3), cor:'#059669'},
      {id:'fi02', sh:'FN·2', n:'BD schema Financeira', d:'Criar tabelas fin_relatorios · fin_kpis · fin_objectivos · fin_acoes_correctivas em PostgreSQL', resp:'MF', prior:'P2', s:new Date(2026,6,6), e:new Date(2026,6,9), cor:'#059669', xdep:'fi01'},
      {id:'fi03', sh:'FN·3', n:'Ecrã Reports Financeiros', d:'P&L por empresa e marca · margens · comparativo de períodos · desvios · export', resp:'MF', prior:'P2', s:new Date(2026,6,10), e:new Date(2026,6,21), cor:'#059669', xdep:'fi02', marco:true},
      {id:'fi04', sh:'FN·4', n:'Ecrã Clientes e Stocks', d:'Aging CC clientes · nº dias médio recebimento · stock por produto · alertas de excesso e rotura', resp:'MF', prior:'P2', s:new Date(2026,6,22), e:new Date(2026,7,3), cor:'#059669', xdep:'fi02', marco:true},
      {id:'fi05', sh:'FN·5', n:'Ecrã Objectivos Financeiros', d:'Metas nº dias CC clientes e nº dias stock · desvios · semáforo · histórico de evolucao', resp:'MF', prior:'P2', s:new Date(2026,7,3), e:new Date(2026,7,10), cor:'#059669', xdep:'fi02', marco:true},
      {id:'fi06', sh:'FN·6', n:'AI medidas correctivas Finance', d:'Sugestao AI por desvio · atribuicao de responsável · controlo execucao · estado e aging da medida', resp:'MF', prior:'P2', s:new Date(2026,7,9), e:new Date(2026,7,16), cor:'#059669', xdep:'fi05'},
      {id:'ph_86v4th', sh:'FN.7', n:'Printplan', d:'', resp:'MF', prior:'P2', s:new Date(2026,7,9), e:new Date(2026,7,16), cor:'', marco:true},
      {id:'fi07', sh:'FN·8', n:'Integracao Primavera — Finance', d:'Dados reais de clientes · stocks · facturas · pagamentos via SQL Primavera V10 (NOLOCK · read-only)', resp:'JP', prior:'P3', s:new Date(2026,7,17), e:new Date(2026,7,27), cor:'#059669', xdep:'wf5', marco:true},
      {id:'fi08', sh:'FN·9', n:'Testes e deploy Financeira', d:'Testes com equipa Finance · validacao de dados reais · ajustes · deploy em producao', resp:'JP', prior:'P3', s:new Date(2026,7,28), e:new Date(2026,8,1), cor:'#059669', xdep:'fi07', marco:true},
      {id:'fn_primfull', sh:'FN·10', n:'Primavera Finance — dados completos', d:'Expansão do POC para dados reais completos · clientes · stocks · facturas · pagamentos integrados nos ecrãs financeiros', resp:'JP', prior:'P2', s:new Date(2026,8,2), e:new Date(2026,8,7), cor:'#059669', xdep:'fi08'},
      {id:'fn_cobreal', sh:'FN·11', n:'Cobranças — dados reais Primavera', d:'Substituir dados mock por aging real do Primavera · CC clientes · facturas em aberto · pagamentos actualizados diariamente', resp:'MF', prior:'P2', s:new Date(2026,8,7), e:new Date(2026,8,12), cor:'#059669', xdep:'fn_primfull'},
      {id:'fn_ctrlreal', sh:'FN·12', n:'Controlling — dados reais Primavera', d:'FSE · compras e activos com dados reais ERP · desvios vs orçamento calculados com dados actualizados automaticamente', resp:'MF', prior:'P2', s:new Date(2026,8,12), e:new Date(2026,8,17), cor:'#059669', xdep:'fn_primfull'},
      {id:'fn_tesour', sh:'FN·13', n:'Ecrã Tesouraria', d:'Cash flow diário consolidado · projecção rolling 13 semanas · calendário de pagamentos a fornecedores · alertas de cobertura · reconciliação bancária assistida AI', resp:'MF', prior:'P3', s:new Date(2026,8,17), e:new Date(2026,9,5), cor:'#047857', marco:true},
      {id:'fn_contab', sh:'FN·14', n:'Ecrã Contabilidade', d:'Lançamentos repetitivos assistidos por AI · checklist de fecho mensal por empresa SGPS · pré-validação IVA e retenções · pacote mensal estruturado para contabilista externo', resp:'MF', prior:'P3', s:new Date(2026,10,5), e:new Date(2026,11,1), cor:'#047857', xdep:'fn_tesour', marco:true},
    ],
  },
  {
    id: 'logistica', label: 'Módulo Logística', color: '#0ea5e9',
    phases: [
      {id:'lg01', sh:'LG·1', n:'Levantamento requisitos Logística', d:'Levantamento com equipa Logística · dias de stock · nível serviço · rotação · Print Plan e consignações', resp:'JP', prior:'P2', s:new Date(2026,7,17), e:new Date(2026,7,21), cor:'#0ea5e9'},
      {id:'lg02', sh:'LG·2', n:'BD schema Logística', d:'Criar tabelas lgt_dias_stock · lgt_nivel_servico · lgt_rotacao_stock · lgt_print_plan', resp:'MF', prior:'P2', s:new Date(2026,7,21), e:new Date(2026,7,27), cor:'#0ea5e9', xdep:'lg01', marco:true},
      {id:'lg03', sh:'LG·3', n:'Ecrã Dias de Stock', d:'Mapa dias stock por família (MP/PI/PA/ME) · CMV · consumos Print Plan · Ordens de Fabrico · DECAL e Mimaki · visão mensal e acumulada', resp:'MF', prior:'P2', s:new Date(2026,8,1), e:new Date(2026,8,12), cor:'#0ea5e9', xdep:'lg02', marco:true},
      {id:'lg04', sh:'LG·4', n:'Ecrã Nível de Serviço de Entregas', d:'Tempo encomenda→entrega · data prometida→entrega · idade lotes faturados vs fabrico · expedições diário/mensal · tipo terceiro · vendedor · país', resp:'MF', prior:'P2', s:new Date(2026,8,12), e:new Date(2026,8,22), cor:'#0ea5e9', xdep:'lg02', marco:true},
      {id:'lg05', sh:'LG·5', n:'Ecrã Rotação de Stock', d:'Tabela rotação produtos de tabela · artigos que deveriam estar e não estão · top 5 pos/neg por família modelo quantidade cor tamanho tipo cliente', resp:'MF', prior:'P2', s:new Date(2026,8,22), e:new Date(2026,9,1), cor:'#0ea5e9', xdep:'lg02', marco:true},
      {id:'lg06', sh:'LG·6', n:'Ecrã Print Plan e Consignações', d:'Stock por cliente Print Plan e consignação · quantidade · valor · % consumos · evolução mensal ritmo consumo vs stock disponível', resp:'MF', prior:'P2', s:new Date(2026,9,1), e:new Date(2026,9,9), cor:'#0ea5e9', xdep:'lg02', marco:true},
      {id:'lg07', sh:'LG·7', n:'Integração Primavera — Logística', d:'Dados reais de stocks · expedições · ordens de fabrico via SQL Primavera V10 (NOLOCK · read-only)', resp:'JP', prior:'P3', s:new Date(2026,9,9), e:new Date(2026,9,22), cor:'#0284c7', xdep:'wf5', marco:true},
      {id:'lg08', sh:'LG·8', n:'Testes e deploy Logística', d:'Testes com equipa Logística · validação de dados reais · ajustes · deploy em produção', resp:'JP', prior:'P3', s:new Date(2026,9,23), e:new Date(2026,9,28), cor:'#0284c7', xdep:'lg07', marco:true},
    ],
  },
  {
    id: 'producao', label: 'Módulo Produção', color: '#f59e0b',
    phases: [
      {id:'pr01', sh:'PD·1', n:'Levantamento requisitos Producao', d:'Levantamento detalhado com MF e equipa Producao · KPIs dias satisfacao encomendas · stock PI · NC · desperdícios', resp:'JP', prior:'P2', s:new Date(2026,7,17), e:new Date(2026,7,20), cor:'#f59e0b'},
      {id:'pr02', sh:'PD·2', n:'BD schema Producao', d:'Criar tabelas prd_kpis · prd_stock_pi · prd_plano_mensal · prd_nao_conformes · prd_desperdicios', resp:'MF', prior:'P2', s:new Date(2026,7,21), e:new Date(2026,7,26), cor:'#f59e0b', xdep:'pr01', marco:true},
      {id:'pr03', sh:'PD·3', n:'Ecrã KPIs Producao ', d:'Dias de satisfacao de encomendas · NC % · desperdícios · semáforo · trending por período', resp:'MF', prior:'P2', s:new Date(2026,7,27), e:new Date(2026,8,7), cor:'#f59e0b', xdep:'pr02', marco:true},
      {id:'pr04', sh:'PD·4', n:'Ecrã Stock PI', d:'Níveis stock PI actuais vs objectivo 60 dias · alertas de rotura · histórico por referência', resp:'MF', prior:'P2', s:new Date(2026,8,8), e:new Date(2026,8,15), cor:'#f59e0b', xdep:'pr02', marco:true},
      {id:'pr05', sh:'PD·5', n:'Ecrã Plano Mensal Producao', d:'Plano mensal Producao PI · plano vs actual · capacidade disponível · desvios · responsável', resp:'MF', prior:'P2', s:new Date(2026,8,16), e:new Date(2026,8,28), cor:'#f59e0b', xdep:'pr02', marco:true},
      {id:'pr06', sh:'PD·6', n:'Ecrã Nao Conformes', d:'Registo de NC · tipificacao · análise causa raiz · accao correctiva · estado · aging · responsável', resp:'MF', prior:'P2', s:new Date(2026,8,28), e:new Date(2026,9,5), cor:'#f59e0b', xdep:'pr02', marco:true},
      {id:'pr07', sh:'PD·7', n:'Ecrã Desperdícios', d:'Registo de desperdícios por tipo e turno · trending · objectivos de reducao · alertas de desvio', resp:'MF', prior:'P2', s:new Date(2026,9,5), e:new Date(2026,9,8), cor:'#f59e0b', xdep:'pr02', marco:true},
      {id:'pr08', sh:'PD·8', n:'Integracao Primavera — Producao', d:'Dados reais de encomendas · producao · stocks via SQL Primavera V10 (NOLOCK · read-only)', resp:'JP', prior:'P3', s:new Date(2026,9,9), e:new Date(2026,9,22), cor:'#d97706', xdep:'wf5', marco:true},
      {id:'pr09', sh:'PD·9', n:'Testes e deploy Producao', d:'Testes com equipa Producao · validacao de dados reais · ajustes · deploy em producao', resp:'JP', prior:'P3', s:new Date(2026,9,23), e:new Date(2026,9,28), cor:'#d97706', xdep:'pr08', marco:true},
    ],
  },
  {
    id: 'qualidade', label: 'Módulo Qualidade', color: '#ef4444',
    phases: [
      {id:'ql01', sh:'QL·1', n:'Levantamento requisitos Qualidade', d:'Levantamento detalhado com MF e equipa Qualidade · fluxos de reclamacao · medidas correctivas · reunioes de gestao', resp:'JP', prior:'P2', s:new Date(2026,8,1), e:new Date(2026,8,3), cor:'#ef4444'},
      {id:'ql02', sh:'QL·2', n:'BD schema Qualidade', d:'Criar tabelas qld_reclamacoes_fornecedor · qld_reclamacoes_cliente · qld_medidas · qld_reunioes', resp:'MF', prior:'P2', s:new Date(2026,8,4), e:new Date(2026,8,7), cor:'#ef4444', xdep:'ql01'},
      {id:'ql03', sh:'QL·3', n:'Ecrã Reclamacoes Fornecedores', d:'CRUD de reclamacoes a fornecedores · estado · análise · accao correctiva · aging · responsável', resp:'MF', prior:'P2', s:new Date(2026,8,8), e:new Date(2026,8,14), cor:'#ef4444', xdep:'ql02', marco:true},
      {id:'ql04', sh:'QL·4', n:'Ecrã Reclamacoes Clientes - SKU/Tipificadas', d:'Reclamacoes de clientes por SKU · tipificadas · estado · trending · % por marca', resp:'MF', prior:'P2', s:new Date(2026,8,15), e:new Date(2026,8,25), cor:'#ef4444', xdep:'ql02', marco:true},
      {id:'ql05', sh:'QL·5', n:'Ecrã Reunioes Equipa Gestao', d:'Agendamento de reunioes · ata AI automática · acoes com responsáveis · follow-up e controlo', resp:'MF', prior:'P2', s:new Date(2026,8,28), e:new Date(2026,9,1), cor:'#ef4444', xdep:'ql02', marco:true},
      {id:'ql_aval', sh:'QL·AV', n:'Ecrã Avaliação Fornecedores', d:'Avaliacao por entrega · qtd recebida vs encomendada · prazo · avaliacao anual MP e consumíveis · condições comerciais e qualidade', resp:'MF', prior:'P2', s:new Date(2026,9,2), e:new Date(2026,9,9), cor:'#ef4444', xdep:'ql02', marco:true},
      {id:'ql06', sh:'QL·6', n:'AI medidas correctivas Qualidade', d:'AI sugere medidas correctivas por reclamacao · atribuicao de responsável · controlo execucao · estado', resp:'MF', prior:'P2', s:new Date(2026,9,9), e:new Date(2026,9,16), cor:'#ef4444', xdep:'ql03', marco:true},
      {id:'ql07', sh:'QL·7', n:'Integracao Primavera — Qualidade', d:'Dados reais de SKU · fornecedores · histórico de compras via SQL Primavera V10 (NOLOCK · read-only)', resp:'MF', prior:'P3', s:new Date(2026,9,12), e:new Date(2026,9,22), cor:'#dc2626', xdep:'wf5'},
      {id:'ql08', sh:'QL·8', n:'Testes e deploy Qualidade', d:'Testes com equipa Qualidade · validacao de dados reais · ajustes · deploy em producao', resp:'JP', prior:'P3', s:new Date(2026,9,23), e:new Date(2026,9,27), cor:'#dc2626', xdep:'ql07', marco:true},
    ],
  },
  {
    id: 'sistema', label: 'Módulo Sistema', color: '#6366f1',
    phases: [
      {id:'sis01', sh:'SIS·1', n:'Sistema KPIs — métricas reais', d:'Integração com logs reais do servidor · latência por endpoint · uptime rolling 30d · custos Claude API por modelo e utilizador · substituição dos dados mock actuais', resp:'JP', prior:'P3', s:new Date(2026,9,1), e:new Date(2026,9,7), cor:'#6366f1'},
      {id:'sis02', sh:'SIS·2', n:'Inbox — filtros avançados e histórico', d:'Filtros por marca · estado · data · número de telemóvel · exportação · histórico completo paginado · pesquisa full-text', resp:'JP', prior:'P3', s:new Date(2026,9,7), e:new Date(2026,9,10), cor:'#6366f1', xdep:'sis01'},
      {id:'sis03', sh:'SIS·3', n:'Agentes — alertas automáticos e recovery', d:'Alertas por email/WA quando agente fica offline · acções de recovery directamente do ecrã · histórico de incidentes e uptime por agente', resp:'JP', prior:'P3', s:new Date(2026,9,10), e:new Date(2026,9,14), cor:'#6366f1', xdep:'sis02', marco:true},
    ],
  },
  {
    id: 'sat', label: 'Módulo SAT', color: '#f97316',
    phases: [
      {id:'sat01', sh:'SAT·1', n:'Levantamento requisitos SAT', d:'Levantamento com equipa SAT · fluxos de ticket · escalação L1-L5 · tipos de avaria por equipamento · KPIs de resolução e satisfação do técnico', resp:'JP', prior:'P3', s:new Date(2026,10,1), e:new Date(2026,10,3), cor:'#f97316'},
      {id:'sat02', sh:'SAT·2', n:'Overview SAT — dados reais', d:'Dashboard de tickets reais · abertos · em progresso · resolvidos · tempo médio de resolução · por técnico e equipamento · semáforo de SLA', resp:'JP', prior:'P3', s:new Date(2026,10,5), e:new Date(2026,10,9), cor:'#f97316', xdep:'sat01', marco:true},
      {id:'sat03', sh:'SAT·3', n:'Inbox WA SAT — assistência AI', d:'Integração heydigi_sessions filtrado por contexto SAT · histórico por sessão · escalação L1-L5 · sugestões AI de diagnóstico em tempo real para o técnico de campo', resp:'JP', prior:'P3', s:new Date(2026,10,9), e:new Date(2026,10,14), cor:'#f97316', xdep:'sat02', marco:true},
      {id:'sat04', sh:'SAT·4', n:'Histórico e base de conhecimento', d:'Arquivo de tickets resolvidos · pesquisa por cliente · equipamento · tipo de avaria e solução aplicada · base de conhecimento AI para reduzir tempo de resolução em casos recorrentes', resp:'JP', prior:'P3', s:new Date(2026,10,14), e:new Date(2026,10,21), cor:'#f97316', xdep:'sat03', marco:true},
    ],
  },
  {
    id: 'ferias', label: 'Férias da equipa', color: '#94a3b8',
    phases: [
      {id:'jp_ferias', sh:'FRIAS', n:'Férias João Paulino', d:'João Paulino em férias · sem disponibilidade para desenvolvimento', resp:'JP', s:new Date(2026,7,3), e:new Date(2026,7,14), cor:'#94a3b8', vac:true},
      {id:'fc_ferias', sh:'FRIAS', n:'Férias Fábio Costa', d:'Fábio Costa em férias · sem disponibilidade para trabalho de conteúdo e SPs', resp:'FC', s:new Date(2026,7,17), e:new Date(2026,7,31), cor:'#94a3b8', vac:true},
      {id:'wn_ferias', sh:'FRIAS', n:'Férias Walter Noia', d:'Walter Noia em férias · sem disponibilidade para desenvolvimento da API Gestor', resp:'WN', s:new Date(2026,7,3), e:new Date(2026,7,31), cor:'#94a3b8', vac:true},
    ],
  },
  {
    id: 'gestor', label: 'GestorConnect API · Walter', color: '#10b981',
    phases: [
      {id:'w0a', sh:'0A', n:'Prep FileMaker · conta serviço API', d:'Conta serviço API nos 4 tenants · confirmar layouts FMRS_API_* · validação de acessos', resp:'WN', s:new Date(2026,4,19), e:new Date(2026,5,1), cor:'#6366f1'},
      {id:'w0b', sh:'0B', n:'Regras negócio FM · validações', d:'Validações de negócio · integridade referencial · detecção e bloqueio de duplicados nos scripts API', resp:'WN', s:new Date(2026,5,1), e:new Date(2026,5,8), cor:'#8b5cf6'},
      {id:'wf1', sh:'F1', n:'Setup Python + FastAPI', d:'Estrutura do projecto com uv · primeiro endpoint de teste funcional · pipeline CI básico', resp:'WN', s:new Date(2026,5,8), e:new Date(2026,5,15), cor:'#0ea5e9'},
      {id:'wf2', sh:'F2', n:'Auth JWT · tenant isolation', d:'Sistema de login · middleware de autenticação JWT · isolamento completo por tenant', resp:'WN', s:new Date(2026,5,15), e:new Date(2026,5,22), cor:'#10b981'},
      {id:'wf3', sh:'F3', n:'Repositório FM · GET/WRITE', d:'GET via FMRS_API_* · WRITE via scripts GestorConnect · camada de abstracção de dados FM', resp:'WN', s:new Date(2026,5,22), e:new Date(2026,5,30), cor:'#f59e0b'},
      {id:'wf4', sh:'F4', n:'5 Endpoints (Entidades · Contactos…)', d:'Endpoints REST para: Entidades · Contactos · Oportunidades · Notas · Utilizadores', resp:'WN', s:new Date(2026,6,1), e:new Date(2026,6,7), cor:'#f97316'},
      {id:'wf5', sh:'F5', n:'OpenAPI + Entrega ao João', d:'Testes integração · Swagger publicado · entrega ao João · marco de integração com API Primavera', resp:'WN', s:new Date(2026,6,8), e:new Date(2026,6,20), cor:'#14b8a6', marco:true},
      {id:'wbuf', sh:'BUF', n:'Buffer · margem 2 semanas', d:'2 semanas de margem para imprevistos · gestão de risco do cronograma antes do fecho', resp:'WN', s:new Date(2026,6,20), e:new Date(2026,6,31), buf:true},
      {id:'wset', sh:'SET', n:'Setembro · ajustes + feedback', d:'Ajustes após feedback do João · expansão de endpoints · preparação para integração em produção', resp:'WN', s:new Date(2026,8,1), e:new Date(2026,8,30), cor:'#3b82f6'},
    ],
  },
  {
    id: 'track_ph_0edkk2', label: 'Integração Primavera Geral', color: '#6366f1',
    phases: [
      {id:'ph_r6mtkg', sh:'NEW', n:'Instalar Docker e Sistema no IMac', d:'', resp:'JP', prior:'P2', s:new Date(2026,5,8), e:new Date(2026,5,9), cor:'', marco:true},
      {id:'ph_fm54gm', sh:'NEW', n:'Fazer primeira consulta e testes', d:'', resp:'JP', prior:'P2', s:new Date(2026,5,8), e:new Date(2026,5,15), cor:'', marco:true},
      {id:'ph_tr1mjn', sh:'NEW', n:'Fazer Primeiras tabelas no PostGree', d:'', resp:'JP', prior:'P2', s:new Date(2026,5,8), e:new Date(2026,5,15), cor:'', marco:true},
      {id:'ph_runqp0', sh:'NEW', n:'Nova fase', d:'', resp:'JP', prior:'P2', s:new Date(2026,5,8), e:new Date(2026,5,15), cor:''},
    ],
  },
];
// Runtime: lê localStorage se disponível, cai para hardcoded
const TRACKS_DATA = (() => {
  try { const s = localStorage.getItem('digi-roadmap-data'); if (s) return JSON.parse(s, (k,v) => (k==='s'||k==='e') && typeof v==='string' ? new Date(v) : v); } catch(e) {}
  return TRACKS_DATA_DEFAULT;
})();
window.TRACKS_DATA = TRACKS_DATA;
// ─── Lookup all phases by id ──────────────────────────────────
const ALL_PHASES_MAP = {};
TRACKS_DATA.forEach(t => t.phases.forEach(p => { ALL_PHASES_MAP[p.id] = p; }));

const ROW_H  = 38;  // phase row height px
const HDR_H  = 32;  // month header height px
const TRACK_H= 33;  // track section header height px

// ─── Gantt Bar ────────────────────────────────────────────────
const GBar = ({ ph, trackColor, onBarClick }) => {
  const c = ph.cor || trackColor || RESP_COLORS_RM[ph.resp] || '#94a3b8';
  const isBuf = ph.buf, isVac = ph.vac;
  const bg = isBuf
    ? 'repeating-linear-gradient(45deg,#e2e8f0,#e2e8f0 4px,#f1f5f9 4px,#f1f5f9 8px)'
    : isVac
    ? 'repeating-linear-gradient(45deg,#cbd5e1,#cbd5e1 4px,#e2e8f0 4px,#e2e8f0 8px)'
    : c;
  const sDate = ph.s < TL_S ? TL_S : ph.s;
  const eDate = ph.e > TL_E ? TL_E : (ph.e <= ph.s ? new Date(ph.s.getTime()+864e5) : ph.e);
  const days = (eDate - sDate) / 864e5;
  return (
    <div title={ph.n}
      onClick={() => !isBuf && !isVac && onBarClick && onBarClick(ph.id)}
      style={{ position:'absolute', left:`calc(${pct(sDate)} + 1px)`,
        width:`calc(${spn(sDate,eDate)} - 2px)`, minWidth:(!isBuf&&!isVac)?6:0,
        top:'50%', transform:'translateY(-50%)', height:22, borderRadius:4,
        background:bg, boxShadow:(isBuf||isVac)?'none':'0 1px 3px rgba(0,0,0,.2)',
        display:'flex', alignItems:'center', padding:'0 5px', overflow:'hidden',
        zIndex:3, cursor:(isBuf||isVac)?'default':'pointer', transition:'filter .12s' }}
      onMouseEnter={e=>{ if(!isBuf&&!isVac) e.currentTarget.style.filter='brightness(1.15)'; }}
      onMouseLeave={e=>{ e.currentTarget.style.filter='none'; }}>
      {!isBuf && !isVac && days > 4 && (
        <span style={{ fontSize:9, fontWeight:600, color:'#fff', whiteSpace:'nowrap',
          textShadow:'0 1px 2px rgba(0,0,0,.3)', overflow:'hidden', textOverflow:'ellipsis' }}>
          {ph.xdep && <span style={{ opacity:.75, marginRight:3 }}>←</span>}
          {ph.n.length>26 ? ph.n.slice(0,24)+'…' : ph.n}
        </span>
      )}
    </div>
  );
};

// ─── Bar area row (right panel) ───────────────────────────────
const GBarRow = ({ ph, trackColor, onBarClick }) => (
  <div style={{ height:ROW_H, position:'relative', overflow:'hidden',
    borderBottom:'1px solid var(--border)',
    background: ph.vac ? 'rgba(148,163,184,.06)' : 'transparent' }}>
    {WEEKS.map((w,i) => (
      <div key={i} style={{ position:'absolute', top:0, bottom:0, left:pct(w),
        borderLeft:'1px solid var(--border)', opacity:.25, pointerEvents:'none' }} />
    ))}
    {MONTHS_TL.map((m,i) => (
      <div key={i} style={{ position:'absolute', top:0, bottom:0, left:pct(m.s),
        borderLeft:'1px solid var(--border)', opacity:.6, pointerEvents:'none' }} />
    ))}
    <div style={{ position:'absolute', top:0, bottom:0, left:pct(TODAY),
      borderLeft:'2px solid var(--ai-500)', zIndex:4, pointerEvents:'none' }} />
    <div style={{ position:'absolute', top:0, bottom:0, left:pct(MARCO),
      borderLeft:'2px dashed #14b8a6', zIndex:4, pointerEvents:'none' }} />
    <GBar ph={ph} trackColor={trackColor} onBarClick={onBarClick} />
  </div>
);

// ─── Legend + Zoom ────────────────────────────────────────────
const ZOOM_LEVELS = [1, 1.5, 2, 3, 4, 6];
const ZOOM_LABELS = { 1:'Ano completo', 1.5:'8 meses', 2:'6 meses', 3:'4 meses', 4:'3 meses', 6:'2 meses' };

const GLegendAndZoom = ({ zoom, onZoom }) => {
  const idx = ZOOM_LEVELS.indexOf(zoom);
  const btn = (dis) => ({
    width:26, height:26, borderRadius:5, border:'1px solid var(--border)',
    background:'var(--surface-muted)', color:dis?'var(--text-dim)':'var(--text)',
    cursor:dis?'default':'pointer', fontSize:15, fontWeight:600,
    display:'grid', placeItems:'center', flexShrink:0, opacity:dis?.4:1,
  });
  return (
    <div style={{ display:'flex', gap:10, flexWrap:'wrap', marginBottom:16, alignItems:'center' }}>

      <div style={{ marginLeft:'auto', display:'flex', alignItems:'center', gap:6 }}>
        <span style={{ fontSize:11, color:'var(--text-dim)', fontFamily:'var(--font-mono)',
          whiteSpace:'nowrap' }}>{ZOOM_LABELS[zoom]}</span>
        <button style={btn(idx<=0)} onClick={()=>idx>0&&onZoom(ZOOM_LEVELS[idx-1])}>−</button>
        <span style={{ fontSize:11, fontFamily:'var(--font-mono)', color:'var(--text-muted)',
          minWidth:28, textAlign:'center' }}>{zoom===1?'1×':`${zoom}×`}</span>
        <button style={btn(idx>=ZOOM_LEVELS.length-1)} onClick={()=>idx<ZOOM_LEVELS.length-1&&onZoom(ZOOM_LEVELS[idx+1])}>+</button>

      </div>
    </div>
  );
};

// ─── Phase List Row (accordion) ───────────────────────────────
const PhaseListRow = ({ ph, trackColor, isOpen, onToggle }) => {
  if (ph.buf) return null;
  const c = ph.cor || trackColor || RESP_COLORS_RM[ph.resp] || '#94a3b8';
  const fmt = d => d.toLocaleDateString('pt-PT', {day:'2-digit',month:'short',year:'numeric'});
  const dep = ph.xdep ? ALL_PHASES_MAP[ph.xdep] : null;
  return (
    <div id={'phase-'+ph.id} style={{ borderBottom:'1px solid var(--border)' }}>
      <div onClick={onToggle} style={{ display:'flex', gap:8, alignItems:'center',
        padding:'9px 14px', cursor:'pointer',
        background:isOpen?'var(--surface-muted)':'transparent', transition:'background .12s' }}>
        <span style={{ fontSize:9, fontWeight:700, padding:'1px 5px', borderRadius:3,
          background:c, color:'#fff', fontFamily:'var(--font-mono)', flexShrink:0 }}>{ph.sh}</span>
        <span style={{ flex:1, fontSize:12.5, fontWeight:500, lineHeight:1.3,
          color:ph.vac?'var(--text-dim)':'var(--text)' }}>{ph.n}</span>
        {ph.resp && (
          <span style={{ fontSize:9, fontWeight:700, padding:'1px 5px', borderRadius:3,
            background: RESP_COLORS_RM[ph.resp] || '#94a3b8', color:'#fff',
            fontFamily:'var(--font-mono)', flexShrink:0, letterSpacing:'0.04em' }}>
            {ph.resp}
          </span>
        )}
        <span style={{ fontSize:11, color:'var(--text-muted)', fontFamily:'var(--font-mono)',
          whiteSpace:'nowrap', flexShrink:0 }}>
          {fmt(ph.s)}{ph.s.getTime()!==ph.e.getTime()&&' – '+fmt(ph.e)}
        </span>
        <span style={{ fontSize:9, color:'var(--text-dim)', flexShrink:0, marginLeft:2 }}>
          {isOpen?'▲':'▼'}
        </span>
      </div>
      {isOpen && (
        <div style={{ padding:'9px 14px 13px 38px', background:'var(--surface-muted)',
          borderTop:'1px solid var(--border)' }}>
          <p style={{ fontSize:12, color:'var(--text-muted)', margin:'0 0 9px', lineHeight:1.6 }}>{ph.d}</p>
          <div style={{ display:'flex', gap:18, fontSize:11, fontFamily:'var(--font-mono)',
            color:'var(--text-dim)', flexWrap:'wrap' }}>
            <span><b style={{ color:'var(--text-muted)' }}>Início </b>{fmt(ph.s)}</span>
            <span><b style={{ color:'var(--text-muted)' }}>Fim </b>{fmt(ph.e)}</span>
            {ph.prior&&<span><b style={{ color:'var(--text-muted)' }}>Prior </b>{ph.prior}</span>}
            {ph.xdep&&<span style={{ color:'#f97316' }}><b>← dep </b>{dep?dep.n:ph.xdep}</span>}
            {ph.marco&&<span style={{ color:'#14b8a6', fontWeight:700 }}>● Marco</span>}
          </div>
        </div>
      )}
    </div>
  );
};

// ─── Main Screen ──────────────────────────────────────────────
const BusinessAIRoadmapScreen = () => {
  // Re-lê localStorage em cada montagem para reflectir edições do editor sem precisar de refresh
  const _tdRef = React.useRef(null);
  if (_tdRef.current === null) {
    try {
      const s = localStorage.getItem('digi-roadmap-data');
      if (s) {
        const parsed = JSON.parse(s, (k,v) => (k==='s'||k==='e') && typeof v==='string' ? new Date(v) : v);
        Object.keys(ALL_PHASES_MAP).forEach(k => delete ALL_PHASES_MAP[k]);
        parsed.forEach(t => t.phases.forEach(p => { ALL_PHASES_MAP[p.id] = p; }));
        _tdRef.current = parsed;
      }
    } catch(e) {}
    if (_tdRef.current === null) _tdRef.current = TRACKS_DATA_DEFAULT;
  }
  const td = _tdRef.current;
  const [openId,    setOpenId]    = React.useState(null);
  const [collapsed, setCollapsed] = React.useState(() => {
    try { const s = localStorage.getItem('digi-roadmap-collapsed'); if (s) return JSON.parse(s); } catch(e) {}
    return Object.fromEntries(td.map(t => [t.id, true]));
  });
  const [zoom, setZoom] = React.useState(() => {
    try { const s = localStorage.getItem('digi-roadmap-zoom'); if (s) return parseFloat(s) || 1; } catch(e) {}
    return 1;
  });
  React.useEffect(() => {
    try { localStorage.setItem('digi-roadmap-collapsed', JSON.stringify(collapsed)); } catch(e) {}
  }, [collapsed]);
  React.useEffect(() => {
    try { localStorage.setItem('digi-roadmap-zoom', String(zoom)); } catch(e) {}
  }, [zoom]);
  React.useEffect(() => {
    const raw = localStorage.getItem('digi-roadmap-scroll');
    if (!raw) return;
    localStorage.removeItem('digi-roadmap-scroll');
    try {
      const { trackId, phaseId } = JSON.parse(raw);
      setCollapsed(prev => ({ ...prev, [trackId]: false }));
      setTimeout(() => {
        const el = document.getElementById(phaseId ? 'phase-'+phaseId : 'track-acc-'+trackId);
        if (el) el.scrollIntoView({ behavior:'smooth', block:'center' });
      }, 150);
    } catch(e) {}
  }, []);

  const handleBarClick = (id) => {
    setOpenId(id);
    setTimeout(() => {
      const el = document.getElementById('phase-'+id);
      if (el) el.scrollIntoView({ behavior:'smooth', block:'center' });
    }, 60);
  };
  const togglePhase = (id) => setOpenId(p => p===id ? null : id);
  const toggleTrack = (tid) => setCollapsed(p => ({ ...p, [tid]: !p[tid] }));
  const totalFases  = td.reduce((n,t) => n+t.phases.filter(p=>!p.buf).length, 0);

  // Shared row renderer helpers (label side)
  const renderLabelHeader = () => (
    <div style={{ height:HDR_H, padding:'0 10px', display:'flex', alignItems:'center',
      fontSize:10, fontWeight:600, letterSpacing:'0.08em', color:'var(--text-dim)',
      fontFamily:'var(--font-display)', textTransform:'uppercase',
      borderBottom:'1px solid var(--border)', background:'var(--surface)',
      position:'sticky', top:0, zIndex:5 }}>
      Fase
    </div>
  );

  const trackTotalDays = (track) =>
    track.phases.filter(p=>!p.buf&&!p.vac)
      .reduce((s,p) => s + Math.max(1, Math.round((p.e - p.s) / 864e5)), 0);
  const fmtDur = (days) => days >= 14
    ? Math.round(days/7) + ' sem. (' + days + 'd)'
    : days + ' dias';

  const renderLabelTrackHeader = (track) => {
    return (
      <div onClick={()=>toggleTrack(track.id)}
        style={{ height:TRACK_H, display:'flex', alignItems:'center', gap:8, padding:'0 10px',
          background:'var(--surface-muted)', borderTop:'1px solid var(--border)',
          borderBottom:'1px solid var(--border)', cursor:'pointer', userSelect:'none' }}>
        <span style={{ width:8, height:8, borderRadius:'50%', background:track.color, flexShrink:0 }}/>
        <span style={{ fontSize:10, fontWeight:700, letterSpacing:'0.07em', textTransform:'uppercase',
          color:'var(--text-muted)', flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
          {track.label}
        </span>
        <span style={{ fontSize:10, color:'var(--text-dim)' }}>
          {collapsed[track.id]?'▶':'▼'}
        </span>
      </div>
    );
  };

  const renderLabelPhaseRow = (ph, track) => (
    <div key={ph.id} style={{ height:ROW_H, display:'flex', alignItems:'center', gap:7,
      padding:'0 10px', borderBottom:'1px solid var(--border)',
      background:ph.vac?'var(--surface-muted)':'var(--surface)' }}>
      <span style={{ fontSize:9, fontWeight:700, padding:'1px 4px', borderRadius:3, flexShrink:0,
        background:ph.cor||track.color||RESP_COLORS_RM[ph.resp]||'#94a3b8', color:'#fff',
        fontFamily:'var(--font-mono)', letterSpacing:'0.03em' }}>{ph.sh}</span>
      <span style={{ fontSize:10.5, fontWeight:500, overflow:'hidden', textOverflow:'ellipsis',
        whiteSpace:'nowrap', color:ph.vac?'var(--text-dim)':'var(--text)' }}>{ph.n}</span>
    </div>
  );

  // Bar side helpers
  const renderBarHeader = () => (
    <div style={{ display:'flex', height:HDR_H, borderBottom:'1px solid var(--border)',
      background:'var(--surface)', position:'sticky', top:0, zIndex:5 }}>
      {MONTHS_TL.map((m,i) => (
        <div key={i} style={{ width:spn(m.s,m.e), padding:'0 0 0 6px', display:'flex',
          alignItems:'center', fontSize:10, fontWeight:600, letterSpacing:'0.06em',
          color:'var(--text-muted)', borderRight:'1px solid var(--border)',
          fontFamily:'var(--font-display)', textTransform:'uppercase',
          overflow:'hidden', whiteSpace:'nowrap' }}>{m.l}</div>
      ))}
    </div>
  );

  const renderBarTrackHeader = (track) => {
    const activePh = track.phases.filter(p => !p.buf && !p.vac);
    const tStart   = activePh.reduce((m,p) => p.s < m ? p.s : m, activePh[0]?.s || TL_S);
    const tEnd     = activePh.reduce((m,p) => p.e > m ? p.e : m, activePh[0]?.e || TL_S);
    const sDate    = tStart < TL_S ? TL_S : tStart;
    const eDate    = tEnd   > TL_E ? TL_E : tEnd;
    return (
      <div onClick={()=>toggleTrack(track.id)}
        style={{ height:TRACK_H, background:'var(--surface-muted)',
          borderTop:'1px solid var(--border)', borderBottom:'1px solid var(--border)',
          cursor:'pointer', position:'relative', overflow:'hidden' }}>
        {MONTHS_TL.map((m,i) => (
          <div key={i} style={{ position:'absolute', top:0, bottom:0, left:pct(m.s),
            borderLeft:'1px solid var(--border)', opacity:.3, pointerEvents:'none' }} />
        ))}
        {WEEKS.map((w,i) => (
          <div key={i} style={{ position:'absolute', top:0, bottom:0, left:pct(w),
            borderLeft:'1px solid var(--border)', opacity:.15, pointerEvents:'none' }} />
        ))}
        <div style={{ position:'absolute', top:0, bottom:0, left:pct(TODAY),
          borderLeft:'2px solid var(--ai-500)', opacity:.4, pointerEvents:'none' }} />
        <div style={{ position:'absolute', top:0, bottom:0, left:pct(MARCO),
          borderLeft:'2px dashed #14b8a6', opacity:.4, pointerEvents:'none' }} />
        {/* Total span bar */}
        {activePh.length > 0 && (
          <div style={{
            position:'absolute',
            left: `calc(${pct(sDate)} + 1px)`,
            width: `calc(${spn(sDate, eDate)} - 2px)`,
            top:'50%', transform:'translateY(-50%)',
            height: 14, borderRadius: 3,
            background: track.color,
            opacity: 0.55,
            pointerEvents: 'none',
          }} />
        )}
      </div>
    );
  };

  return (
    <div className="scrollbar" style={{ flex:1, overflowY:'auto', padding:'24px 28px 80px' }}>
      <div className="font-mono" style={{ fontSize:11, color:'var(--text-dim)',
        letterSpacing:'0.08em', marginBottom:6 }}>BUSINESS & AI · ROADMAP</div>
      <h2 className="font-display" style={{ margin:'0 0 4px', fontSize:26,
        fontWeight:500, letterSpacing:'-0.01em' }}>Roadmap 2026</h2>
      <p style={{ fontSize:13, color:'var(--text-muted)', marginBottom:20, lineHeight:1.55 }}>
        {td.length} tracks · {totalFases} fases.
        Clica numa barra do Gantt para ver o detalhe abaixo.
      </p>

      <GLegendAndZoom zoom={zoom} onZoom={setZoom} />

      {/* ── Gantt: dois painéis separados ── */}
      <div style={{ display:'flex', border:'1px solid var(--border)', borderRadius:8,
        marginBottom:24, overflow:'hidden', boxShadow:'var(--shadow-sm)' }}>

        {/* PAINEL ESQUERDO — labels, sem scroll horizontal */}
        <div style={{ width:190, flexShrink:0, borderRight:'1px solid var(--border)',
          overflowX:'hidden', background:'var(--surface)' }}>
          {renderLabelHeader()}
          {td.map(track => (
            <React.Fragment key={track.id}>
              {renderLabelTrackHeader(track)}
              {!collapsed[track.id] && track.phases.map(ph => renderLabelPhaseRow(ph, track))}
            </React.Fragment>
          ))}
        </div>

        {/* PAINEL DIREITO — barras, com scroll horizontal independente */}
        <div style={{ flex:1, overflowX:'auto', minWidth:0 }}>
          <div style={{ minWidth:`${zoom*700}px` }}>
            {renderBarHeader()}
            {td.map(track => (
              <React.Fragment key={track.id}>
                {renderBarTrackHeader(track)}
                {!collapsed[track.id] && track.phases.map(ph => (
                  <GBarRow key={ph.id} ph={ph} trackColor={track.color} onBarClick={handleBarClick} />
                ))}
              </React.Fragment>
            ))}
          </div>
        </div>
      </div>

      {/* Phase lists (accordion) */}
      <div style={{ display:'flex', flexDirection:'column', gap:16 }}>
        {td.map(track => (
          <div id={'track-acc-'+track.id} key={track.id} className="card" style={{ overflow:'hidden' }}>
            <div style={{ padding:'10px 14px', background:'var(--surface-muted)',
              borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', gap:8 }}>
              <span style={{ width:8, height:8, borderRadius:'50%', background:track.color }}/>
              <span className="font-display" style={{ fontSize:11, fontWeight:700,
                letterSpacing:'0.08em', color:'var(--text-muted)', textTransform:'uppercase' }}>
                {track.label}
              </span>
              <span style={{ fontSize:10, color:'var(--text-dim)', fontFamily:'var(--font-mono)' }}>
                {track.phases.filter(p=>!p.buf).length} fases
              </span>

            </div>
            {track.phases.slice().sort((a,b)=>new Date(a.s)-new Date(b.s)).map(ph => (
              <PhaseListRow key={ph.id} ph={ph} trackColor={track.color}
                isOpen={openId===ph.id} onToggle={()=>togglePhase(ph.id)} />
            ))}
          </div>
        ))}
      </div>

      {/* Legenda responsáveis */}
      <div style={{ display:'flex', gap:8, flexWrap:'wrap', marginTop:24 }}>
        {Object.entries(RESP_COLORS_RM).map(([k,c]) => (
          <div key={k} style={{ display:'flex', alignItems:'center', gap:5, fontSize:11,
            color:'var(--text-muted)', background:'var(--surface-muted)',
            border:'1px solid var(--border)', borderRadius:6, padding:'3px 10px' }}>
            <span style={{ width:9, height:9, borderRadius:2, background:c, display:'inline-block' }}/>
            {k==='JP'?'João Paulino':k==='FC'?'Fábio Costa':k==='WN'?'Walter Nóia':'Márcio Fojo'}
          </div>
        ))}
      </div>
    </div>
  );
};
window.BusinessAIRoadmapScreen = BusinessAIRoadmapScreen;
