r.some(c=>c!==''));
}
// ── UTILITÁRIOS ───────────────────────────────────────────
function safe(v){ const s=String(v===null||v===undefined?'':v).trim(); return(s&&s!=='null'&&s!=='#N/A'&&s!=='nan')?s:''; }
function col(row,header,headers){
let i=headers.indexOf(header);
if(i===-1) i=headers.findIndex(h=>h.trim().replace(/\s+/g,' ')===header.trim().replace(/\s+/g,' '));
return i===-1?'':safe(row[i]||'');
}
function get(r,campo){ return col(r._row,campo,r._headers); }
function getCons(rc,campo){
if(!rc) return '';
if(rc._consolidated){
if(rc._vals[campo]!==undefined&&rc._vals[campo]!=='') return rc._vals[campo];
for(const row of rc._rows){ const v=get(row,campo); if(v) return v; }
return '';
}
return get(rc,campo);
}
function parseNum(v){
if(v===null||v===undefined||v==='') return null;
if(typeof v==='number') return isNaN(v)?null:v;
let s=String(v).trim().replace(/R\$\s*/g,'').replace(/\s/g,'').replace(/\./g,'').replace(',','.');
const n=parseFloat(s);
return isNaN(n)?null:n;
}
function parseData(str){
if(!str) return null;
const p=String(str).split('/');
if(p.length===3) return new Date(+p[2],+p[1]-1,+p[0]);
return null;
}
function fmtBRL(v){ const n=parseNum(v); return n!==null?'R$ '+n.toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2}):'—'; }
function fmtNum(v){ const n=parseNum(v); return n!==null?n.toLocaleString('pt-BR'):'—'; }
function fmtPct(v){ const n=parseNum(v); return n!==null?n.toFixed(1)+'%':'—'; }
function comparativo(atual,anterior){
const a=parseNum(atual), b=parseNum(anterior);
if(a===null||b===null||b===0) return '— sem comparativo
';
const diff=((a-b)/Math.abs(b)*100).toFixed(1);
const up=a>=b;
return `${up?'▲':'▼'} ${Math.abs(diff)}% vs período ant.
`;
}
// ── CONSOLIDAR MÚLTIPLOS CLIENTES ────────────────────────
function consolidar(recs){
if(!recs||!recs.length) return null;
if(recs.length===1) return recs[0];
const base=recs[0];
const camposNum=['Receita Bruta da Semana','Despesas Totais da Semana','Nº de Vendas Realizadas',
'Total de Leads Recebidos (Semana)','Leads de Tráfego Convertidos (Venda Direta)',
'Leads de Recaptação Convertidos (Base antiga)','Leads Fora de Perfil (Desqualificados)',
'Agendamentos/Visitas Realizadas','Faturamento Acumulado (Mês)','Meta de Faturamento Mensal',
'Novos Seguidores (Instagram)','Quantidade de Criativos Publicados (Total da semana)',
'Valor Total Investido (Semana)','Curtidas (Melhor Criativo)','Comentários (Melhor Criativo)',
'Compartilhamentos (Melhor Criativo)','Visualizações (Melhor Criativo',
'Leads Recebidos Instagram','Leads Recebidos WhatsApp',
'Leads Convertidos Instagram',CAMPO_CONV_WHATS,'Alcance Total da Semana'];
const camposMed=['Ticket Médio da Semana','CPL Médio (Custo por Lead)','CTR Médio (Taxa de Clique)'];
const vals={};
camposNum.forEach(c=>{ const soma=recs.reduce((s,r)=>s+(parseNum(get(r,c))||0),0); vals[c]=soma>0?String(soma):''; });
camposMed.forEach(c=>{ const ns=recs.map(r=>parseNum(get(r,c))).filter(v=>v!==null); vals[c]=ns.length>0?String(ns.reduce((s,v)=>s+v,0)/ns.length):''; });
return { _consolidated:true, _rows:recs, _vals:vals, _headers:base._headers, _row:base._row };
}
// ── FILTROS ───────────────────────────────────────────────
function registrosFiltrados(){
let recs=todosRegistros;
if(clientesSelecionados.length>0)
recs=recs.filter(r=>clientesSelecionados.includes(get(r,'Cliente')));
// Filtro por semana (dropdown de semanas)
const df=document.getElementById('dd-inicio')?.dataset.val||'';
const dt=document.getElementById('dd-fim')?.dataset.val||'';
const dfD=df?parseData(df):null;
const dtD=dt?parseData(dt):null;
if(dfD) recs=recs.filter(r=>{ const d=parseData(get(r,'Início da Semana (Data da segunda-feira)')); return d&&d>=dfD; });
if(dtD) recs=recs.filter(r=>{ const d=parseData(get(r,'Fim da Semana (Data do domingo)')); return d&&d<=dtD; });
return recs.sort((a,b)=>(parseData(get(b,'Início da Semana (Data da segunda-feira)'))||0)-(parseData(get(a,'Início da Semana (Data da segunda-feira)'))||0));
}
// ── DROPDOWN DE SEMANAS ───────────────────────────────────
function buildSemanaDropdown(campo, ddId, labelId, placeholder){
const vals=[...new Set(todosRegistros.map(r=>get(r,campo)).filter(Boolean))];
vals.sort((a,b)=>(parseData(b)||0)-(parseData(a)||0));
const dd=document.getElementById(ddId);
const lbl=document.getElementById(labelId);
if(!dd||!lbl) return;
dd.innerHTML=`
${placeholder}
${vals.map(v=>`${v}
`).join('')}`;
}
function filterSemDD(input,ddId){
const q=input.value.toLowerCase();
document.querySelectorAll(`#${ddId} .sem-opt`).forEach(opt=>{
opt.style.display=opt.dataset.val.toLowerCase().includes(q)||opt.dataset.val===''?'':'none';
});
}
function selectSem(val,labelId,ddId,placeholder){
const lbl=document.getElementById(labelId);
const btn=document.getElementById(labelId+'-btn');
const dd=document.getElementById(ddId);
if(lbl){ lbl.dataset.val=val; }
if(btn){ btn.textContent=val||placeholder; btn.classList.toggle('active',!!val); }
dd.classList.remove('open');
semanaIdx=0; renderModulo();
}
function toggleSemDD(ddId){
const dd=document.getElementById(ddId);
document.querySelectorAll('.sem-dd').forEach(d=>{ if(d.id!==ddId) d.classList.remove('open'); });
dd.classList.toggle('open');
}
document.addEventListener('click',e=>{
if(!e.target.closest('.sem-wrap')) document.querySelectorAll('.sem-dd').forEach(d=>d.classList.remove('open'));
if(!e.target.closest('#ms-wrap')){ document.getElementById('ms-dropdown')?.classList.remove('open'); document.getElementById('ms-btn')?.classList.remove('open'); }
});
// ── RENDER MODULO ─────────────────────────────────────────
function renderModulo(){
const registros=registrosFiltrados();
const mainEl=document.getElementById('content');
if(!mainEl) return;
// Agrupar por semana
const semanas=[], semanaMap={};
registros.forEach(r=>{
const k=get(r,'Início da Semana (Data da segunda-feira)')||'sem-data';
if(!semanaMap[k]){semanaMap[k]=[];semanas.push(k);}
semanaMap[k].push(r);
});
semanas.sort((a,b)=>(parseData(b)||0)-(parseData(a)||0));
if(!semanas.length){
mainEl.innerHTML='Nenhum dado para os filtros selecionados.
';
return;
}
semanaIdx=Math.min(semanaIdx,semanas.length-1);
const semanaKey=semanas[semanaIdx];
const semanaKeyAnt=semanas[semanaIdx+1]||null;
const grpAtual=semanaMap[semanaKey]||[];
const grpAnt=semanaKeyAnt?semanaMap[semanaKeyAnt]||[]:[];
const r=consolidar(grpAtual);
const rAnt=grpAnt.length?consolidar(grpAnt):null;
const _get=(rc,c)=>getCons(rc,c);
// Registros consolidados para gráficos históricos (filtrados)
const registrosConsolidados=semanas.map(k=>consolidar(semanaMap[k]));
// Todos os registros consolidados (sem filtro de semana) para gráfico histórico financeiro
const todosConsolidados=(()=>{
const sm={}, sk=[];
todosRegistros.filter(r=>clientesSelecionados.length===0||clientesSelecionados.includes(get(r,'Cliente'))).forEach(r=>{
const k=get(r,'Início da Semana (Data da segunda-feira)')||'sem-data';
if(!sm[k]){sm[k]=[];sk.push(k);}
sm[k].push(r);
});
sk.sort((a,b)=>(parseData(b)||0)-(parseData(a)||0));
return sk.map(k=>consolidar(sm[k]));
})();
// Período humanizado
const di=get(grpAtual[0],'Início da Semana (Data da segunda-feira)');
const df=get(grpAtual[0],'Fim da Semana (Data do domingo)');
const meses=['jan','fev','mar','abr','mai','jun','jul','ago','set','out','nov','dez'];
function hum(s){ if(!s) return ''; const p=s.split('/'); return p.length===3?`${p[0]} ${meses[+p[1]-1]}`:''; }
const periodo=di&&df?`${hum(di)} → ${hum(df)}`:di||'—';
// Nav header
const semTot=semanas.length;
const navHTML=`
${periodo}
${semanaIdx+1}/${semTot}
`;
if(moduloAtivo==='insights'){
mainEl.innerHTML=navHTML+`
Gerados automaticamente com base nos dados do período selecionado.
`;
gerarInsightsIA(r,rAnt,registrosConsolidados,_get);
return;
}
const renders={financeiro:renderFinanceiro,comercial:renderComercial,marketing:renderMarketing,trafego:renderTrafego};
try{
const html=renders[moduloAtivo]?.(r,rAnt,registrosConsolidados,todosConsolidados,_get)||'';
mainEl.innerHTML=navHTML+html;
} catch(e){
console.error('Erro ao renderizar módulo:',e);
mainEl.innerHTML=navHTML+`Erro ao carregar módulo.
${e.message}
`;
}
}
function navegarSemana(dir){
semanaIdx=Math.max(0,semanaIdx-dir);
renderModulo();
}
// ── GRÁFICOS UTILITÁRIOS ──────────────────────────────────
function svgLine(regs,campo,fmtFn,_getFn,color='#5C3317',dash=''){
const pts=regs.slice(0,8).reverse();
const nums=pts.map(r=>parseNum(_getFn(r,campo))||0);
const maxV=Math.max(...nums,1);
const W=400,H=90,pad=12;
const coords=nums.map((v,i)=>({
x:pad+(i/(nums.length-1||1))*(W-2*pad),
y:H-pad-((v/maxV)*(H-2*pad)), v
}));
const path='M '+coords.map(p=>`${p.x},${p.y}`).join(' L ');
const area='M '+coords[0].x+','+H+' L '+coords.map(p=>`${p.x},${p.y}`).join(' L ')+' L '+coords[coords.length-1].x+','+H+' Z';
const labels=pts.map((r,i)=>{
const d=_getFn(r,'Início da Semana (Data da segunda-feira)');
return `${d?String(d).slice(0,5):'S'+(i+1)}`;
}).join('');
const dots=coords.map((p,i)=>`${fmtFn(p.v)}`).join('');
return ``;
}
function svgBars(regs,campo,fmtFn,_getFn,color='var(--marrom)'){
const pts=regs.slice(0,6).reverse();
const nums=pts.map(r=>parseNum(_getFn(r,campo))||0);
const maxV=Math.max(...nums,1);
const n=pts.length, bW=Math.floor(60/n), gap=Math.floor(40/n);
const offset=(100-n*(bW+gap)+gap)/2;
let bars='',lbls='';
pts.forEach((r,i)=>{
const x=offset+i*(bW+gap);
const h=Math.max(2,(nums[i]/maxV)*80);
const d=_getFn(r,'Início da Semana (Data da segunda-feira)');
bars+=`${fmtFn(nums[i])}`;
lbls+=`${d?String(d).slice(0,5):'S'+(i+1)}`;
});
return ``;
}
// Gráfico de linha dupla com tooltip interativo
function svgDualLine(regs,campoA,campoB,labelA,labelB,fmtA,fmtB,_getFn,colorA='#5C3317',colorB='#C5A467'){
const pts=regs.slice(0,8).reverse();
const numA=pts.map(r=>parseNum(_getFn(r,campoA)));
const numB=pts.map(r=>parseNum(_getFn(r,campoB)));
const allVals=[...numA,...numB].filter(v=>v!==null);
const maxV=Math.max(...allVals,1);
const W=400,H=90,pad=12;
function mkCoords(nums){
return nums.map((v,i)=>({
x:pad+(i/(nums.length-1||1))*(W-2*pad),
y:v!==null?H-pad-((v/maxV)*(H-2*pad)):null, v
}));
}
const cA=mkCoords(numA), cB=mkCoords(numB);
function mkPath(coords,color,dash=''){
const valid=coords.filter(p=>p.y!==null);
if(!valid.length) return '';
return ``;
}
const labels=pts.map((r,i)=>{
const d=_getFn(r,'Início da Semana (Data da segunda-feira)');
return `${d?String(d).slice(0,5):'S'+(i+1)}`;
}).join('');
const dotsA=cA.map((p,i)=>p.y!==null?`${labelA}: ${fmtA(p.v)}`:'').join('');
const dotsB=cB.map((p,i)=>p.y!==null?`${labelB}: ${fmtB(p.v)}`:'').join('');
const leg=`
${labelA}
${labelB}
`;
return `${leg}
`;
}
// ── FINANCEIRO ────────────────────────────────────────────
function renderFinanceiro(r,rAnt,regsConsolidados,todosConsol,_get){
const rec = _get(r,'Receita Bruta da Semana');
const desp = _get(r,'Despesas Totais da Semana');
const recN = parseNum(rec), despN=parseNum(desp);
const resN = recN!==null&&despN!==null?recN-despN:null;
const vendas= _get(r,'Nº de Vendas Realizadas');
const tickRaw=_get(r,'Ticket Médio da Semana');
const tickN = parseNum(tickRaw)||(recN&&parseNum(vendas)>0?recN/parseNum(vendas):null);
const acum = _get(r,'Faturamento Acumulado (Mês)');
const meta = _get(r,'Meta de Faturamento Mensal');
const top5 = _get(r,'Top 5 Vendas');
const roiN = despN&&despN>0&&resN!==null?resN/despN*100:null;
const metaN = parseNum(meta), acumN=parseNum(acum);
const pctMeta= metaN&&metaN>0&&acumN!==null?Math.min(100,acumN/metaN*100):null;
function kpi(icon,label,val,comp,sub,cor){
return `
${icon?`${icon} `:''}${label}
${val||'—'}
${sub?`
${sub}
`:''}
${comp||''}
`;
}
// Gráfico histórico financeiro (TODAS semanas, não filtra por semana selecionada)
const grafHist=svgDualLine(todosConsol,'Receita Bruta da Semana','Despesas Totais da Semana','Receita','Despesas',fmtBRL,fmtBRL,(rc,c)=>getCons(rc,c));
// Top 5 Vendas — texto descritivo
const top5HTML=top5?`${top5.replace(/\*\*(.*?)\*\*/g,'$1').replace(/\n/g,'
')}
`:'Preencha o campo "Top 5 Vendas" na planilha
';
// Progress bar meta
const progressHTML=metaN?`
`:'Preencha a meta na planilha
';
return `
${kpi('🔥','Receita Bruta',fmtBRL(rec),comparativo(rec,rAnt?_get(rAnt,'Receita Bruta da Semana'):null))}
${kpi('💸','Despesas',fmtBRL(desp),comparativo(desp,rAnt?_get(rAnt,'Despesas Totais da Semana'):null))}
${kpi('📊','Resultado Líquido',fmtBRL(resN),comparativo(resN,rAnt&&parseNum(_get(rAnt,'Receita Bruta da Semana'))!==null?parseNum(_get(rAnt,'Receita Bruta da Semana'))-parseNum(_get(rAnt,'Despesas Totais da Semana')):null),'Receita − Despesas',resN!==null?(resN>=0?'var(--verde)':'var(--vermelho)'):'')}
${kpi('🛒','Nº de Vendas',vendas||'—',comparativo(vendas,rAnt?_get(rAnt,'Nº de Vendas Realizadas'):null))}
${kpi('📈','ROI',roiN!==null?roiN.toFixed(1)+'%':'—',comparativo(roiN,rAnt&&parseNum(_get(rAnt,'Receita Bruta da Semana'))!==null?(parseNum(_get(rAnt,'Receita Bruta da Semana'))-parseNum(_get(rAnt,'Despesas Totais da Semana')))/parseNum(_get(rAnt,'Despesas Totais da Semana'))*100:null),'(Receita − Despesas) ÷ Despesas',roiN!==null?(roiN>=0?'var(--verde)':'var(--vermelho)'):'')}
🗓️ Faturamento Acumulado vs Meta
${fmtBRL(acum)}
${progressHTML}
Histórico · Receita vs Despesas todas as semanas
${grafHist}
Detalhes Top Vendas
${top5HTML}
`;
}
// ── COMERCIAL ─────────────────────────────────────────────
function renderComercial(r,rAnt,regsConsol,todosConsol,_get){
const leads = _get(r,'Total de Leads Recebidos (Semana)');
const agend = _get(r,'Agendamentos/Visitas Realizadas');
const vendas = _get(r,'Nº de Vendas Realizadas');
const recap = _get(r,'Leads de Recaptação Convertidos (Base antiga)');
const fora = _get(r,'Leads Fora de Perfil (Desqualificados)');
const analise= _get(r,'Análise Estratégica de Leads');
const lInsta = _get(r,'Leads Recebidos Instagram');
const lWhats = _get(r,'Leads Recebidos WhatsApp');
const cInsta = _get(r,'Leads Convertidos Instagram');
const cWhats = _get(r,CAMPO_CONV_WHATS);
const lN=parseNum(leads)||0, aN=parseNum(agend)||0, vN=parseNum(vendas)||0;
const recapN=parseNum(recap)||0, foraN=parseNum(fora)||0;
const taxaTotal=lN>0?((vN/lN)*100).toFixed(1):null;
// Funil SVG
const steps=[
{label:'Leads Recebidos',val:lN,color:'#5C3317'},
{label:'Qualificados',val:recapN,color:'#7A4520'},
{label:'Agendamentos',val:aN,color:'#A0622A'},
{label:'Vendas',val:vN,color:'#C5A467'},
];
const fW=440,fH=220,fPad=6,slotH=(fH-fPad*(steps.length-1))/steps.length;
const topW=[1,.76,.55,.38];
let funil='';
steps.forEach((s,i)=>{
const y=i*(slotH+fPad);
const tw=topW[i]*fW, bw=topW[Math.min(i+1,steps.length-1)]*fW;
const tx=(fW-tw)/2, bx=(fW-bw)/2;
const pts=`${tx},${y} ${tx+tw},${y} ${bx+bw},${y+slotH} ${bx},${y+slotH}`;
const prevV=i>0?steps[i-1].val:null;
const taxa=prevV&&prevV>0?((s.val/prevV)*100).toFixed(0)+'%':null;
funil+=``;
funil+=`${s.label}`;
funil+=`${s.val}`;
if(taxa&&i>0){
const by=y-fPad/2;
funil+=``;
funil+=`↓ ${taxa}`;
}
});
const funilSVG=``;
// Tabela canais
function pct(n,d){ const nN=parseNum(n),dN=parseNum(d); return nN!==null&&dN&&dN>0?((nN/dN)*100).toFixed(0)+'%':'—'; }
const tabela=`
| Canal | Recebidos | % | Taxa Conv. |
| Instagram | ${lInsta||'—'} | ${pct(lInsta,leads)} | ${pct(cInsta,lInsta)} |
| WhatsApp | ${lWhats||'—'} | ${pct(lWhats,leads)} | ${pct(cWhats,lWhats)} |
| Recaptação | ${recap||'—'} | ${pct(recap,leads)} | 100% |
| Total | ${leads||'—'} | 100% | ${taxaTotal?taxaTotal+'%':'—'} |
`;
return `
Total de Leads (Semana)
${leads||'—'}
${comparativo(leads,rAnt?_get(rAnt,'Total de Leads Recebidos (Semana)'):null)}
Agendamentos
${agend||'—'}
${comparativo(agend,rAnt?_get(rAnt,'Agendamentos/Visitas Realizadas'):null)}
Vendas Realizadas
${vendas||'—'}
${comparativo(vendas,rAnt?_get(rAnt,'Nº de Vendas Realizadas'):null)}
Taxa Lead → Venda
${taxaTotal?taxaTotal+'%':'—'}
${vN} vendas / ${lN} leads
Funil de Conversão
${funilSVG}
Leads por Canal
${tabela}
Histórico · Leads Semanais
${svgBars(regsConsol,'Total de Leads Recebidos (Semana)',v=>v+' leads',(rc,c)=>getCons(rc,c))}
${analise?`Análise Estratégica de Leads
${analise}
`:''}`;
}
// ── MARKETING ─────────────────────────────────────────────
function renderMarketing(r,rAnt,regsConsol,todosConsol,_get){
const seg = _get(r,'Novos Seguidores (Instagram)');
const cria = _get(r,'Quantidade de Criativos Publicados (Total da semana)');
const fmt_ = _get(r,'Formato Predominante (Vídeos, Cards ou Carrosséis)');
const link = _get(r,'Link do Melhor Criativo (Performance)');
const views = _get(r,'Visualizações (Melhor Criativo');
const curt = _get(r,'Curtidas (Melhor Criativo)');
const coment = _get(r,'Comentários (Melhor Criativo)');
const compart= _get(r,'Compartilhamentos (Melhor Criativo)');
const alcance= _get(r,'Alcance Total da Semana');
const analise= _get(r,'Análise Estratégica de Conteúdo');
const segN=parseNum(seg)||0;
const curtN=parseNum(curt)||0, comentN=parseNum(coment)||0;
const engPct=segN>0?((curtN+comentN)/segN*100).toFixed(1)+'%':(curtN+comentN?String(curtN+comentN):'—');
// Link criativo
const linkRec=regsConsol.map(rc=>getCons(rc,'Link do Melhor Criativo (Performance)')).find(v=>v&&(v.startsWith('http')||v.startsWith('www')))||link;
const linkFmt=linkRec&&!linkRec.startsWith('http')?'https://'+linkRec:linkRec;
const btnCriativo=linkFmt
?`
Abrir Criativo Destaque
`
:'Sem link cadastrado na planilha
';
function metricBox(val,label){ return ``; }
return `
Novos Seguidores
${fmtNum(seg)}
${comparativo(seg,rAnt?_get(rAnt,'Novos Seguidores (Instagram)'):null)}
Criativos Publicados
${cria||'—'}
${fmt_?`
${fmt_}
`:''}
Engajamento
${engPct}
(Curtidas + Coment.) ÷ Seguidores
Formato / Qtd
${fmt_?fmt_.split(/[,;]/).map(f=>`${f.trim()}`).join(' '):'—'}
Publicados${cria||'—'}
${analise?`
Análise Estratégica
${analise}
`:''}
Criativo Destaque da Semana
${btnCriativo}
${metricBox(fmtNum(alcance),'Alcance')}
${metricBox(fmtNum(views),'Visualizações')}
${metricBox(curt,'Curtidas')}
${metricBox(coment,'Comentários')}
${metricBox(compart,'Compartilhamentos')}
${metricBox(fmtNum(seg),'Seguidores')}
Histórico · Seguidores
${svgLine(regsConsol,'Novos Seguidores (Instagram)',fmtNum,(rc,c)=>getCons(rc,c))}
Curtidas (semana)${curt||'—'}
Visualizações${fmtNum(views)}
Semana 1 → semana N
`;
}
// ── TRÁFEGO PAGO ──────────────────────────────────────────
function renderTrafego(r,rAnt,regsConsol,todosConsol,_get){
const inv = _get(r,'Valor Total Investido (Semana)');
const cpl = _get(r,'CPL Médio (Custo por Lead)');
const ctr = _get(r,'CTR Médio (Taxa de Clique)');
const camp = _get(r,'Quantidade de Campanhas Ativas');
const analise=_get(r,'Análise Estratégica de Tráfego');
const invN=parseNum(inv), recN=parseNum(_get(r,'Receita Bruta da Semana'));
const roasN=(recN!==null&&invN&&invN>0)?(recN-invN)/invN*100:null;
const ctrN=parseNum(ctr);
const ctrFmt=ctrN!==null?(ctrN<1?(ctrN*100).toFixed(1)+'%':ctrN.toFixed(1)+'%'):'—';
// Gráfico Investimento (barras) + ROAS (linha)
function invRoasChart(regs,_getFn){
const pts=regs.slice(0,6).reverse();
const invVals=pts.map(rc=>parseNum(_getFn(rc,'Valor Total Investido (Semana)'))||0);
const recVals=pts.map(rc=>parseNum(_getFn(rc,'Receita Bruta da Semana')));
const roasVals=pts.map((_,i)=>invVals[i]>0&&recVals[i]!==null?(recVals[i]-invVals[i])/invVals[i]*100:null);
const maxInv=Math.max(...invVals,1);
const absRoas=roasVals.filter(v=>v!==null).map(Math.abs);
const maxRoas=Math.max(...absRoas,1);
const n=pts.length, bW=Math.floor(55/n), gap=Math.floor(35/n);
const offset=(100-n*(bW+gap)+gap)/2;
let bars='',roasLine='',lbls='',rDots='';
const rPts=[];
pts.forEach((rc,i)=>{
const x=offset+i*(bW+gap);
const h=Math.max(2,(invVals[i]/maxInv)*75);
bars+=`Invest: ${fmtBRL(invVals[i])}`;
const d=_getFn(rc,'Início da Semana (Data da segunda-feira)');
lbls+=`${d?String(d).slice(0,5):'S'+(i+1)}`;
if(roasVals[i]!==null){
const cy=80-((roasVals[i]/maxRoas)*35+40);
rPts.push({x:x+bW/2,y:cy,v:roasVals[i]});
}
});
if(rPts.length>1){ roasLine=``; }
rDots=rPts.map(p=>`ROAS: ${p.v.toFixed(1)}%`).join('');
const leg=`InvestimentoROAS
`;
return `${leg}
`;
}
// CTR com transformação decimal→%
function ctrVal(rc,c){ const v=getCons(rc,c); const n=parseNum(v); return n!==null?(n<1?n*100:n):null; }
function cplVal(rc,c){ return parseNum(getCons(rc,c)); }
return `
💸 Investimento
${fmtBRL(inv)}
Na semana
🎯 CPL Médio
${fmtBRL(cpl)}
Custo por lead
CTR
${ctrFmt}
Taxa de clique
ROAS
${roasN!==null?roasN.toFixed(1)+'%':'—'}
(Receita − Invest.) ÷ Invest.
Histórico · Investimento e ROAS
${invRoasChart(regsConsol,(rc,c)=>getCons(rc,c))}
Histórico · CTR e CPL
${svgDualLine(regsConsol,'CTR Médio (Taxa de Clique)','CPL Médio (Custo por Lead)','CTR (%)','CPL (R$)',v=>v.toFixed(1)+'%',fmtBRL,(rc,c)=>{
const raw=getCons(rc,c);
if(c==='CTR Médio (Taxa de Clique)'){ const n=parseNum(raw); return n!==null?(n<1?String(n*100):String(n)):null; }
return raw;
})}
${camp||analise?`Campanhas Ativas${camp?' ('+camp+')':''}
${analise?`
Análise Estratégica de Tráfego
${analise}
`:''}
`:''}`;
}
// ── INSIGHTS IA ───────────────────────────────────────────
function gerarInsightsIA(r,rAnt,regsConsol,_get){
const receita = parseNum(_get(r,'Receita Bruta da Semana'));
const despesas = parseNum(_get(r,'Despesas Totais da Semana'));
const seg = parseNum(_get(r,'Novos Seguidores (Instagram)'));
const criativos= parseNum(_get(r,'Quantidade de Criativos Publicados (Total da semana)'));
const leads = parseNum(_get(r,'Total de Leads Recebidos (Semana)'));
const vendas = parseNum(_get(r,'Nº de Vendas Realizadas'));
const invN = parseNum(_get(r,'Valor Total Investido (Semana)'));
const acumN = parseNum(_get(r,'Faturamento Acumulado (Mês)'));
const metaN = parseNum(_get(r,'Meta de Faturamento Mensal'));
const recAnt = rAnt?parseNum(_get(rAnt,'Receita Bruta da Semana')):null;
const despAnt = rAnt?parseNum(_get(rAnt,'Despesas Totais da Semana')):null;
const segAnt = rAnt?parseNum(_get(rAnt,'Novos Seguidores (Instagram)')):null;
const insights=[], insightsFin=[];
function add(cor,t,d){insights.push({cor,titulo:t,descricao:d});}
function addF(cor,t,d){insightsFin.push({cor,titulo:t,descricao:d});}
// Financeiro
if(receita!==null&&despesas!==null){
const roi=(receita-despesas)/despesas*100;
if(roi>=200) addF('green',`ROI de ${roi.toFixed(0)}% — operação altamente rentável`,'Cada real investido retorna mais de 3x. Estrutura financeira saudável.');
else if(roi>=0) addF('yellow',`ROI de ${roi.toFixed(0)}% — margem positiva`,'Retorno positivo. Avaliar principais despesas para otimizar margem.');
else addF('red',`ROI negativo (${roi.toFixed(0)}%) — despesas superaram receita`,'Revisar despesas e antecipar recebimentos pendentes.');
const res=receita-despesas;
if(res>0) addF('green',`Resultado líquido R$ ${res.toLocaleString('pt-BR',{minimumFractionDigits:2})}`,'Receita superou despesas. Operação saudável no período.');
else addF('red',`Resultado líquido negativo R$ ${Math.abs(res).toLocaleString('pt-BR',{minimumFractionDigits:2})}`,'Despesas superaram receita. Verificar lançamentos.');
}
if(receita!==null&&recAnt!==null){
const v=(receita-recAnt)/Math.abs(recAnt)*100;
if(v>=15) add('green',`Receita subiu ${v.toFixed(0)}% vs semana anterior`,'Crescimento expressivo. Identificar o que impulsionou.');
else if(v<=-15) add('red',`Receita caiu ${Math.abs(v).toFixed(0)}% vs semana anterior`,'Queda relevante. Investigar causas e acionar plano de recuperação.');
else add('yellow',`Receita estável (${v>0?'+':''}${v.toFixed(0)}% vs semana anterior)`,'Desempenho similar à semana anterior. Buscar alavancas de crescimento.');
}
if(acumN!==null&&metaN!==null){
const pct=acumN/metaN*100;
if(pct>=100) addF('green','Meta mensal atingida! 🎉','Parabéns! Continue o ritmo e supere a meta.');
else addF('yellow',`${pct.toFixed(0)}% da meta mensal atingida`,`Faltam R$ ${(metaN-acumN).toLocaleString('pt-BR',{minimumFractionDigits:2})} para bater a meta.`);
}
// Mídia
if(seg!==null&&segAnt!==null){
const v=(seg-segAnt)/Math.abs(segAnt)*100;
if(v<=-50) add('red',`Seguidores reduziram ${Math.abs(v).toFixed(0)}% vs semana anterior`,'Queda no crescimento orgânico. Avaliar frequência e relevância dos conteúdos.');
else if(v>=20) add('green',`Seguidores cresceram ${v.toFixed(0)}% vs semana anterior`,'Boa tração orgânica. Identificar conteúdo de melhor performance e replicar.');
else add('yellow',`Seguidores ${v>=0?'cresceram':'caíram'} ${Math.abs(v).toFixed(0)}% vs semana anterior`,'Crescimento estável. Testar novos formatos para acelerar.');
}
if(criativos!==null){
if(criativos>=5) add('green',`${criativos} criativos publicados — boa frequência de conteúdo`,'Volume adequado de publicações. Manter consistência e testar formatos variados.');
else add('yellow',`${criativos} criativo(s) publicado(s) — frequência baixa`,'Aumentar cadência de publicações para melhorar alcance orgânico.');
}
const grid=document.getElementById('insights-grid');
if(!grid) return;
function renderSection(titulo,lista){
if(!lista.length) return '';
return `${lista.map(ins=>`${ins.titulo}
${ins.descricao}
`).join('')}`;
}
grid.innerHTML=renderSection('Mídia & Performance',insights)+renderSection('Financeiro',insightsFin);
}
// ── CARREGAR DADOS ────────────────────────────────────────
async function carregarDados(){
document.getElementById('status-txt').textContent='Atualizando...';
try{
const res=await fetch(`${SHEET_CSV_URL}&t=${Date.now()}`);
if(!res.ok) throw new Error('HTTP '+res.status);
const text=await res.text();
const rows=parseCSV(text);
if(rows.length<2) throw new Error('Planilha vazia');
const headers=rows[0].map(h=>h.replace(/\r/g,'').trim());
todosRegistros=rows.slice(1).filter(r=>r.some(c=>c.trim())).map(r=>({_row:r,_headers:headers}));
// Popular dropdown de clientes
const clientes=[...new Set(todosRegistros.map(r=>col(r._row,'Cliente',r._headers)).filter(Boolean))];
const list=document.getElementById('ms-list');
if(list){
list.innerHTML=clientes.map(c=>`
${c}
somente este
`).join('');
}
clientesSelecionados=[...clientes];
const btn=document.getElementById('ms-btn');
if(btn) btn.textContent=clientes.length===1?clientes[0]:'Todos os clientes';
// Popular dropdowns de semana
buildSemanaDropdown('Início da Semana (Data da segunda-feira)','dd-inicio-list','dd-inicio','Início Semana');
buildSemanaDropdown('Fim da Semana (Data do domingo)','dd-fim-list','dd-fim','Fim Semana');
document.getElementById('status-txt').textContent='Atualizado';
semanaIdx=0;
renderModulo();
} catch(e){
console.error(e);
document.getElementById('status-txt').textContent='Erro';
document.getElementById('content').innerHTML=`Não foi possível carregar os dados.
Verifique se a planilha está publicada.
${e.message}
`;
}
}
// ── NAVEGAÇÃO ─────────────────────────────────────────────
document.querySelectorAll('.nav-item').forEach(item=>{
item.addEventListener('click',()=>{
document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
item.classList.add('active');
moduloAtivo=item.dataset.modulo;
document.getElementById('page-title').textContent=TITULOS[moduloAtivo];
semanaIdx=0;
renderModulo();
const nav=document.getElementById('sidebar-nav');
if(nav&&window.innerWidth<=768) nav.classList.remove('open');
});
});
function somenteEste(e,nome){
e.stopPropagation();
document.querySelectorAll('.ms-cliente-cb').forEach(cb=>{cb.checked=cb.value===nome;});
atualizarClientesSelecionados();
}
function toggleMS(){
const dd=document.getElementById('ms-dropdown');
const btn=document.getElementById('ms-btn');
dd.classList.toggle('open');
btn.classList.toggle('open');
}
function toggleTodos(){
const all=document.getElementById('ms-all');
all.checked=!all.checked;
document.querySelectorAll('.ms-cliente-cb').forEach(cb=>cb.checked=all.checked);
atualizarClientesSelecionados();
}
function atualizarClientesSelecionados(){
const cbs=[...document.querySelectorAll('.ms-cliente-cb')];
clientesSelecionados=cbs.filter(cb=>cb.checked).map(cb=>cb.value);
const allCb=document.getElementById('ms-all');
if(allCb) allCb.checked=clientesSelecionados.length===cbs.length;
const label=clientesSelecionados.length===0?'Nenhum':clientesSelecionados.length===cbs.length?'Todos os clientes':clientesSelecionados.length===1?clientesSelecionados[0]:`${clientesSelecionados.length} clientes`;
const btn=document.getElementById('ms-btn');
if(btn) btn.textContent=label;
semanaIdx=0;
renderModulo();
}
// ── PDF ────────────────────────────────────────────────────
async function downloadPDF(){
const btn=document.querySelector('.btn-pdf');
if(btn){btn.disabled=true;btn.textContent='Gerando...';}
try{
const canvas=await html2canvas(document.getElementById('content'),{scale:2,useCORS:true,backgroundColor:'#F9F7F2'});
const {jsPDF}=window.jspdf;
const pdf=new jsPDF({orientation:'landscape',unit:'mm',format:'a4'});
const W=pdf.internal.pageSize.getWidth(), H=pdf.internal.pageSize.getHeight();
pdf.addImage(canvas.toDataURL('image/png'),'PNG',0,0,W,H);
pdf.save(`Firmare_${moduloAtivo}_${new Date().toLocaleDateString('pt-BR').replace(/\//g,'-')}.pdf`);
} catch(e){console.error(e);}
if(btn){btn.disabled=false;btn.textContent='Baixar PDF';}
}
// ── INIT ──────────────────────────────────────────────────
// Login handled by early script above
if(window._loginOk){ carregarDados(); window._loginOk=false; }