Processo judicial é daqui que saem as movimentações
acompanhamento Dados do processo
Informe o número ou marque “Não tem processo judicial”.
Link direto do processo no sistema do tribunal.
Obrigatório quando há processo.
Atualizado pelas movimentações; pode ser ajustado à mão.
Cada linha que começar com uma data vira uma movimentação. Datas em dd/mm/aaaa.
Observações
Registro
Histórico de alterações
Agenda
Novo compromisso
Campo obrigatório.
Compromisso externo exige horário.
Campo obrigatório.
Campo obrigatório.
Campo obrigatório.
Sem término, reserva uma hora.
Quem for marcado enxerga o compromisso na própria agenda.
Registro
Status
Em espera ou aguardando retorno
O texto entra nos comentários do registro, com seu nome e o horário.
Escreva o motivo para continuar.
Obrigatório neste status.
Indicadores
Relatório gerado
O arquivo foi enviado para a pasta de downloads do navegador.
Ele abre direto no Excel — as colunas já vêm separadas por ponto e vírgula, o padrão brasileiro.
Se o download não aparecer, use o botão Copiar conteúdo e cole numa planilha em branco.
O relatório segue exatamente os filtros que estão na tela.
Acesso rápido
Contato da equipe
Clique no e-mail para escrever ou use o botão para copiar.
Clique em uma linha para abrir o registro.
O encaminhamento fica registrado no histórico do registro.
Gestão Financeira
'+
'
'+txt+'
'+(botoes||'')+'' : '';
}
/* O módulo é uma página inteira. Tentamos três caminhos, porque o ambiente do
artefato pode bloquear um deles: escrever no iframe, srcdoc e blob. */
function montarFinanceiro(forcar){
if(FIN_PRONTO && !forcar) return;
const html = finHTML();
if(!html){ finStatus('O módulo financeiro não foi encontrado neste arquivo.'); return; }
try{ localStorage.setItem('gf_tema', S.cfg.tema==='dark'?'dark':'light'); }catch(e){}
FIN_PRONTO=false; FIN_TENTATIVA=0;
finStatus('Abrindo a plataforma financeira…');
tentarFin(html);
}
function tentarFin(html){
const fr = $('fin-frame');
FIN_TENTATIVA++;
try{
if(FIN_TENTATIVA===1){
fr.removeAttribute('srcdoc'); fr.src='about:blank';
setTimeout(()=>{ try{
const d=fr.contentDocument; d.open(); d.write(html); d.close();
}catch(e){} verificarFin(html); }, 60);
return;
}
if(FIN_TENTATIVA===2){ fr.removeAttribute('src'); fr.srcdoc = html; }
else if(FIN_TENTATIVA===3){
fr.removeAttribute('srcdoc');
FIN_URL = URL.createObjectURL(new Blob([html],{type:'text/html;charset=utf-8'}));
fr.src = FIN_URL;
} else {
finStatus('Este navegador está bloqueando a exibição do módulo financeiro dentro da plataforma. '+
'Você pode abri-lo em uma aba separada — os dados são os mesmos.',
'
'+
''+
'
');
return;
}
}catch(e){}
verificarFin(html);
}
function verificarFin(html){
setTimeout(()=>{
if(finCarregou()){ FIN_PRONTO=true; finStatus(''); sincronizarTemaFin(); setTimeout(()=>sincronizarAssociados(true), 400); }
else tentarFin(html);
}, 1400);
}
function alternarFinFull(){
const box=$('fin-box'), cheio=!box.classList.contains('full');
box.classList.toggle('full',cheio);
document.body.classList.toggle('finfull',cheio);
$('fin-btn-full').innerHTML = cheio?'⤡ Sair da tela cheia':'⛶ Tela cheia';
$('fin-btn-full').title = cheio?'Voltar ao tamanho normal (Esc)':'Ampliar para tela cheia';
$('fin-btitulo').textContent = cheio?'Gestão Financeira · tela cheia' : 'Gestão Financeira';
if(cheio){ try{ if(box.requestFullscreen) box.requestFullscreen({navigationUI:'hide'}); }catch(e){} }
else { try{ if(document.fullscreenElement && document.exitFullscreen) document.exitFullscreen(); }catch(e){} }
}
function sairFinFull(){ if($('fin-box') && $('fin-box').classList.contains('full')) alternarFinFull(); }
document.addEventListener('fullscreenchange', ()=>{
const box=$('fin-box');
if(!document.fullscreenElement && box && box.classList.contains('full') && UI.fullNativo) sairFinFull();
UI.fullNativo = !!document.fullscreenElement;
});
function abrirFinNovaAba(){
try{
if(!FIN_URL) FIN_URL = URL.createObjectURL(new Blob([finHTML()],{type:'text/html;charset=utf-8'}));
const a=document.createElement('a'); a.href=FIN_URL; a.target='_blank'; a.rel='noopener';
document.body.appendChild(a); a.click(); a.remove();
}catch(e){ toast('Não foi possível abrir em outra aba.'); }
}
/* O módulo financeiro guarda o próprio tema em 'gf_tema'. Escrevemos a chave antes
de carregá-lo e, com ele já aberto, alternamos a classe direto no documento. */
function sincronizarTemaFin(){
const escuro = S.cfg.tema==='dark';
try{ localStorage.setItem('gf_tema', escuro?'dark':'light'); }catch(e){}
try{ const d = $('fin-frame').contentDocument; if(d && d.body) d.body.classList.toggle('dark', escuro); }catch(e){}
}
function finPendentes(){
if(!podeFinanceiro()) return [];
return S.tarefas.filter(t=>t.tipo==='financeiro' && t.fin && FIN_LANCAVEIS.indexOf(t.fin.tipo)>=0
&& !t.fin.lancado && t.status!==CONCLUIDA && visivel(t));
}
function renderAlertasFin(){
const el=$('fin-alertas'); if(!el) return;
const p = finPendentes().slice().sort(ordena);
if(!p.length){ el.innerHTML=''; return; }
el.innerHTML = '
Pendente de lançamento '+p.length+'
'+
'
'+(p.length===1?'Um registro financeiro criado no painel de tarefas ainda não foi lançado aqui.'
:p.length+' registros financeiros criados no painel de tarefas ainda não foram lançados aqui.')+'
'+esc(t.fin.tipo)+''+
''+esc(t.descricao)+''+
''+
''+
''+(cli?esc(cli)+' · ':'')+'Criado por '+esc(t.criadoPor||'—')+' em '+fmtDH(t.criadoEm)+
' · '+esc(TIPOS[t.tipo].dataLabel)+' '+fmtD(t.data)+(t.fin.nf==='Sim'?' · emitir NF':'')+'
';
}).join('')+'
';
}
function marcarLancado(id){
const t=S.tarefas.find(x=>x.id===id); if(!t) return;
if(!podeEditar(t)){ toast('Você não pode alterar este registro.'); return; }
t.fin.lancado=true; t.fin.lancadoPor=eu().nome; t.fin.lancadoEm=agoraISO();
hist(t,'Marcado como lançado na Gestão Financeira.');
t.alteradoEm=agoraISO(); t.alteradoPor=eu().nome;
salvar(); redesenhar(); toast('Registro baixado do alerta financeiro.');
}
/* =========================================================
PAINEL INÍCIO — "Minha visão" e "Indicadores"
Reúne, em uma única tela, o que é meu (acompanhamento pessoal)
e o que é do escritório (leitura de gestão, restrita).
========================================================= */
/* Perfis de gestão: mesmo recorte usado na Gestão Financeira. */
function ehGestor(m){ m = m || eu(); return !!m && FUNCOES_FINANCEIRO.indexOf(m.funcao)>=0; }
/* Justificar é obrigatório apenas na janela de tratamento — ali o prazo já está
vencendo e a gestão precisa saber o porquê. Nas alterações comuns o motivo é
opcional, mas a mudança de data e de responsável sempre vai ao histórico. */
function vgEstado(){
if(!UI.vg) UI.vg = {quem:'eu', grupo:'data', prodPer:'semana',
semVazias: !!(S.cfg && S.cfg.vgSemVazias),
indModo:'tipo', indVista:'aberto', indPer:'mes'};
return UI.vg;
}
function vgAlvo(){
const v = vgEstado();
if(!ehGestor() || v.quem==='eu') return S.usuario;
const id = +v.quem;
return membro(id) ? id : S.usuario;
}
function vgEuMesmo(){ return vgAlvo()===S.usuario; }
function vgNomeAlvo(){ const m=membro(vgAlvo()); return m? m.nome : '—'; }
function vgPrimeiroNome(){ return String(vgNomeAlvo()).split(/\s+/)[0]; }
/* ---------- contas de tempo ---------- */
function horasEntre(a,b){
const x=new Date(a), y=new Date(b);
if(!a||!b||isNaN(x)||isNaN(y)) return null;
return (y-x)/3600000;
}
function fmtDur(h){
if(h==null) return '—';
if(h<1) return Math.max(1,Math.round(h*60))+' min';
if(h<48) return (h<10 ? h.toFixed(1).replace('.',',') : Math.round(h))+' h';
const d=h/24;
return (d<10 ? d.toFixed(1).replace('.',',') : Math.round(d))+' dias';
}
/* Dias úteis daqui até a data (0 = hoje, negativo = já passou). */
function diasUteisAte(dataISO){
const d=pd(dataISO); if(!d) return null;
const h=hoje0();
if(+d===+h) return 0;
const sinal = d>h ? 1 : -1;
let n=0, cur=new Date(h.getTime()), guarda=0;
while(+cur!==+d && guarda++<800){
cur.setDate(cur.getDate()+sinal);
const dw=cur.getDay();
if(dw!==0 && dw!==6) n++;
}
return sinal*n;
}
/* Dias úteis decorridos desde um carimbo de data/hora. */
function diasUteisDesde(isoTs){
if(!isoTs) return null;
const d=new Date(isoTs); if(isNaN(d)) return null;
d.setHours(0,0,0,0);
const h=hoje0();
let n=0, cur=new Date(d.getTime()), guarda=0;
while(cur{ if(c.em && (!u || c.em>u)) u=c.em; });
(t.historico||[]).forEach(h=>{
if(h.em && VG_MOV_RE.test(h.txt||'') && !/^registro criado/i.test(h.txt||'')){
if(!u || h.em>u) u=h.em;
}
});
return u;
}
/* "Parada": entrega a até 2 dias úteis e sem nenhum sinal de trabalho
nos últimos 2 dias úteis (nem comentário, nem mudança de status). */
function estaParada(t){
if(t.status===CONCLUIDA) return false;
const du = diasUteisAte(t.data);
if(du===null || du<0 || du>2) return false; /* os vencidos têm alerta próprio */
const mov = ultimaMovimentacao(t);
if(!mov) return diasUteisDesde(t.criadoEm)>=1 || du<=0;
return diasUteisDesde(mov) >= 2;
}
/* ---------- recortes ---------- */
function vgTarefasDe(alvo){
return S.tarefas.filter(t=>{
if(!visivel(t)) return false;
if(t.tipo==='financeiro' && !podeFinanceiro()) return false;
return (t.responsaveis||[]).indexOf(alvo)>=0;
});
}
function vgCompsDe(alvo){
return (S.compromissos||[]).filter(c=> c.donoId===alvo || (c.participantes||[]).indexOf(alvo)>=0 );
}
function compOculto(c){
if(c.donoId===S.usuario || (c.participantes||[]).indexOf(S.usuario)>=0) return false;
if(c.particular) return true;
return agConfig(membro(c.donoId)).visivel==='ocupado';
}
/* =========================================================
ESQUELETO
========================================================= */
function montarVisao(){
const el=$('pane-visao'); if(!el) return;
el.innerHTML =
'
'+
'
'+
''+
'
Parte 1 · o seu trabalho'+
'
Minha visão
'+
''+
'
'+
''+
'
1
'+
'
Quanto há em aberto em cada frente de trabalho. Clique em um quadro para abrir a lista completa.
O retrato comercial do escritório: quantos contratos foram assinados, de onde vieram os clientes '+
'e até onde o escritório chegou. Escolha o mês e o ano — todo período já cadastrado continua '+
'disponível, então este quadro serve também como registro histórico.
'+
'
Período analisado'+
'
'+
''+
''+
'
'+
''+
''+
''+
'
'+
'
'+
'
2Demandas por responsável
'+
'
Cada linha é uma pessoa do escritório; cada coluna, um recorte do trabalho dela. '+
'Alterne entre tipo de demanda e status para descobrir onde o trabalho está parado. '+
'Clique em qualquer número para abrir a lista dos registros.
'+
'
Recorte exibido'+
''+
''+
''+
''+
'
'+
''+
''+
'
'+
'
3Produtividade da equipe
'+
'
Quem concluiu mais no período e com que rapidez. O intervalo entre movimentações mostra o ritmo '+
'de migração de status: quanto menor, mais fluida está a demanda na mão da pessoa.
'+
''+
'
4Sugestões de melhoria e alertas
'+
'
Pontos que merecem uma conversa com o responsável antes de virarem urgência. '+
'Os atalhos com o nome de cada pessoa abrem só os registros dela.
'+
'
'+
'';
ligarArrasteLateral($('vg-kanban'));
}
function vgSet(campo, valor){ vgEstado()[campo]=valor; renderVisao(); }
function vgSetInd(campo, valor){ vgEstado()[campo]=valor; renderVisao(); }
/* =========================================================
RENDER GERAL
========================================================= */
function renderVisao(){
if(!$('vg-ola')) montarVisao();
const v=vgEstado(), u=eu();
const h=new Date().getHours();
const saud = h<12 ? 'Bom dia' : h<18 ? 'Boa tarde' : 'Boa noite';
$('vg-ola').textContent = saud+', '+String(u?u.nome:'').split(/\s+/)[0]+'.';
const d=hoje0();
$('vg-data').textContent = ['domingo','segunda-feira','terça-feira','quarta-feira','quinta-feira','sexta-feira','sábado'][d.getDay()]+
', '+d.getDate()+' de '+MESES_NOME[d.getMonth()]+' de '+d.getFullYear();
/* seletor de pessoa — só para a gestão */
$('vg-minha-tools').innerHTML = ehGestor()
? 'Ver as demandas de'+
''
: '';
$('vg-minha-sub').textContent = vgEuMesmo()
? 'Somente o que está sob sua responsabilidade — os três painéis de trabalho reunidos aqui.'
: 'Acompanhamento das demandas de '+vgNomeAlvo()+' (leitura da gestão).';
const meu = vgEuMesmo(), nome = vgPrimeiroNome();
$('vg-bh1').textContent = meu ? 'Minhas demandas' : 'Demandas de '+nome;
$('vg-bh2').textContent = meu ? 'Meu quadro de trabalho' : 'Quadro de trabalho de '+nome;
$('vg-bh3').textContent = meu ? 'Minha produtividade' : 'Produtividade de '+nome;
$('vg-hint3').textContent = 'Quanto foi concluído em cada período e quanto tempo cada registro levou entre a criação e a conclusão.';
renderVgFaixa();
renderVgCaixas();
renderVgBarra();
renderVgKanban();
renderVgProdutividade();
const ind = ehGestor() && pode('ver_visao');
$('vg-sec-ind').style.display = ind ? '' : 'none';
if(ind) renderVgIndicadores();
}
/* ---------- faixa de atenção: o resumo do dia em uma linha ---------- */
function renderVgFaixa(){
const abertas = vgAbertas();
const comps = vgCompsDe(vgAlvo());
const grupos = {
atraso:{cls:'f-al', ic:'⚠', txt:'em atraso', itens:abertas.filter(t=>{const d=dias(t.data);return d!==null&&d<0;})},
hoje: {cls:'f-hj', ic:'●', txt:'com entrega hoje', itens:abertas.filter(t=>dias(t.data)===0)},
sete: {cls:'f-7d', ic:'📅',txt:'nos próximos 7 dias', itens:abertas.filter(t=>{const d=dias(t.data);return d!==null&&d>0&&d<=7;})},
espera:{cls:'f-es', ic:'⏸', txt:'aguardando retorno', itens:abertas.filter(t=>t.status===ESPERA)},
semdata:{cls:'f-sd',ic:'○', txt:'sem data definida', itens:abertas.filter(t=>!t.data)}
};
const hj = comps.filter(c=>dias(c.data)===0);
let h = Object.keys(grupos).filter(k=>grupos[k].itens.length).map(k=>{
const g=grupos[k];
return '';
}).join('');
if(hj.length) h += '';
if(!h) h = '';
$('vg-faixa').innerHTML = h;
}
function vgFaixaLista(k){
const abertas = vgAbertas();
const def = {
atraso :{t:'Em atraso', s:'Data de entrega já passada e registro ainda aberto.', f:t=>{const d=dias(t.data);return d!==null&&d<0;}},
hoje :{t:'Com entrega hoje', s:'Precisa sair hoje.', f:t=>dias(t.data)===0},
sete :{t:'Próximos 7 dias', s:'A vencer dentro da semana.', f:t=>{const d=dias(t.data);return d!==null&&d>0&&d<=7;}},
espera :{t:'Aguardando retorno', s:'Dependem de resposta de terceiro — vale reiterar a cobrança.', f:t=>t.status===ESPERA},
semdata:{t:'Sem data definida', s:'Sem data não entram na agenda nem nos alertas de vencimento.', f:t=>!t.data}
}[k];
if(!def) return;
UI.ordem='data-asc';
abrirListaVG(vgEuMesmo()?'Minhas demandas':'Demandas de '+vgNomeAlvo(), def.t, def.s,
abertas.filter(def.f).slice().sort(ordena), 'tarefa', ()=>vgFaixaLista(k));
}
/* =========================================================
MINHAS DEMANDAS — quadros por natureza do registro
========================================================= */
const VG_CAIXAS = [
{id:'prazo', nome:'Prazos', tipos:['prazo'], cor:'#b3261e'},
{id:'audiencia', nome:'Audiências', tipos:['audiencia'], cor:'#1D6FE0'},
{id:'tarefa', nome:'Tarefas', tipos:['tarefa'], cor:'#2d5486'},
{id:'pericias', nome:'Perícias e Avaliações Sociais', tipos:['pericia','avaliacao'], cor:'#6b3fa0'},
{id:'atendimento',nome:'Atendimentos', tipos:['atendimento'], cor:'#127a52'},
{id:'financeiro', nome:'Financeiro', tipos:['financeiro'], cor:'#8a6d1f', so:'fin'},
{id:'comp', nome:'Compromissos pessoais', tipos:null, cor:'#8a5a00'}
];
function vgItensCaixa(id){
const alvo=vgAlvo(), cx=VG_CAIXAS.find(c=>c.id===id);
if(!cx) return [];
if(cx.id==='comp') return vgCompsDe(alvo);
return vgTarefasDe(alvo).filter(t=>cx.tipos.indexOf(t.tipo)>=0);
}
function renderVgCaixas(){
$('vg-caixas').innerHTML = VG_CAIXAS.filter(cx=>{
if(cx.so==='fin') return podeFinanceiro() && vgItensCaixa('financeiro').length;
return true;
}).map(cx=>{
const itens = vgItensCaixa(cx.id);
let abertos, conc, atras, hoje;
if(cx.id==='comp'){
const fut = itens.filter(c=>{ const d=dias(c.data); return d!==null && d>=0; });
abertos = fut.length;
conc = itens.length-fut.length;
atras = 0;
hoje = itens.filter(c=>dias(c.data)===0).length;
}else{
const ab = itens.filter(t=>t.status!==CONCLUIDA);
abertos = ab.length;
conc = itens.length-ab.length;
atras = ab.filter(t=>{const d=dias(t.data); return d!==null && d<0;}).length;
hoje = ab.filter(t=>dias(t.data)===0).length;
}
const sete = cx.id==='comp'
? itens.filter(c=>{const d=dias(c.data); return d!==null&&d>0&&d<=7;}).length
: itens.filter(t=>t.status!==CONCLUIDA).filter(t=>{const d=dias(t.data); return d!==null&&d>0&&d<=7;}).length;
const marcas=[];
if(atras) marcas.push(''+atras+' em atraso');
if(hoje) marcas.push(''+hoje+' hoje');
if(sete) marcas.push(''+sete+' em 7 dias');
if(conc) marcas.push(''+conc+(cx.id==='comp'?' já passou':' concluído'+(conc>1?'s':''))+'');
if(!marcas.length) marcas.push('nada pendente');
return '';
}).join('');
}
function vgAbrirCaixa(id){
const cx=VG_CAIXAS.find(c=>c.id===id); if(!cx) return;
const quem = vgEuMesmo() ? 'Suas demandas' : 'Demandas de '+vgNomeAlvo();
if(id==='comp'){
const itens = vgCompsDe(vgAlvo()).slice().sort((a,b)=>String(b.data).localeCompare(String(a.data)));
abrirListaVG(quem, cx.nome, 'Compromissos da agenda pessoal — os particulares aparecem apenas como "Ocupado" para quem não participa.',
itens, 'comp', ()=>vgAbrirCaixa(id));
return;
}
UI.ordem='data-asc';
const itens = vgItensCaixa(id).slice().sort((a,b)=>{
const ca=a.status===CONCLUIDA?1:0, cb=b.status===CONCLUIDA?1:0;
return ca-cb || ordena(a,b);
});
abrirListaVG(quem, cx.nome, 'Em aberto primeiro, por data de entrega mais próxima.', itens, 'tarefa', ()=>vgAbrirCaixa(id));
}
/* Lista genérica reaproveitando a janela dos indicadores */
function abrirListaVG(kind, titulo, sub, itens, modo, recarregar){
UI.listaModo='vg';
UI.vgLista = recarregar || null;
$('lk-kind').textContent = kind+' · '+itens.length+' registro'+(itens.length===1?'':'s');
$('lk-titulo').textContent = titulo;
$('lk-sub').textContent = sub||'';
const linha = modo==='comp' ? linhaComp : modo==='cliente' ? linhaCliente : linhaLista;
$('lk-body').innerHTML = itens.length
? itens.map(linha).join('')
: '
'+(vgEuMesmo()?'Concluídos e tempo de execução':'Concluídos e tempo de execução de '+esc(vgPrimeiroNome()))+
''+per.map(p=>'').join('')+'
'+
'
Os quatro números abaixo somam tudo o que já foi concluído em cada janela de tempo — '+
'clique em qualquer um para abrir a lista. O período escolhido no botão acima muda o gráfico e a média destacada.
'+
'
'+kpis+'
'+
'
'+
'
'+
'
Quantos foram concluídos por '+VG_PERIODOS[v.prodPer].toLowerCase()+'
'+
'
'+barras+'
'+
'
Cada barra abrange '+({dia:'um dia',semana:'uma semana',mes:'um mês',ano:'um ano'}[v.prodPer])+
' fechad'+(v.prodPer==='semana'?'a':'o')+', do mais antigo à esquerda para o mais recente à direita.
'+
'
'+
'
'+
'
Quanto tempo levou entre a criação e a conclusão
'+
'
'+
'
Média '+rotPer+''+
''+(tpPer?fmtDur(tpPer.media):'—')+''+
''+(tpPer? tpPer.n+' registro'+(tpPer.n===1?'':'s')+' concluído'+(tpPer.n===1?'':'s')+' no período'
: 'nada concluído neste período')+'
'+
'
Média geral'+(tpTot?fmtDur(tpTot.media):'—')+''+
''+(tpTot? 'em '+tpTot.n+' registro'+(tpTot.n===1?'':'s') : 'ainda sem histórico')+'
'+
'
Mais rápido'+(tpTot?fmtDur(tpTot.min):'—')+''+
'melhor marca
'+
'
Mais demorado'+(tpTot?fmtDur(tpTot.max):'—')+''+
'ponto de atenção
'+
'
Tempo total dedicado'+(tpTot?fmtDur(tpTot.soma):'—')+''+
'soma de todos os registros concluídos
'+
'
'+
'
O contador começa quando o registro é criado — por qualquer pessoa do escritório — '+
'e para quando '+quem+' o registro.
'+
'
'+
'
'+
'
';
}
function vgAbrirConcluidas(p){
UI.ordem='data-desc';
const l = vgNoPeriodo(vgConcluidas(vgAlvo()), p).slice()
.sort((a,b)=>String(b.concluidoEm).localeCompare(String(a.concluidoEm)));
const rot = {dia:'hoje', semana:'nesta semana', mes:'neste mês', ano:'neste ano'}[p];
abrirListaVG((vgEuMesmo()?'Concluídos por mim':'Concluídos por '+vgNomeAlvo()),
'Concluídos '+rot,
l.length ? 'Do mais recente para o mais antigo. Tempo médio: '+fmtDur(vgTempos(l).media)+'.' : '',
l, 'tarefa', ()=>vgAbrirConcluidas(p));
}
/* =========================================================
KANBAN DA MINHA VISÃO
========================================================= */
const VG_GRUPOS = {data:'Data de entrega', tipo:'Tipo de demanda', status:'Status',
prioridade:'Prioridade Alta', sugestao:'Sugestões de Ordem Diária'};
function renderVgBarra(){
const v=vgEstado();
$('vg-bar').innerHTML =
'Agrupar por'+
'
'+ Object.keys(VG_GRUPOS).map(g=>
'').join('')+
'
'+
(v.grupo==='sugestao'
? ''
: '')+
''+
'';
const hint = {
data:'Suas demandas separadas por quando precisam sair — as vencidas e as de hoje ficam sempre à vista, mesmo vazias. Arraste o quadro para o lado para ver as faixas seguintes.',
tipo:'Uma coluna para cada natureza de registro — prazos, audiências, tarefas, perícias, avaliações sociais, atendimentos e financeiro. Só aparecem os tipos que você tem em aberto.',
status:'Em que estágio cada demanda está. Arraste o cartão de uma coluna para outra para mudar o status sem abrir o registro.',
prioridade:'O que foi marcado como prioridade alta vem primeiro, independentemente da data. Útil para conferir se o urgente e o importante estão alinhados.',
sugestao:'Ordem sugerida pelo sistema considerando risco de prazo, hora marcada com terceiros, prioridade e dependência de retorno. Cada cartão traz a posição, o que fazer primeiro e um botão que copia o pedido pronto para o Claude.'
}[v.grupo];
const el=$('vg-hint'); if(el) el.textContent = hint;
}
/* A preferência acompanha o usuário nas próximas aberturas. */
function vgSemVazias(on){
const v=vgEstado(); v.semVazias=!!on;
S.cfg.vgSemVazias=!!on; salvar();
renderVgBarra(); renderVgKanban();
toast(on?'As colunas sem cartão ficam ocultas.':'Todas as colunas voltaram a aparecer.');
}
function vgAbertas(){
return vgTarefasDe(vgAlvo()).filter(t=>t.status!==CONCLUIDA);
}
/* Peso de urgência usado na sugestão de ordem do dia. */
function vgScore(t){
const d=dias(t.data);
let s=0;
if(d===null) s+=10;
else if(d<0) s+=100+Math.min(30,Math.abs(d));
else if(d===0) s+=92;
else if(d===1) s+=74;
else if(d<=3) s+=58;
else if(d<=7) s+=42;
else if(d<=15) s+=26;
else s+=12;
s += ({Alta:26,'Média':10,Baixa:0})[t.prioridade||'Média'] || 10;
if(t.tipo==='audiencia'||t.tipo==='pericia'||t.tipo==='avaliacao') s+=18; /* hora marcada com terceiros */
if(t.tipo==='prazo') s+=14; /* preclusão não espera */
if(t.hora) s+=6;
if(t.status==='Últimos ajustes') s+=16;
else if(t.status==='Em revisão') s+=12;
else if(t.status==='Em andamento') s+=9;
else if(t.status===ESPERA) s-=26;
const ck = t.checklist && t.checklist.ativo ? (t.checklist.itens||[]) : null;
if(ck && ck.length && ck.filter(i=>i.ok).length/ck.length>=.6) s+=8;
return s;
}
const VG_RECEITA = {
prazo:'Reserve um bloco sem interrupções: leia os autos, monte a tese e deixe a peça revisada antes de protocolar.',
audiencia:'Confirme link ou endereço, revise a tese e fale com o cliente na véspera para alinhar o depoimento.',
tarefa:'Quebre em subtarefas curtas e feche uma de cada vez — comece pela que destrava as demais.',
pericia:'Confirme dia, hora e endereço com o cliente e reforce quais documentos e exames ele deve levar.',
avaliacao:'Alinhe com o cliente o que a assistente social observa e quem precisa estar presente na visita.',
atendimento:'Releia o histórico do cliente antes da conversa e registre os encaminhamentos assim que encerrar.',
financeiro:'Confira valores e comprovantes e faça o lançamento na Gestão Financeira no mesmo dia.'
};
function vgComoFazer(t){
const d=dias(t.data), partes=[];
if(t.status===ESPERA) partes.push('Está aguardando retorno: cobre a resposta hoje e registre em comentário o que foi feito.');
else if(d!==null && d<0) partes.push('Já venceu há '+Math.abs(d)+' dia'+(Math.abs(d)>1?'s':'')+' — trate como primeira tarefa do dia.');
else if(d===0) partes.push('Vence hoje — bloqueie o horário agora.');
else if(d===1) partes.push('Vence amanhã — deixe pronto hoje para revisar com calma.');
partes.push(VG_RECEITA[t.tipo]||'Defina o próximo passo concreto e registre no cartão.');
return partes.join(' ');
}
function vgColunasVisao(lista){
const v=vgEstado();
if(v.grupo==='data'){
const faixa = t=>{ const d=dias(t.data);
if(d===null) return 'sem'; if(d<0) return 'atraso'; if(d===0) return 'hoje';
if(d===1) return 'amanha'; if(d<=7) return 'semana'; if(d<=30) return 'mes'; return 'depois'; };
const defs=[
{id:'atraso',nome:'Em atraso', dot:'#b3261e'},
{id:'hoje', nome:'Entrega hoje', dot:'#e9a23b'},
{id:'amanha',nome:'Amanhã', dot:'#c9862a'},
{id:'semana',nome:'Próximos 7 dias', dot:'#8a6d1f'},
{id:'mes', nome:'Até 30 dias', dot:'#2F6FBF'},
{id:'depois',nome:'Mais adiante', dot:'#2d5486'},
{id:'sem', nome:'Sem data definida',dot:'#9aa2ae'}
];
return defs.map(c=>({id:c.id,nome:c.nome,dot:c.dot,itens:lista.filter(t=>faixa(t)===c.id)}))
.filter(c=>c.itens.length || (!v.semVazias && (c.id==='atraso' || c.id==='hoje')));
}
if(v.grupo==='tipo'){
return Object.keys(TIPOS).map(k=>({id:k, nome:TIPOS[k].nome, dot:TIPO_COR[k]||'#2d5486',
itens:lista.filter(t=>t.tipo===k)})).filter(c=>c.itens.length);
}
if(v.grupo==='status'){
return ABERTOS.map(x=>({id:x, nome:x, dot:STATUS_DOT[x],
itens:lista.filter(t=>t.status===x)})).filter(c=>c.itens.length || !v.semVazias);
}
if(v.grupo==='prioridade'){
const defs=[{id:'Alta',nome:'▲ Prioridade alta',dot:'#b3261e'},
{id:'Média',nome:'Prioridade média',dot:'#c9862a'},
{id:'Baixa',nome:'Prioridade baixa',dot:'#9fc7b4'}];
return defs.map(c=>({id:c.id,nome:c.nome,dot:c.dot,
itens:lista.filter(t=>(t.prioridade||'Média')===c.id)}))
.filter(c=>c.itens.length || (!v.semVazias && c.id==='Alta'));
}
/* sugestão de ordem diária */
const ord = lista.slice().sort((a,b)=>vgScore(b)-vgScore(a) || ordena(a,b));
const espera=[], agora=[], hoje=[], semana=[], depois=[];
ord.forEach(t=>{
if(t.status===ESPERA){ espera.push(t); return; }
const d=dias(t.data);
if(agora.length<3){ agora.push(t); return; }
if(d!==null && d<=1) hoje.push(t);
else if(d!==null && d<=7) semana.push(t);
else depois.push(t);
});
const cols=[
{id:'agora', nome:'Comece por aqui', dot:'#b3261e', itens:agora},
{id:'hoje', nome:'Ainda hoje', dot:'#e9a23b', itens:hoje},
{id:'semana',nome:'No decorrer da semana', dot:'#8a6d1f', itens:semana},
{id:'depois',nome:'Pode aguardar', dot:'#2d5486', itens:depois},
{id:'espera',nome:'Depende de terceiros', dot:'#E9954A', itens:espera}
];
return cols.filter(c=>c.itens.length || (!v.semVazias && c.id==='agora'));
}
function renderVgKanban(){
const v=vgEstado();
UI.ordem = 'data-asc';
const lista = vgAbertas();
const el=$('vg-kanban');
const cnt=$('vg-conta');
if(cnt) cnt.textContent = lista.length+' registro'+(lista.length===1?'':'s')+' em aberto';
if(!lista.length){
el.innerHTML = '
'+
(vgEuMesmo()?'Nada em aberto sob sua responsabilidade':'Nada em aberto para '+esc(vgNomeAlvo()))+''+
'Assim que um prazo, audiência, tarefa, perícia, avaliação ou atendimento for atribuído, ele aparece aqui.
';
return;
}
/* posição sugerida, calculada uma vez para toda a lista */
const rank = new Map();
lista.slice().sort((a,b)=>vgScore(b)-vgScore(a) || ordena(a,b)).forEach((t,i)=>rank.set(t.id,i+1));
const cols = vgColunasVisao(lista);
el.innerHTML = cols.map(c=>{
const itens = v.grupo==='sugestao' ? c.itens : c.itens.slice().sort(ordena);
const arr = v.grupo==='status'
? ' ondragover="colOver(event)" ondragleave="colLeave(event)" ondrop="vgColDrop(event)"' : '';
return '
'+(v.grupo==='status'?'Arraste um cartão para cá':'Nada nesta faixa')+'
') +
'
';
}).join('');
}
/* Soltar um cartão em outra coluna do Início muda o status do registro. */
function vgColDrop(e){
e.preventDefault(); e.currentTarget.classList.remove('over');
if(vgEstado().grupo!=='status') return;
const id = dragId || +(e.dataTransfer.getData('text/plain')||0);
const t = S.tarefas.find(x=>x.id===id); if(!t) return;
if(!podeEditar(t)){ toast('Você não tem permissão para alterar este registro.'); return; }
const novo = e.currentTarget.dataset.col;
if(!novo || t.status===novo) return;
aplicarStatus(t.id, novo, false);
}
/* ---------- pedidos prontos para o Claude ---------- */
function vgLinhaPedido(t,i){
const d=dias(t.data);
return (i!=null?(i+1)+'. ':'')+TIPOS[t.tipo].kind+' — '+t.descricao+
' | ação: '+(t.acao||'não definida')+
' | entrega: '+(t.data?fmtD(t.data)+(t.hora?' às '+t.hora:''):'sem data')+
(d!==null && d<0?' (vencido há '+Math.abs(d)+'d)':d===0?' (hoje)':'')+
' | prioridade: '+(t.prioridade||'Média')+
' | status: '+t.status+
' | cliente: '+((t.clientes||[]).map(id=>{const c=cliente(id);return c?c.nome:'';}).filter(Boolean).join(', ')||'não relacionado')+
(t.processo?' | processo: '+t.processo:'');
}
function vgRoteiroDoDia(){
const lista = vgAbertas().slice().sort((a,b)=>vgScore(b)-vgScore(a) || ordena(a,b)).slice(0,12);
if(!lista.length){ toast('Não há registros em aberto para montar o roteiro.'); return; }
const txt =
'Sou da equipe do escritório Schneider e Bahnert Advogados, que atua em Direito do Trabalho (lado do empregado) e Direito Previdenciário. '+
'Abaixo estão as minhas demandas em aberto de hoje ('+fmtD(iso(hoje0()))+'). '+
'Monte uma ordem de execução para o meu dia, explicando em cada item por que ele vem nessa posição, '+
'quanto tempo reservar e qual é o primeiro passo concreto. '+
'Considere risco de preclusão, compromissos com hora marcada e dependências de terceiros. '+
'Ao final, aponte o que dá para delegar ou adiar sem prejuízo.\n\n'+
lista.map((t,i)=>vgLinhaPedido(t,i)).join('\n');
copiar(txt,'Roteiro do dia');
}
function vgPedirClaude(id){
const t=S.tarefas.find(x=>x.id===id); if(!t) return;
const txt =
'Sou da equipe do escritório Schneider e Bahnert Advogados (Trabalhista para o empregado e Previdenciário). '+
'Preciso executar a demanda abaixo. Me diga, em passos objetivos, como conduzi-la do início ao fim, '+
'o que preciso reunir antes de começar, os pontos de atenção técnicos e um rascunho do que devo produzir.\n\n'+
vgLinhaPedido(t,null)+
((t.checklist && t.checklist.ativo && (t.checklist.itens||[]).length)
? '\nSubtarefas já previstas: '+t.checklist.itens.map(i=>(i.ok?'[feito] ':'[pendente] ')+i.txt).join('; ')
: '');
copiar(txt,'Pedido para o Claude');
}
/* =========================================================
GRÁFICOS — desenhados na própria página, sem biblioteca externa
========================================================= */
const TIPO_COR = {prazo:'#C62828', audiencia:'#1D6FE0', tarefa:'#2d5486', pericia:'#6b3fa0',
avaliacao:'#c9862a', atendimento:'#127a52', financeiro:'#8a6d1f'};
const ORIG_COR = {
'Parceria (parceiro operacional)':'#2d5486',
'Parceria (parceiro prospector)':'#6d9ad4',
'Google Ads':'#1D6FE0',
'Meta Ads':'#6b3fa0',
'Google Meu Negócio':'#127a52',
'Indicação':'#EA6A0A',
'Conhecido de sócio':'#B8860B',
'Conhecido de Colab. ou de Assoc.':'#8C3A44',
'Sem origem':'#9aa2ae'
};
const AREA_COR = {'Previdenciário':'#EA6A0A','Trabalhista':'#B8860B','Sucessões':'#8C3A44','Família':'#c1553a',
'Cível':'#6b3fa0','Consumidor':'#0e7490','Criminal':'#3a4a60','Tributário':'#92400e','Empresarial':'#4338ca',
'Administrativo':'#0f6a48','Imobiliário':'#6d28d9','Bancário':'#2b4fc7','Médico e da Saúde':'#9c2b24',
'Outra':'#9aa2ae','Sem área':'#9aa2ae'};
function corArea(a){ return AREA_COR[a] || '#2d5486'; }
/* barras verticais — itens: {rot, n, sel, acao, dica} */
function gBarrasV(itens, opts){
opts = opts || {};
const topo = Math.max.apply(null, itens.map(i=>i.n).concat([1]));
return '
';
}
/* =========================================================
FECHAMENTO DE CONTRATOS
========================================================= */
const UF_NOME = (function(){ const o={}; ESTADOS.forEach(e=>o[e[0]]=e[1]); return o; })();
function ctrEstado(){
const v=vgEstado();
if(v.ctrAno==null){ const h=hoje0(); v.ctrAno=h.getFullYear(); v.ctrMes=h.getMonth(); }
if(!v.ctrEsc) v.ctrEsc='mes';
return v;
}
function ctrSet(campo, valor){ const v=ctrEstado(); v[campo]= (campo==='ctrEsc') ? valor : +valor; renderVgContratos(); }
function contratosTodos(){ return S.clientes.filter(c=>pd(c.dataContrato)); }
function contratosDe(ano, mes){
return contratosTodos().filter(c=>{
const d=pd(c.dataContrato);
return d.getFullYear()===ano && (mes==null || d.getMonth()===mes);
});
}
function ctrAnosDisponiveis(){
const set=new Set(contratosTodos().map(c=>pd(c.dataContrato).getFullYear()));
set.add(hoje0().getFullYear());
return [...set].sort((a,b)=>b-a);
}
function ctrEscopo(){
const v=ctrEstado();
return v.ctrEsc==='ano' ? contratosDe(v.ctrAno, null) : contratosDe(v.ctrAno, v.ctrMes);
}
function ctrRotuloEscopo(){
const v=ctrEstado();
return v.ctrEsc==='ano' ? 'em '+v.ctrAno : 'em '+MESES_NOME[v.ctrMes]+' de '+v.ctrAno;
}
/* UFs que só apareceram a partir do período escolhido */
function ufNovas(lista){
const v=ctrEstado();
const corte = v.ctrEsc==='ano' ? new Date(v.ctrAno,0,1) : new Date(v.ctrAno, v.ctrMes, 1);
const antes = new Set(contratosTodos().filter(c=>pd(c.dataContrato) < corte).map(c=>c.estado).filter(Boolean));
return new Set(lista.map(c=>c.estado).filter(Boolean).filter(uf=>!antes.has(uf)));
}
function ctrLista(titulo, sub, lista){
abrirListaVG('Contratos · '+ctrRotuloEscopo(), titulo, sub,
lista.slice().sort((a,b)=>String(b.dataContrato).localeCompare(String(a.dataContrato))), 'cliente', null);
}
function ctrListaMes(ano, mes){
const l = contratosDe(ano, mes);
abrirListaVG('Contratos', cap1(MESES_NOME[mes])+' de '+ano,
'Contratos com data de assinatura dentro do mês, do mais recente para o mais antigo.',
l.slice().sort((a,b)=>String(b.dataContrato).localeCompare(String(a.dataContrato))), 'cliente',
()=>ctrListaMes(ano,mes));
}
function ctrListaPor(campo, valor){
const lista = ctrEscopo().filter(c=> (c[campo]||'') === (valor==='—'?'':valor) );
const rot = campo==='estado' ? (UF_NOME[valor]||valor) : valor;
ctrLista(rot, 'Contratos fechados '+ctrRotuloEscopo()+'.', lista);
}
/* Cada bloco de número abre a lista completa por trás dele. */
function ctrKpiLista(qual){
const v = ctrEstado();
const recente = (a,b)=>String(b.dataContrato).localeCompare(String(a.dataContrato));
let lista, titulo, sub;
if(qual==='mes'){
lista = contratosDe(v.ctrAno, v.ctrMes).slice().sort(recente);
titulo = 'Contratos fechados em '+cap1(MESES_NOME[v.ctrMes])+' de '+v.ctrAno;
sub = 'Data de assinatura dentro do mês, do mais recente para o mais antigo.';
} else if(qual==='ano'){
lista = contratosDe(v.ctrAno, null).slice().sort(recente);
titulo = 'Contratos fechados em '+v.ctrAno;
sub = 'Todos os contratos assinados no ano, do mais recente para o mais antigo.';
} else if(qual==='uf'){
const novas = ufNovas(ctrEscopo());
lista = ctrEscopo().filter(c=>c.estado).slice()
.sort((a,b)=> String(a.estado).localeCompare(String(b.estado)) || recente(a,b));
const ufs = [...new Set(lista.map(c=>c.estado))];
titulo = 'Estados alcançados '+ctrRotuloEscopo();
sub = ufs.length
? ufs.map(u=>(UF_NOME[u]||u)+(novas.has(u)?' (inédito)':'')).join(' · ')+'.'
: 'Nenhum contrato com estado informado neste período.';
} else {
lista = contratosTodos().slice().sort(recente);
titulo = 'Histórico completo de contratos';
sub = 'Todos os contratos com data registrada desde o início, do mais recente para o mais antigo.';
}
abrirListaVG('Fechamento de contratos', titulo, sub, lista, 'cliente', ()=>ctrKpiLista(qual));
}
function renderVgContratos(){
const v = ctrEstado();
const anos = ctrAnosDisponiveis();
if(anos.indexOf(v.ctrAno)<0) v.ctrAno = anos[0];
/* cabeçalho: mês e ano */
$('ctr-sel').innerHTML =
'Ver'+
''+
''+
'
'+
''+
''+
'
';
const doMes = contratosDe(v.ctrAno, v.ctrMes);
const doAno = contratosDe(v.ctrAno, null);
const antMes = v.ctrMes===0 ? 11 : v.ctrMes-1;
const antAno = v.ctrMes===0 ? v.ctrAno-1 : v.ctrAno;
const anterior = contratosDe(antAno, antMes).length;
const dif = doMes.length - anterior;
const pct = anterior ? Math.round((dif/anterior)*100) : (doMes.length?100:0);
const escopo = ctrEscopo();
const novas = ufNovas(escopo);
const ufs = [...new Set(escopo.map(c=>c.estado).filter(Boolean))];
const hoje = hoje0();
const mesesCorridos = v.ctrAno===hoje.getFullYear() ? hoje.getMonth()+1 : 12;
const media = doAno.length ? doAno.length/mesesCorridos : 0;
const bloco = (cls,chave,k,val,n) =>
'';
$('ctr-kpi').innerHTML =
bloco('destaque','mes','Contratos em '+cap1(MESES_NOME[v.ctrMes]), doMes.length,
[(anterior===0 && !doMes.length ? 'nenhum contrato no mês'
: dif===0 ? 'mesmo número do mês anterior'
: (dif>0?'▲ +':'▼ ')+dif+' em relação a '+MESES_NOME[antMes]+' ('+(dif>0?'+':'')+pct+'%)'),
(dif>0?'sobe':dif<0?'desce':'')])+
bloco('','ano','Total em '+v.ctrAno, doAno.length,
['média de '+media.toFixed(1).replace('.',',')+' por mês'])+
bloco('','uf','Estados alcançados '+(v.ctrEsc==='ano'?'no ano':'no mês'), ufs.length,
[(novas.size? novas.size+' alcançado'+(novas.size>1?'s':'')+' pela primeira vez' : 'nenhum estado inédito')])+
bloco('','tudo','Histórico completo', contratosTodos().length,
['contratos com data registrada desde o início']);
/* --- contratos por mês, no ano escolhido --- */
const barras = MESES_NOME.map((m,i)=>({
rot: m.slice(0,3),
n: contratosDe(v.ctrAno,i).length,
sel: i===v.ctrMes,
acao: 'ctrSet(\'ctrMes\','+i+')',
dica: cap1(m)+' de '+v.ctrAno+': '+contratosDe(v.ctrAno,i).length+' contrato(s) — clique para ver este mês'
}));
$('ctr-mes').innerHTML =
'
Contratos fechados por mês em '+v.ctrAno+
'
'+
gBarrasV(barras)+
'
Clique em uma barra para trocar o mês analisado nos quadros abaixo. '+
'O registro é permanente: qualquer mês já cadastrado continua disponível.
'+(novas.size? 'Em verde, os estados alcançados pela primeira vez pelo escritório. ':'')+
(semUf? semUf+' contrato(s) sem estado informado. ':'')+
'No histórico completo o escritório já atendeu '+
new Set(contratosTodos().map(c=>c.estado).filter(Boolean)).size+' estado(s).
'+(houve
? 'O tempo médio até concluir conta da criação do registro — por qualquer pessoa — até a conclusão pelo responsável.'
: 'Ainda não há conclusões registradas no período escolhido. Troque o período para ver o histórico.')+'
'+
'
';
}
function vgAbrirProdutividade(membroId){
const v=vgEstado();
const l = vgBaseGestao().filter(t=>(t.responsaveis||[]).indexOf(membroId)>=0 &&
t.status===CONCLUIDA && t.concluidoEm && vgPeriodoGestao(t, v.indPer))
.sort((a,b)=>String(b.concluidoEm).localeCompare(String(a.concluidoEm)));
const tp = vgTempos(l);
abrirListaVG(membro(membroId)?membro(membroId).nome:'—', 'Registros concluídos',
(l.length? 'Tempo médio de '+fmtDur(tp.media)+' entre a criação e a conclusão.' : ''),
l, 'tarefa', ()=>vgAbrirProdutividade(membroId));
}
/* ---------- sugestões de melhoria e alertas ---------- */
function vgChipsPorResponsavel(lista, chave){
const mapa = new Map();
lista.forEach(t=>{
const rs = (t.responsaveis||[]);
if(!rs.length){ mapa.set(0,(mapa.get(0)||0)+1); return; }
rs.forEach(id=>mapa.set(id,(mapa.get(id)||0)+1));
});
return [...mapa.entries()].sort((a,b)=>b[1]-a[1]).map(([id,n])=>{
const m = id?membro(id):null;
return '';
}).join('');
}
let VG_ALERTAS = {};
function vgAlertaLista(chave, respId){
const a = VG_ALERTAS[chave]; if(!a) return;
const l = (respId==null ? a.itens : a.itens.filter(t=> respId
? (t.responsaveis||[]).indexOf(respId)>=0
: !(t.responsaveis||[]).length ));
UI.ordem='data-asc';
abrirListaVG('Alerta · '+(respId==null?'todos':(membro(respId)?membro(respId).nome:'Sem responsável')),
a.titulo, a.desc, l.slice().sort(ordena), 'tarefa', ()=>vgAlertaLista(chave, respId));
}
function renderVgAlertas(){
const base = vgBaseGestao();
const abertas = base.filter(t=>t.status!==CONCLUIDA);
VG_ALERTAS = {};
const paradas = abertas.filter(estaParada);
const vencidos = abertas.filter(t=>{const d=dias(t.data); return d!==null && d<0;});
const espera = abertas.filter(t=>{
if(t.status!==ESPERA) return false;
const mov = ultimaMovimentacao(t) || t.alteradoEm || t.criadoEm;
return diasUteisDesde(mov) >= 5;
});
const semResp = abertas.filter(t=>!(t.responsaveis||[]).length);
const semData = abertas.filter(t=>!t.data);
const finPend = (typeof finPendentes==='function') ? finPendentes() : [];
/* carga desequilibrada */
const cargas = atribuiveis().map(m=>({m:m, n:abertas.filter(t=>(t.responsaveis||[]).indexOf(m.id)>=0).length}));
const totalCarga = cargas.reduce((a,b)=>a+b.n,0);
const mediaCarga = cargas.length ? totalCarga/cargas.length : 0;
const sobre = cargas.filter(c=>c.n>=5 && mediaCarga>0 && c.n >= mediaCarga*1.6);
const defs = [
{k:'parada', sev:'a1', ic:'⏳', titulo:'Entrega em até 2 dias úteis e sem movimentação',
itens:paradas,
desc:'Estes registros vencem em até dois dias úteis e não receberam nenhum sinal de trabalho nos últimos dois dias úteis — '+
'nenhum comentário dizendo que está em andamento e nenhuma mudança de status. Vale procurar o responsável antes que vire urgência.'},
{k:'vencido', sev:'a1', ic:'⚠', titulo:'Vencidos e ainda em aberto',
itens:vencidos,
desc:'A data de entrega já passou e o registro continua aberto. Ou o prazo foi cumprido e falta atualizar o status, ou há risco real — vale confirmar caso a caso.'},
{k:'espera', sev:'a2', ic:'⏸', titulo:'Parados em espera há mais de uma semana útil',
itens:espera,
desc:'Aguardar retorno de terceiro é legítimo, mas sem reiteração vira esquecimento. Combine com o responsável uma data para cobrar novamente.'},
{k:'semresp', sev:'a2', ic:'👤', titulo:'Sem responsável definido',
itens:semResp,
desc:'Registro sem dono não entra na rotina de ninguém. Atribua um responsável para que apareça na visão pessoal dele.'},
{k:'semdata', sev:'a3', ic:'📅', titulo:'Sem data de entrega',
itens:semData,
desc:'Sem data, o registro não aparece na agenda nem nos alertas de vencimento. Defina ao menos uma data-meta interna.'},
{k:'fin', sev:'a3', ic:'R$', titulo:'Pendentes de lançamento na Gestão Financeira',
itens:finPend,
desc:'Honorário contratual, RPV, precatório ou outros valores registrados aqui e ainda não baixados no módulo financeiro.'}
].filter(d=>d.itens && d.itens.length);
defs.forEach(d=>{ VG_ALERTAS[d.k] = {titulo:d.titulo, desc:d.desc, itens:d.itens}; });
let html = defs.map(d=>
'
'+
'
'+d.ic+''+esc(d.titulo)+''+d.itens.length+'
'+
'
'+esc(d.desc)+'
'+
'
'+vgChipsPorResponsavel(d.itens, d.k)+'
'+
''+
'
').join('');
if(sobre.length){
html += '
⚖Carga concentrada em poucas pessoas'+
''+sobre.length+'
'+
'
'+esc(sobre.map(c=>c.m.nome.split(/\s+/).slice(0,2).join(' ')+' está com '+c.n+' registros em aberto').join('; '))+
'. A média do escritório é de '+mediaCarga.toFixed(1).replace('.',',')+' por pessoa. '+
'Vale redistribuir o que não depende de conhecimento específico do caso.
';
}
if(!html){
html = '
✓Nenhum alerta no momento
'+
'
Nada vencido, nada parado às vésperas do prazo e todos os registros têm responsável. '+
'Os alertas voltam a aparecer automaticamente assim que algum registro perder o ritmo.
';
}
$('vg-alertas').innerHTML = html;
}
function renderPainel(){
if(PAINEIS_KANBAN[UI.painel]) renderKanban(UI.painel);
if(UI.painel==='visao') renderVisao();
if(UI.painel==='clientes') renderClientes();
if(UI.painel==='financeiro') montarFinanceiro();
if(UI.painel==='rapido') renderRapido();
renderAgenda(); /* a agenda acompanha qualquer alteração */
renderAlertasFin();
renderPopNotif(); /* os avisos mudam de canto na Gestão Financeira */
}
function redesenhar(){ renderHeaderUser(); renderTabs(); renderPainel(); renderRascunhos(); renderSino();
renderPopNotif(); if($('ov-cfg').classList.contains('on')) renderCfg(); }
/* =========================================================
MODAL DE REGISTRO (prazo, audiência, tarefa, perícia,
avaliação social, atendimento, financeiro)
========================================================= */
function novo(tipo, rascunho){
if(!pode('criar')){ toast('Seu perfil não tem permissão para criar registros.'); return; }
if(tipo==='financeiro' && !podeFinanceiro()){
toast('Registros financeiros são restritos à Administração e aos sócios.'); return; }
UI.tarefaEdit = {novo:true, tipo:tipo, id:null};
UI.arquivosTmp = [];
montarForm(tipo, null);
$('t-kind').textContent = 'Novo registro · '+TIPOS[tipo].kind;
$('t-titulo').textContent = ({prazo:'Novo prazo',audiencia:'Nova audiência',tarefa:'Nova tarefa',
pericia:'Nova perícia',avaliacao:'Nova avaliação social',atendimento:'Novo atendimento',financeiro:'Novo registro financeiro'})[tipo];
$('t-del').style.display='none';
$('t-cmt-wrap').style.display='';
$('t-cmt-list').innerHTML=''; $('t-cmt-n').textContent=''; $('t-cmt-novo').value='';
$('t-cmt-acao').style.display='none'; /* sem registro salvo, o comentário entra junto com o Salvar */
$('t-hist-wrap').style.display='none';
statusMudou();
$('t-criado').value = eu().nome+' · '+fmtDH(agoraISO());
$('t-alt').value = '—';
$('t-link').value=''; $('t-files').innerHTML=''; $('t-err').classList.remove('on','aviso');
$('t-salvar').style.display=''; travarForm(false);
if(rascunho){ preencherForm(rascunho); toast('Rascunho recuperado. Revise e salve.'); }
else {
const r=(S.rascunhos||[]).find(x=>x.tipo===tipo && x.autorId===eu().id);
if(r && confirm('Há um rascunho de '+TIPOS[tipo].kind.toLowerCase()+' guardado ('+ (r.dados.descricao||'sem descrição') +').\n\nRecuperar esse rascunho?')) preencherForm(r.dados);
}
abrir('ov-tarefa');
}
function abrirTarefa(id){
const t = S.tarefas.find(x=>x.id===id); if(!t) return;
UI.tarefaEdit = {novo:false, tipo:t.tipo, id:id, dataOrig:t.data||''};
UI.arquivosTmp = (t.arquivos||[]).slice();
montarForm(t.tipo, t);
$('t-kind').textContent = TIPOS[t.tipo].kind+' · '+PAINEIS.find(p=>p.k===TIPOS[t.tipo].painel).nome;
$('t-titulo').textContent = t.descricao || TIPOS[t.tipo].nome;
$('t-del').style.display = podeExcluir(t)?'':'none';
$('t-cmt-wrap').style.display=''; $('t-cmt-acao').style.display=''; $('t-hist-wrap').style.display='';
$('t-cmt-novo').value=''; statusMudou();
$('t-criado').value = (t.criadoPor||'—')+' · '+fmtDH(t.criadoEm);
$('t-alt').value = t.alteradoEm ? (t.alteradoPor||'—')+' · '+fmtDH(t.alteradoEm) : '—';
$('t-link').value = t.link||'';
renderArquivos(); renderComentarios(t); renderHistorico(t);
const ro = !podeEditar(t);
$('t-salvar').style.display = ro?'none':'';
travarForm(ro);
if(ro) toast('Somente leitura: este registro é de outro integrante.');
$('t-err').classList.remove('on','aviso');
abrir('ov-tarefa');
}
function montarForm(tipo, t){
if(tipo==='financeiro') sincronizarAssociados(true);
const T = TIPOS[tipo];
const clis = S.clientes.slice().sort((a,b)=>a.nome.localeCompare(b.nome));
const selCli = (t&&t.clientes)||[];
const selResp = (t&&t.responsaveis)||[];
const h = [];
h.push(campo('full','Descrição',true,
'','t-desc'));
h.push(campo('full','Cliente',true,
msHTML('t-cli','Digite o nome do cliente…',
'
'+
(clis.length?'':'')+
'
'),
't-cli','Comece a digitar para buscar. Pode selecionar mais de um.'));
h.push(campo('full','Área',false,'',null,
'Vem automaticamente do cadastro do cliente.'));
h.push(campo('full','Processo',false,
'',null,
'Puxa o número do processo do cadastro do cliente, quando houver. Pode ajustar se o cliente tiver mais de um.'));
h.push(campo('',T.dataLabel,true,'','t-data'));
h.push(campo('','Horário',false,'',null,
'Opcional. Sem horário, entra na agenda no início do dia.'));
h.push(campo('full','Motivo da alteração da data',false,
'','t-data-motivo',
'A nova data vai para o histórico de qualquer forma — o motivo explica a mudança para quem consultar depois.'));
h.push(campo('full','Status',true,
'
','t-status',
'Ao marcar Concluída, o registro vai para a visualização de concluídas.'));
h.push(campo('full','Ação a executar',true, msHTML('t-acao','Digite para filtrar as ações…'), 't-acao',
'Comece a digitar e a lista sugere as ações deste tipo de registro.'));
h.push(campo('full','Responsável',true,
msHTML('t-resp','Digite o nome do integrante…'),
't-resp','Comece a digitar para buscar. Pode selecionar mais de um — qualquer integrante do escritório.'));
$('t-campos').innerHTML = h.join('');
msInit('t-cli', clis.map(c=>({id:c.id, nome:c.nome, sub:[c.cpf, c.processo, (c.areas||[]).join(' · ')].filter(Boolean).join(' · '),
busca:[c.cpf,c.processo,(c.areas||[]).join(' ')].join(' ')})), selCli);
MS['t-cli'].onChange = syncAreas;
msInit('t-resp', atribuiveis().map(m=>({id:m.id, nome:m.nome,
sub:m.funcao + (m.status==='pendente'?' · convite pendente':''), busca:m.funcao+' '+(m.email||'')})), selResp);
msInit('t-acao', ACOES[tipo].map((a,i)=>({id:i, nome:a})), [], {unico:true});
if(t && t.acao) msDefinir('t-acao', t.acao);
dataMudou(); /* o motivo da nova data só aparece quando a data muda */
UI.semCliente = !!(t && t.semCliente);
aplicarSemCliente();
/* campos exclusivos do Financeiro */
if(tipo==='financeiro'){
const f=(t&&t.fin)||{};
$('t-fin').innerHTML = '
Dados financeiros
'+
campo('','Tipo',true,'','t-fin-tipo')+
campo('','Emitir NF',true,'','t-fin-nf')+
campo('','Advogado parceiro',false,selParceiro('t-fin-parc','PARCEIRO',f.parceiro,'Nenhum'),null,
'Vem de Gestão Financeira › Advogados parceiros e associados.')+
campo('','Associado envolvido',false,selParceiro('t-fin-assoc','ASSOCIADO',f.associado,'Nenhum'),null,
'Vem de Gestão Financeira › Advogados parceiros e associados.')+
'
';
} else $('t-fin').innerHTML='';
UI.prioTmp = (t && t.prioridade) || 'Média';
UI.checkTmp = t && t.checklist ? JSON.parse(JSON.stringify(t.checklist)) : {ativo:false, itens:[]};
renderPrio(); renderChecklist(); statusMudou();
UI.procManual = !!(t && t.processo); UI.procAuto = '';
montarTransferencia(t);
syncAreas();
}
function renderPrio(){
const v = UI.prioTmp||'Média';
const dica = {Baixa:'pode esperar a fila normal', 'Média':'segue o fluxo do escritório', Alta:'atenção imediata da equipe'};
$('t-prio').innerHTML = 'Prioridade
'+
PRIOS.map(x=>'').join('')+
'
'+esc(dica[v])+'';
}
function setPrio(v){
UI.prioTmp=v; renderPrio();
const t = UI.tarefaEdit && !UI.tarefaEdit.novo ? S.tarefas.find(x=>x.id===UI.tarefaEdit.id) : null;
if(t && !podeEditar(t)){ UI.prioTmp=t.prioridade||'Média'; renderPrio(); toast('Você não pode alterar este registro.'); }
}
/* ---------- subtarefas ---------- */
function tarefaAtual(){ return UI.tarefaEdit && !UI.tarefaEdit.novo ? S.tarefas.find(x=>x.id===UI.tarefaEdit.id) : null; }
function persistirCheck(txtHist){
const t=tarefaAtual();
if(t){ t.checklist = JSON.parse(JSON.stringify(UI.checkTmp));
if(txtHist){ hist(t,txtHist); t.alteradoEm=agoraISO(); t.alteradoPor=eu().nome; }
salvar(); renderHistorico(t); renderPainel(); }
}
function renderChecklist(){
const c = UI.checkTmp || {ativo:false,itens:[]};
if(!c.ativo){
$('t-check').innerHTML = '
Subtarefas
'+
'
'+
'Quebre este registro em etapas menores quando o trabalho tiver várias fases.'+
'
'+
'';
}
function ativarCheck(v){
UI.checkTmp = UI.checkTmp||{ativo:false,itens:[]};
if(!v && UI.checkTmp.itens.length && !confirm('Desativar remove as '+UI.checkTmp.itens.length+' subtarefas. Continuar?')) return;
UI.checkTmp.ativo=v; if(!v) UI.checkTmp.itens=[];
renderChecklist(); persistirCheck(v?'Checklist de subtarefas ativado.':'Checklist de subtarefas desativado.');
}
function addCheck(){
const el=$('ck-novo'), v=(el.value||'').trim(); if(!v) return;
UI.checkTmp.itens.push({txt:v, ok:false, por:'', em:''});
el.value=''; renderChecklist();
const n=$('ck-novo'); if(n) n.focus();
persistirCheck('Subtarefa adicionada: "'+v+'".');
}
function removerCheck(i){
const it=UI.checkTmp.itens[i]; if(!it) return;
UI.checkTmp.itens.splice(i,1); renderChecklist(); persistirCheck('Subtarefa removida: "'+it.txt+'".');
}
function marcarCheck(i,v){
const it=UI.checkTmp.itens[i]; if(!it) return;
it.ok=v; it.por=v?eu().nome:''; it.em=v?agoraISO():'';
renderChecklist(); persistirCheck(null);
const t=tarefaAtual(); if(t){ salvar(); renderPainel(); }
}
/* ---------- histórico de alterações ---------- */
function renderHistorico(t){
const h = (t && t.historico) || [];
$('t-hist-n').textContent = h.length? '('+h.length+')' : '';
$('t-hist-nota').innerHTML = ehAdmin()
? 'Registro automático de tudo o que muda neste item. Como Administração, você pode remover linhas — os demais perfis só conseguem ler.'
: 'Registro automático de tudo o que muda neste item. Não pode ser editado ou apagado por este perfil.';
$('t-hist-list').innerHTML = h.length
? '
'+h.slice().reverse().map((x,ri)=>{
const i = h.length-1-ri;
return '
'+esc(x.txt)+'
'+
'
'+esc(x.autor)+' · '+fmtDH(x.em)+'
'+
(ehAdmin()?'':'')+
'
';
}).join('')+'
'+
(ehAdmin()?'':'')
: '
Sem alterações registradas.
';
}
function apagarHist(i){
if(!ehAdmin()){ toast('Somente a Administração altera o histórico.'); return; }
const t=tarefaAtual(); if(!t) return;
if(!confirm('Remover esta linha do histórico?')) return;
t.historico.splice(i,1); salvar(); renderHistorico(t);
}
function limparHist(){
if(!ehAdmin()){ toast('Somente a Administração altera o histórico.'); return; }
const t=tarefaAtual(); if(!t) return;
if(!confirm('Apagar todo o histórico deste registro?')) return;
t.historico=[]; salvar(); renderHistorico(t);
}
function montarTransferencia(t){
const box=$('t-transf');
if(!t || !podeEditar(t)){ box.innerHTML=''; return; }
const atuais = nomesResp(t);
box.innerHTML = '
Transferir responsabilidade hoje com '+
(atuais.length? esc(atuais.join(', ')) : 'ninguém')+'
'+
'
'+
campo('','Transferir para',false,'')+
campo('','Motivo',false,
'','t-transf-motivo',
'A troca de responsável vai para o histórico com ou sem motivo — o texto ajuda quem for consultar depois.')+
'
'+
''+
''+
'Transferir substitui o responsável atual; incluir mantém quem já estava. Tudo fica registrado nos comentários.'+
'
'+
'
';
}
function transferir(somar){
const t = S.tarefas.find(x=>x.id===(UI.tarefaEdit&&UI.tarefaEdit.id));
if(!t){ toast('Salve o registro antes de transferir.'); return; }
if(!podeEditar(t)){ toast('Você não pode alterar este registro.'); return; }
const sel=$('t-transf-para'), id=+sel.value;
if(!id){ sel.parentNode.classList.add('err'); toast('Escolha para quem vai a responsabilidade.'); return; }
sel.parentNode.classList.remove('err');
const motivo=($('t-transf-motivo').value||'').trim();
const fMot=document.querySelector('[data-for="t-transf-motivo"]');
if(fMot) fMot.classList.remove('err');
const antes = nomesResp(t);
const novo = membro(id);
t.responsaveis = somar ? (t.responsaveis||[]).concat([id]) : [id];
t.alteradoEm=agoraISO(); t.alteradoPor=eu().nome;
hist(t, (somar?'Incluiu ':'Transferiu para ')+novo.nome+
(somar?' como corresponsável':(antes.length?' (antes: '+antes.join(', ')+')':''))+
(motivo?'. Motivo: '+motivo:'')+'.');
salvar();
/* atualiza a tela do próprio formulário */
if(MS['t-resp']){ MS['t-resp'].sel=(t.responsaveis||[]).slice(); msChips('t-resp'); }
$('t-alt').value = (t.alteradoPor||'—')+' · '+fmtDH(t.alteradoEm);
$('t-transf-motivo').value='';
montarTransferencia(t); renderComentarios(t); renderHistorico(t); redesenhar();
toast(somar? novo.nome+' passou a ser corresponsável.' : 'Registro transferido para '+novo.nome+'.');
}
/* ===== ponte com o cadastro "Advogados parceiros e associados" =====
O módulo financeiro guarda a lista em geral.parceiros, dentro da chave
gf_financeiro_v2. Lemos do armazenamento da própria plataforma e, se
necessário, do documento do módulo — os dois compartilham a mesma origem. */
const FIN_KEY = 'gf_financeiro_v2';
function finLerEstado(){
let raw=null;
try{ raw = localStorage.getItem(FIN_KEY); }catch(e){}
if(!raw){ try{ const w=$('fin-frame').contentWindow; raw = w && w.localStorage && w.localStorage.getItem(FIN_KEY); }catch(e){} }
if(!raw) return null;
try{ const g=JSON.parse(raw); return (g && typeof g==='object') ? g : null; }catch(e){ return null; }
}
function finGravarEstado(g){
const txt = JSON.stringify(g); let ok=false;
try{ localStorage.setItem(FIN_KEY, txt); ok=true; }catch(e){}
try{ const w=$('fin-frame').contentWindow; if(w && w.localStorage){ w.localStorage.setItem(FIN_KEY, txt); ok=true; } }catch(e){}
return ok;
}
function finListaParceiros(g){
g = g || finLerEstado(); if(!g) return null;
const arr = (g.geral && g.geral.parceiros) || g.parceiros;
return Array.isArray(arr) ? arr : [];
}
function finCadastro(tipo){
const arr = finListaParceiros();
if(arr===null) return null;
return arr.filter(x=>x && x.tipo===tipo)
.sort((a,b)=>String(a.nome||'').localeCompare(String(b.nome||''),'pt-BR'));
}
/* ===== espelho dos associados: equipe <-> Gestão Financeira ===== */
const FUNCAO_ASSOCIADO = 'Advogado(a) associado(a)';
const chaveNome = n => String(n||'').trim().toLowerCase().replace(/\s+/g,' ');
function sincronizarAssociados(silencioso){
const g = finLerEstado();
if(!g) return {lidos:0, enviados:0, importados:0};
g.geral = g.geral || {};
if(!Array.isArray(g.geral.parceiros)) g.geral.parceiros = Array.isArray(g.parceiros)? g.parceiros : [];
const lista = g.geral.parceiros;
let enviados=0, importados=0;
/* equipe -> financeiro */
const proxId = () => Math.max(0, ...lista.map(x=>+x.id||0)) + 1;
S.membros.filter(m=>m.funcao===FUNCAO_ASSOCIADO && m.status!=='inativo').forEach(m=>{
const jaTem = lista.some(x=>chaveNome(x.nome)===chaveNome(m.nome));
if(!jaTem){
lista.push({id:proxId(), nome:m.nome, tipo:'ASSOCIADO', area:'Outra',
cpf:m.cpf||'', tel:m.tel||'', pix:m.pix||'', obs:'Cadastrado pela equipe da plataforma.'});
enviados++;
}
});
/* financeiro -> equipe */
lista.filter(x=>x && x.tipo==='ASSOCIADO' && x.nome).forEach(x=>{
const jaTem = S.membros.some(m=>chaveNome(m.nome)===chaveNome(x.nome));
if(!jaTem){
S.membros.push({id:novoId('membro'), nome:x.nome, funcao:FUNCAO_ASSOCIADO,
email:'', tel:x.tel||'', cpf:x.cpf||'', pix:x.pix||'', oab:'', uf:'', email2:'', tel2:'', foto:'',
status:'pendente', escopo:'meu', perms:Object.assign({}, FUNCOES[FUNCAO_ASSOCIADO].perms),
agendaVisivel:'detalhes', agendaParticular:false, viaFinanceiro:true, criadoEm:agoraISO()});
importados++;
}
});
if(enviados || importados){
finGravarEstado(g); salvar();
if(FIN_PRONTO && UI.painel!=='financeiro') montarFinanceiro(true);
if(!silencioso){
const partes=[];
if(enviados) partes.push(enviados+' associado(s) enviado(s) para a Gestão Financeira');
if(importados) partes.push(importados+' associado(s) trazido(s) para a equipe');
toast(partes.join(' · ')+'.');
}
}
return {lidos:lista.length, enviados:enviados, importados:importados};
}
function selParceiro(id,tipo,val,vazio){
const lista = finCadastro(tipo);
const atual = val && typeof val==='object' ? val : (val?{id:null,nome:String(val)}:null);
if(lista===null)
return '';
if(!lista.length)
return '';
const jaTem = atual && lista.some(x=>x.nome===atual.nome);
return '';
}
function valorParceiro(id){
const el=$(id); if(!el || !el.value) return null;
const op = el.options[el.selectedIndex];
return {id: op && op.dataset.fid ? +op.dataset.fid : null, nome: el.value};
}
function nomeParceiro(v){ return !v ? '' : (typeof v==='object' ? (v.nome||'') : String(v)); }
function selMembro(id,val,vazio){
return '';
}
/* ================= seleção com busca por digitação ================= */
const MS = {};
function msHTML(id, ph, extra){
return '
'+
'
'+
''+
''+
'
'+
''+
(extra||'')+'
';
}
function msInit(id, itens, sel, opts){ MS[id]={itens:itens, sel:(sel||[]).slice(), unico:!!(opts&&opts.unico)}; msChips(id); }
/* devolve o texto do item escolhido em campos de seleção única */
function msValor(id){ const m=MS[id]; if(!m||!m.sel.length) return '';
const it=m.itens.find(x=>x.id===m.sel[0]); return it?it.nome:''; }
function msDefinir(id, nome){
const m=MS[id]; if(!m) return;
if(!nome){ m.sel=[]; msChips(id); return; }
let it=m.itens.find(x=>x.nome===nome);
if(!it){ it={id: (Math.max(0,...m.itens.map(x=>x.id))+1), nome:nome, sub:'fora da lista padrão'}; m.itens.push(it); }
m.sel=[it.id]; msChips(id);
}
function msSel(id){ return (MS[id]&&MS[id].sel)||[]; }
function msFoco(id){ const i=$(id+'-inp'); if(i && !i.disabled) i.focus(); }
function msFechar(id){ setTimeout(()=>{ const d=$(id+'-drop'); if(d) d.classList.remove('on'); }, 160); }
function msChips(id){
const m=MS[id]; if(!m) return;
const el=$(id+'-chips'); if(!el) return;
el.innerHTML = m.sel.map(v=>{
const it=m.itens.find(x=>x.id===v);
return ''+esc(it?it.nome:'—')+''+
'';
}).join('');
const inp=$(id+'-inp');
if(inp) inp.placeholder = m.sel.length ? 'Adicionar mais…' : inp.dataset.ph || inp.placeholder;
}
function msBuscar(id){
const m=MS[id]; if(!m) return;
const inp=$(id+'-inp'), d=$(id+'-drop');
const q=(inp.value||'').trim().toLowerCase();
/* em campo de escolha única, focar sem digitar já mostra a lista inteira */
if(!q && m.unico && !m.sel.length){
d.innerHTML = m.itens.slice(0,12).map((x,i)=>'').join('');
d.classList.add('on'); return;
}
if(!q){ d.classList.remove('on'); d.innerHTML=''; return; }
const achados = m.itens.filter(x=> m.sel.indexOf(x.id)<0 &&
((x.nome||'').toLowerCase().indexOf(q)>=0 || (x.busca||'').toLowerCase().indexOf(q)>=0)).slice(0,10);
d.innerHTML = achados.length
? achados.map((x,i)=>'').join('')
: '
Nada encontrado para “'+esc(inp.value)+'”.
';
d.classList.add('on');
/* garante que a lista caiba na área visível do formulário */
const cx = d.closest('.m-bd');
if(cx){ const rd=d.getBoundingClientRect(), rc=cx.getBoundingClientRect();
if(rd.bottom > rc.bottom - 6) cx.scrollTop += (rd.bottom - rc.bottom + 14); }
}
function msAdd(id,v){
const m=MS[id]; if(!m || m.sel.indexOf(v)>=0) return;
if(m.unico) m.sel=[v]; else m.sel.push(v);
$(id+'-inp').value=''; msChips(id);
if(m.unico){ const d=$(id+'-drop'); if(d){ d.classList.remove('on'); d.innerHTML=''; } } else msBuscar(id);
if(m.onChange) m.onChange();
const f=document.querySelector('[data-for="'+id+'"]'); if(f) f.classList.remove('err');
$(id+'-inp').focus();
}
function msRemover(id,v){
const m=MS[id]; if(!m) return;
m.sel = m.sel.filter(x=>x!==v); msChips(id);
if(m.onChange) m.onChange();
}
function msTecla(e,id){
const m=MS[id]; if(!m) return;
if(e.key==='Enter'){ e.preventDefault();
const b=$(id+'-drop').querySelector('button'); if(b) b.click(); return; }
if(e.key==='Escape'){ $(id+'-drop').classList.remove('on'); return; }
if(e.key==='Backspace' && !e.target.value && m.sel.length){ msRemover(id, m.sel[m.sel.length-1]); }
}
function msTravar(id,trava){
const c=$(id); if(!c) return;
c.classList.toggle('trava',!!trava);
const i=$(id+'-inp'); if(i){ i.disabled=!!trava; if(trava) i.value=''; }
const d=$(id+'-drop'); if(d) d.classList.remove('on');
}
/* ---- "não relacionado a cliente" ---- */
function toggleSemCliente(){
UI.semCliente = !UI.semCliente;
if(UI.semCliente){ MS['t-cli'].sel=[]; msChips('t-cli'); }
aplicarSemCliente(); syncAreas();
}
function aplicarSemCliente(){
const b=$('t-semcli'); if(b){ b.classList.toggle('on',!!UI.semCliente);
b.innerHTML = (UI.semCliente?'✓ ':'') + 'Não relacionado a cliente'; }
msTravar('t-cli', !!UI.semCliente);
const f=document.querySelector('[data-for="t-cli"]'); if(f && UI.semCliente) f.classList.remove('err');
}
/* O motivo só aparece quando a data de um registro já salvo muda. */
function dataMudou(){
const w = document.querySelector('[data-for="t-data-motivo"]');
if(!w) return;
const orig = (UI.tarefaEdit && UI.tarefaEdit.dataOrig) || '';
const mudou = !!orig && $('t-data').value !== orig;
w.style.display = mudou ? '' : 'none';
if(!mudou && $('t-data-motivo')) $('t-data-motivo').value = '';
}
function campo(cls,label,req,inner,id,hint){
return '
'+
inner + (hint?'
'+hint+'
':'') + '
Campo obrigatório.
';
}
/* Alguns status pedem justificativa por escrito. */
function statusMudou(){
const st = $('t-status') ? $('t-status').value : '';
/* o campo assume a cor do status escolhido */
const sel=$('t-status'), bol=$('t-stbolha');
if(sel){ sel.className = 'stsel ' + (STATUS_CLS[st]||'st-fazer'); }
if(bol){ bol.style.background = STATUS_DOT[st]||'#6b7280'; }
const exige = STATUS_EXIGE_NOTA.indexOf(st)>=0;
const req=$('t-cmt-req'), nota=$('t-cmt-nota');
if(req) req.style.display = exige?'':'none';
if(nota) nota.style.display = exige?'':'none';
if(!exige){ const f=document.querySelector('[data-for="t-cmt-novo"]'); if(f) f.classList.remove('err'); }
}
function syncAreas(){
const ids=msSel('t-cli');
const a=areasDosClientes(ids);
const el=$('t-area'); if(el) el.value = a.length? a.join(' · ') : '';
const pe=$('t-proc'); if(!pe) return;
const auto = ids.map(id=>{const c=cliente(id); return c&&c.processo?c.processo:'';}).filter(Boolean).join(' · ');
/* só sobrescreve enquanto a pessoa não digitou um número próprio */
if(!UI.procManual || !pe.value.trim() || pe.value===UI.procAuto) pe.value = auto;
UI.procAuto = auto;
pe.placeholder = auto? '' : 'Nenhum processo no cadastro do cliente';
}
function coletar(){
const tipo = UI.tarefaEdit.tipo;
const o = {
tipo:tipo,
descricao: ($('t-desc').value||'').trim(),
clientes: msSel('t-cli'),
semCliente: !!UI.semCliente,
processo: ($('t-proc').value||'').trim(),
data: $('t-data').value||'',
hora: $('t-hora')?($('t-hora').value||''):'',
status: $('t-status').value,
acao: msValor('t-acao'),
responsaveis: msSel('t-resp'),
link: ($('t-link').value||'').trim(),
arquivos: UI.arquivosTmp.slice(),
prioridade: UI.prioTmp||'Média',
checklist: JSON.parse(JSON.stringify(UI.checkTmp||{ativo:false,itens:[]}))
};
o.areas = areasDosClientes(o.clientes);
if(tipo==='financeiro') o.fin = {tipo:$('t-fin-tipo').value, nf:$('t-fin-nf').value,
parceiro:valorParceiro('t-fin-parc'), associado:valorParceiro('t-fin-assoc')};
return o;
}
function marcarErro(id,ok){ const f=document.querySelector('[data-for="'+id+'"]'); if(f) f.classList.toggle('err',!ok); }
function validar(o){
const e=[];
marcarErro('t-desc', !!o.descricao); if(!o.descricao) e.push('Descrição');
const cliOk = o.clientes.length>0 || o.semCliente;
marcarErro('t-cli', cliOk); if(!cliOk) e.push('Cliente (ou marque "Não relacionado a cliente")');
marcarErro('t-data', !!o.data); if(!o.data) e.push(TIPOS[o.tipo].dataLabel);
marcarErro('t-status', !!o.status); if(!o.status) e.push('Status');
marcarErro('t-acao', !!o.acao); if(!o.acao) e.push('Ação a executar');
marcarErro('t-resp', o.responsaveis.length>0); if(!o.responsaveis.length) e.push('Responsável');
if(o.tipo==='financeiro'){
marcarErro('t-fin-tipo', !!o.fin.tipo); if(!o.fin.tipo) e.push('Tipo');
marcarErro('t-fin-nf', !!o.fin.nf); if(!o.fin.nf) e.push('Emitir NF');
}
if(o.link && !/^https?:\/\//i.test(o.link)) e.push('Link (precisa começar com http:// ou https://)');
return e;
}
function salvarTarefa(){
const o = coletar();
const err = validar(o);
const anterior = UI.tarefaEdit.novo ? null : (S.tarefas.find(x=>x.id===UI.tarefaEdit.id)||{}).status;
const nota = ($('t-cmt-novo').value||'').trim();
const exigeNota = STATUS_EXIGE_NOTA.indexOf(o.status)>=0 && o.status!==anterior;
const fNota = document.querySelector('[data-for="t-cmt-novo"]');
if(fNota) fNota.classList.toggle('err', exigeNota && !nota);
if(exigeNota && !nota) err.push('Comentário (obrigatório em "'+o.status+'")');
if(err.length){ const b=$('t-err'); b.innerHTML='Revise: '+err.map(esc).join(', ')+'.';
b.classList.remove('aviso'); b.classList.add('on');
$('ov-tarefa').scrollTop=0; return; }
$('t-err').classList.remove('on','aviso');
if(UI.tarefaEdit.novo){
const t = Object.assign({id:novoId('tarefa')}, o, {
criadoPor: eu().nome, criadoPorId: eu().id, criadoEm: agoraISO(),
alteradoEm:null, alteradoPor:null, comentarios:[], historico:[],
concluidoEm: o.status===CONCLUIDA?agoraISO():null});
hist(t, 'Registro criado como '+TIPOS[t.tipo].kind.toLowerCase()+', prioridade '+t.prioridade.toLowerCase()+'.');
if(nota) t.comentarios.push({autor:eu().nome, em:agoraISO(), texto:nota});
S.tarefas.push(t);
S.rascunhos = (S.rascunhos||[]).filter(r=>!(r.tipo===t.tipo && r.autorId===eu().id));
salvar(); renderRascunhos();
const painel = TIPOS[t.tipo].painel;
UI.tarefaEdit = null;
fechar('ov-tarefa');
const naAgenda = UI.painel==='agenda';
if(naAgenda){ if(t.data) agEstado().ref = t.data; }
else {
const alvo = PAINEIS.find(x=>x.k===painel);
if(painel!==UI.painel && (!alvo.perm || pode(alvo.perm))) UI.painel=painel;
if(PAINEIS_KANBAN[painel]){
const pp = panEstado(painel);
if(PAINEIS_KANBAN[painel].todos) pp.tipo = t.tipo;
pp.vista = t.status===CONCLUIDA?'concluido':'aberto';
const sv=$(painel+'-seg-vista');
if(sv) [...sv.children].forEach((b,i)=>b.classList.toggle('on',(i===0)===(pp.vista==='aberto')));
}
}
redesenhar();
if(t.tipo==='financeiro' && t.fin && FIN_LANCAVEIS.indexOf(t.fin.tipo)>=0){
hist(t,'Alerta de lançamento criado na Gestão Financeira ('+t.fin.tipo+').');
salvar(); redesenhar();
toast('Alerta criado na Gestão Financeira: '+t.fin.tipo+' aguardando lançamento.');
} else toast(TIPOS[t.tipo].kind+' criado em '+PAINEIS.find(x=>x.k===painel).nome+
(naAgenda?' e lançado na agenda de '+fmtD(t.data)+'.':'.'));
} else {
const t = S.tarefas.find(x=>x.id===UI.tarefaEdit.id);
const antes = t.status;
/* salvar com mudança de status, de data, de responsável ou com comentário
conta como encaminhamento do que vence hoje */
const encaminhou = antes!==o.status || String(t.data||'')!==String(o.data||'') || !!nota ||
(t.responsaveis||[]).join(',')!==(o.responsaveis||[]).join(',');
diffHistorico(t,o);
Object.assign(t,o);
if(encaminhou) marcarTratado(t);
if(antes!==CONCLUIDA && o.status===CONCLUIDA) t.concluidoEm=agoraISO();
if(o.status!==CONCLUIDA) t.concluidoEm=null;
if(nota){ t.comentarios=t.comentarios||[]; t.comentarios.push({autor:eu().nome, em:agoraISO(), texto:nota}); }
t.alteradoEm=agoraISO(); t.alteradoPor=eu().nome;
salvar(); fechar('ov-tarefa');
if(UI.painel==='agenda' && t.data && t.status!==CONCLUIDA) agEstado().ref = t.data;
redesenhar();
toast(t.status===CONCLUIDA ? 'Registro concluído e retirado da agenda.' : 'Registro atualizado.');
}
}
function nomeLista(ids,fn){ return (ids||[]).map(fn).filter(Boolean).join(', ') || '—'; }
function diffHistorico(t,o){
const reg=[];
const cmp=(rot,a,b)=>{ if(String(a==null?'':a)!==String(b==null?'':b))
reg.push(rot+': "'+(a||'—')+'" → "'+(b||'—')+'"'); };
cmp('Descrição', t.descricao, o.descricao);
cmp('Status', t.status, o.status);
cmp('Prioridade', t.prioridade||'Média', o.prioridade);
cmp('Ação a executar', t.acao, o.acao);
const mudouData = String(t.data||'')!==String(o.data||'');
cmp(TIPOS[o.tipo].dataLabel, t.data?fmtD(t.data):'', o.data?fmtD(o.data):'');
if(mudouData){
const mot = $('t-data-motivo') ? ($('t-data-motivo').value||'').trim() : '';
reg.push('Motivo da nova data: '+(mot||'não informado'));
}
cmp('Horário', t.hora, o.hora);
cmp('Processo', t.processo, o.processo);
cmp('Link', t.link, o.link);
cmp('Clientes', nomeLista(t.clientes,id=>{const c=cliente(id);return c&&c.nome;}),
nomeLista(o.clientes,id=>{const c=cliente(id);return c&&c.nome;}));
const antesR = nomeLista(t.responsaveis,id=>{const m=membro(id);return m&&m.nome;});
const depoisR = nomeLista(o.responsaveis,id=>{const m=membro(id);return m&&m.nome;});
cmp('Responsáveis', antesR, depoisR);
if(String(antesR||'')!==String(depoisR||'')){
const mt = $('t-transf-motivo') ? ($('t-transf-motivo').value||'').trim() : '';
reg.push('Motivo da troca de responsável: '+(mt||'não informado'));
}
const na=(t.arquivos||[]).length, nb=(o.arquivos||[]).length;
if(na!==nb) reg.push('Anexos: '+na+' → '+nb);
if(o.fin){ const f=t.fin||{};
cmp('Tipo financeiro', f.tipo, o.fin.tipo);
cmp('Emitir NF', f.nf, o.fin.nf);
cmp('Advogado parceiro', nomeParceiro(f.parceiro), nomeParceiro(o.fin.parceiro));
cmp('Associado envolvido', nomeParceiro(f.associado), nomeParceiro(o.fin.associado));
}
if(reg.length) hist(t, reg.join(' · '));
}
/* ================= rascunhos de registro novo ================= */
function capturarRascunho(){
if(!UI.tarefaEdit || !UI.tarefaEdit.novo) return;
if(!$('t-desc')) return;
let o; try{ o = coletar(); }catch(e){ return; }
const temConteudo = o.descricao || o.clientes.length || o.semCliente || o.data || o.acao || o.responsaveis.length ||
o.link || (o.arquivos||[]).length || (o.checklist&&o.checklist.itens.length);
if(!temConteudo) return;
S.rascunhos = (S.rascunhos||[]).filter(r=>r.tipo!==o.tipo);
S.rascunhos.push({tipo:o.tipo, dados:o, em:agoraISO(), autorId:eu().id, autor:eu().nome});
salvar(); renderRascunhos();
toast('Rascunho guardado. Você pode recuperá-lo no canto da tela.');
}
function meusRascunhos(){ return (S.rascunhos||[]).filter(r=>r.autorId===eu().id); }
function renderRascunhos(){
const rs = meusRascunhos();
$('rasc').innerHTML = rs.map(r=>
'
Rascunho não salvo · '+esc(TIPOS[r.tipo].kind)+'
'+
'
'+esc(r.dados.descricao || '(sem descrição)')+'
'+
'
Guardado em '+fmtDH(r.em)+'
'+
'
'+
'
').join('');
}
function descartarRascunho(tipo,avisar){
if(avisar && !confirm('Descartar este rascunho? Não há como recuperar depois.')) return;
S.rascunhos = (S.rascunhos||[]).filter(r=>!(r.tipo===tipo && r.autorId===eu().id));
salvar(); renderRascunhos();
if(avisar) toast('Rascunho descartado.');
}
function recuperarRascunho(tipo){
const r = (S.rascunhos||[]).find(x=>x.tipo===tipo && x.autorId===eu().id);
if(!r) return;
novo(tipo, r.dados);
}
function preencherForm(d){
if(!d) return;
$('t-desc').value = d.descricao||'';
if(MS['t-cli']){ MS['t-cli'].sel=(d.clientes||[]).slice(); msChips('t-cli'); }
if(MS['t-resp']){ MS['t-resp'].sel=(d.responsaveis||[]).slice(); msChips('t-resp'); }
UI.semCliente = !!d.semCliente; aplicarSemCliente();
if($('t-data')){ $('t-data').value = d.data||''; dataMudou(); }
if($('t-hora')) $('t-hora').value = d.hora||'';
if($('t-status')) $('t-status').value = d.status||'A fazer';
msDefinir('t-acao', d.acao||'');
if($('t-proc')) { $('t-proc').value = d.processo||''; UI.procManual = !!d.processo; }
$('t-link').value = d.link||'';
UI.arquivosTmp = (d.arquivos||[]).slice(); renderArquivos();
UI.prioTmp = d.prioridade||'Média'; renderPrio();
UI.checkTmp = d.checklist ? JSON.parse(JSON.stringify(d.checklist)) : {ativo:false,itens:[]}; renderChecklist();
if(d.fin){ if($('t-fin-tipo')) $('t-fin-tipo').value=d.fin.tipo||'';
if($('t-fin-nf')) $('t-fin-nf').value=d.fin.nf||'';
if($('t-fin-parc')) $('t-fin-parc').value=nomeParceiro(d.fin.parceiro);
if($('t-fin-assoc')) $('t-fin-assoc').value=nomeParceiro(d.fin.associado); }
syncAreas();
}
function cancelarTarefa(){
if(UI.tarefaEdit && UI.tarefaEdit.novo){
let o=null; try{ o=coletar(); }catch(e){}
const tem = o && (o.descricao||o.clientes.length||o.data||o.acao||o.responsaveis.length);
if(tem && !confirm('Descartar o que você preencheu? Se preferir guardar, feche a janela clicando fora dela — aí fica um rascunho.')) return;
}
UI.tarefaEdit=null; fechar('ov-tarefa');
}
function excluirTarefa(){
const id=UI.tarefaEdit.id; if(!id) return;
const t=S.tarefas.find(x=>x.id===id); if(!t) return;
if(!podeExcluir(t)){
toast(t.status===CONCLUIDA
? 'Registros concluídos ficam no histórico e só a Administração pode excluí-los.'
: 'Com o acesso "Meu acesso" você só pode excluir registros criados por você.');
return; }
if(!confirm('Excluir este registro definitivamente?')) return;
S.tarefas = S.tarefas.filter(t=>t.id!==id);
salvar(); fechar('ov-tarefa'); redesenhar(); toast('Registro excluído.');
}
function travarForm(ro){
document.querySelectorAll('#ov-tarefa .m-bd input,#ov-tarefa .m-bd select,#ov-tarefa .m-bd textarea')
.forEach(el=>{ if(el.id==='t-cmt-novo') return;
if(el.id==='t-criado'||el.id==='t-alt'||el.id==='t-area') return;
el.disabled = ro; });
$('t-drop').style.display = ro?'none':'';
document.querySelectorAll('#t-cli .cx,#t-resp .cx,#t-acao .cx,#t-semcli').forEach(el=>{ el.disabled=ro; el.style.display=ro?'none':''; });
if(ro){ msTravar('t-cli',true); msTravar('t-resp',true); msTravar('t-acao',true); }
else { msTravar('t-cli', !!UI.semCliente); msTravar('t-resp',false); msTravar('t-acao',false); }
document.querySelectorAll('#t-prio .pb,#t-check button,#t-check input').forEach(el=>{ el.disabled=ro;
if(ro) el.style.pointerEvents='none'; else el.style.pointerEvents=''; });
const _t = UI.tarefaEdit.novo?null:S.tarefas.find(x=>x.id===UI.tarefaEdit.id);
$('t-del').style.display = (!ro && _t && podeExcluir(_t))?'':'none';
document.querySelectorAll('#t-files .del').forEach(b=>b.style.display = ro?'none':'');
}
/* ---- anexos ---- */
function renderArquivos(){
$('t-files').innerHTML = UI.arquivosTmp.map((a,i)=>
'
Integrantes do escritório '+lista.length+' cadastrado(s)
'+
(gerir? '
Ao cadastrar OAB e UF, a plataforma passa a poder buscar processos vinculados àquele registro quando a integração com os tribunais estiver ligada.
':
'
Seu perfil enxerga apenas o próprio cadastro. Para ver ou alterar a equipe, fale com a Administração.
')+
(linhas||'
Nenhum integrante cadastrado.
')+
(gerir?'
'+
'
'+
'
Quem tem a função '+esc(FUNCAO_ASSOCIADO)+' aparece automaticamente no cadastro '+
'Advogados parceiros e associados da Gestão Financeira — e o contrário também vale.
':'')+
(gerir? '
Acesso desta instalação
'+
'
Cada pessoa da equipe abre a plataforma no próprio computador, e o que ela enxerga vem das atribuições definidas aqui. '+
'A Administração indica, uma vez, a qual integrante esta instalação pertence — não há alternância de usuário dentro da plataforma. '+
'Quando a plataforma passar a rodar em servidor, esse vínculo vira o login de cada um.
Quem vê a minha agenda vale para os seus compromissos
'+
'
Os outros integrantes sempre enxergam que você está ocupado em determinado horário — isso é o que permite marcar reunião sem conflito. '+
'O que você decide aqui é se eles veem do que se trata.
'+
'
'+
'
'+
'
Marcar novos compromissos como particulares
'+
'
Deixa a caixa “Particular” já marcada ao criar. Você pode desmarcar caso a caso.
'+
'
'+
'
Registros de trabalho — prazos, audiências, perícias, atendimentos — seguem as permissões de cada perfil e '+
'não são afetados por esta escolha: eles são do escritório, não seus.
'+
'
Resumo
'+
'
'+
'
Meus compromissos
'+meus.length+'
'+
'
Particulares
'+meus.filter(x=>x.particular).length+'
'+
'
Convites recebidos
'+parts.length+'
'+
'
';
}
function setAgendaVisivel(v){ const u=eu(); u.agendaVisivel=v; salvar(); renderCfg(); renderPainel();
toast(v==='ocupado'?'A equipe passará a ver apenas “Ocupado” nos seus compromissos.':'A equipe passa a ver os detalhes dos seus compromissos.'); }
function setAgendaParticular(v){ const u=eu(); u.agendaParticular=!!v; salvar(); renderCfg(); }
function cfgNotif(){
const c = notifCfg(), u = eu();
const sw=(t,sub,on,acao,extra)=>'
'+t+'
'+
'
'+sub+'
'+(extra||'')+'
'+
'
';
return '
Como quero ser avisado vale só para você
'+
'
Dentro da plataforma, todo mundo é avisado — sempre. O sino no topo e os avisos '+
'em pop-up no canto da tela não podem ser desligados por ninguém: é o que garante que nenhum prazo passe '+
'despercebido. As preferências abaixo valem para os avisos externos (e-mail, push e WhatsApp), que '+
'entram em operação quando a plataforma virar aplicativo e ganhar endereço na web.
'+
'
'+ NOTIF_CANAIS.map(x=>
'
'+x.n+
(x.fixo?' sempre ativo':(x.pronto?' ativo':' em breve'))+'
'+
'
'+x.d+'
'+
(x.fixo ? '✓ ligado'
: '')+
'
').join('')+
'
'+
(c.canais.email && !(u.email||'').trim() ? '
Cadastre um e-mail no seu registro da equipe para receber por e-mail.
Cadastre um telefone no seu registro da equipe para receber por WhatsApp.
':'')+
'
Sobre o que quero ser avisado apenas nos canais externos
'+
'
Desligar um item aqui não tira o aviso de dentro da plataforma — só evita que ele vire '+
'e-mail, push ou WhatsApp quando esses canais estiverem no ar.
'+
sw('Resumo diário', 'Um único aviso pela manhã com tudo do dia', c.resumo.ativo, 'setNotifResumo()',
'')+
sw('Não perturbe', 'Segura os avisos durante a noite e devolve no horário de retomada', c.silencio.ativo, 'setNotifSilencio()',
'
'+
''+
'
')+
'
Agora
'+
'
Você tem '+gerarNotificacoes().length+' notificação(ões) no sino neste momento.'+
'
';
}
function setNotifCanal(k){
if(k==='app'){ toast('Os avisos dentro da plataforma são obrigatórios para todos os perfis.'); return; }
const c=notifCfg(); c.canais[k]=!c.canais[k]; salvar(); renderCfg(); renderSino();
toast((c.canais[k]?'Ativado: ':'Desativado: ')+NOTIF_CANAIS.find(x=>x.k===k).n+'.'); }
function setNotifEvento(k){ const c=notifCfg(); c.eventos[k]=!c.eventos[k]; salvar(); renderCfg(); renderSino(); }
function setNotifAntec(v){ const c=notifCfg(); c.antecedencia=+v; salvar(); renderCfg(); renderSino(); }
function setNotifResumo(){ const c=notifCfg(); c.resumo.ativo=!c.resumo.ativo; salvar(); renderCfg(); }
function setNotifResumoHora(v){ const c=notifCfg(); c.resumo.hora=v; salvar(); }
function setNotifSilencio(){ const c=notifCfg(); c.silencio.ativo=!c.silencio.ativo; salvar(); renderCfg(); }
function setNotifSil(qual,v){ const c=notifCfg(); c.silencio[qual]=v; salvar(); }
function cfgAparencia(){
const z=S.cfg.zoom;
return '
Tema
'+
'
'+
['light','dark'].map(t=>'').join('')+'
'+
'
Tamanho da fonte '+z+'%
'+
'
'+
[90,100,110,125,150].map(v=>'').join('')+'
'+
'
A escala vale para toda a plataforma e fica gravada neste navegador.
'+
'
Menu lateral
'+
'
'+
''+
''+
'
'+
'
Logo do escritório
'+
'
A logo dourada já vem embutida. Troque aqui se quiser outra versão: o símbolo aparece com o menu recolhido e a logo completa com o menu expandido. PNG com fundo transparente fica melhor sobre o azul.
'+
'
'+
'
'+
'
'+(S.cfg.logoIcone?'':LOGO_ICONE)+'
'+
'
'+
(S.cfg.logoIcone?'':'')+'
'+
'
'+
'
'+
'
'+(S.cfg.logoFull?'':LOGO_FULL)+'
'+
'
'+
(S.cfg.logoFull?'':'')+'
'+
'
'+
'
';
}
function cfgIntegr(){
const i=S.cfg.integr, c=S.cfg.integrCfg;
const sw=(k,t,s,extra)=>'
'+t+'
'+s+'
'+(extra||'')+'
'+
'
';
return '
Integrações
'+
sw('agenda','Google Agenda','Lança prazos, audiências e perícias na agenda do responsável.',
'')+
sw('meet','Google Meet','Cria o link da sala ao registrar atendimento ou audiência online.',
'')+
sw('slack','Slack','Avisa o canal quando um prazo entra em risco ou é concluído.',
'')+
'
As chaves de conexão serão pedidas na primeira sincronização. Por enquanto ficam registradas as preferências — nenhum dado sai da plataforma.
';
}
function cfgDados(){
return '
Dados da plataforma
'+
'
Tudo fica gravado neste navegador. Exporte com frequência para não depender de um único computador.
'+
'
'+
''+
''+
''+
''+
'
'+
'
Resumo
'+
'
'+
'
Integrantes
'+S.membros.length+'
'+
'
Clientes
'+S.clientes.length+'
'+
'
Registros
'+S.tarefas.length+'
'+
'
'+
cfgLimpeza()+
'
Zona de risco
'+
'';
}
function carregarLogo(e,campo,max){
const f=e.target.files[0]; e.target.value=''; if(!f) return;
if(f.size>3*1024*1024){ toast('Arquivo acima de 3 MB. Escolha um menor.'); return; }
const r=new FileReader();
r.onload=()=>{ redimensionarPng(r.result,max,d=>{ S.cfg[campo]=d; salvar(); renderLogo(); renderCfg(); toast('Logo atualizada.'); }); };
r.readAsDataURL(f);
}
function redimensionarPng(dataUrl,max,cb){
const img=new Image();
img.onload=()=>{ const s=Math.min(1,max/Math.max(img.width,img.height));
const c=document.createElement('canvas'); c.width=Math.round(img.width*s); c.height=Math.round(img.height*s);
c.getContext('2d').drawImage(img,0,0,c.width,c.height); cb(c.toDataURL('image/png')); };
img.onerror=()=>cb(dataUrl); img.src=dataUrl;
}
function toggleIntegr(k){ S.cfg.integr[k]=!S.cfg.integr[k]; salvar(); renderCfg();
toast((S.cfg.integr[k]?'Integração ativada: ':'Integração desativada: ')+({agenda:'Google Agenda',meet:'Google Meet',slack:'Slack'})[k]); }
function setTema(t){ S.cfg.tema=t; salvar(); aplicarTema(); renderCfg(); }
function ciclarTema(){ setTema(S.cfg.tema==='dark'?'light':'dark'); }
function aplicarTema(){ document.body.classList.toggle('dark', S.cfg.tema==='dark');
if(typeof sincronizarTemaFin==='function') setTimeout(sincronizarTemaFin,0);
$('btn-tema').textContent = S.cfg.tema==='dark'?'☀':'🌙';
$('btn-tema').title = S.cfg.tema==='dark'?'Voltar ao modo claro':'Alternar para o modo escuro'; }
function setZoom(v){ S.cfg.zoom=v; salvar(); aplicarZoom(); renderCfg(); }
function aplicarZoom(){ document.documentElement.style.zoom = (S.cfg.zoom/100); }
/* ---- backup ---- */
function baixar(nome,conteudo,tipo){
const b=new Blob([conteudo],{type:tipo}); const u=URL.createObjectURL(b);
const a=document.createElement('a'); a.href=u; a.download=nome; a.click(); setTimeout(()=>URL.revokeObjectURL(u),2000);
}
function exportarJSON(){ baixar('gestao-escritorio-'+new Date().toISOString().slice(0,10)+'.json', JSON.stringify(S,null,1),'application/json'); toast('Backup gerado.'); }
function importarJSON(e){
const f=e.target.files[0]; if(!f) return;
const r=new FileReader();
r.onload=()=>{ try{ const s=JSON.parse(r.result); if(!s.membros) throw 0;
if(!confirm('Substituir todos os dados atuais pelo backup?')) return;
S=migrar(s); salvar(); UI.ordem = S.cfg.ordem || 'data-asc';
aplicarTema(); aplicarZoom(); aplicarSide(); redesenhar(); toast('Backup importado.');
}catch(x){ toast('Arquivo inválido.'); } };
r.readAsText(f); e.target.value='';
}
function exportarCSV(){
const cab=['Tipo','Descrição','Clientes','Áreas','Processo','Data','Hora','Status','Prioridade','Ação','Responsáveis','Subtarefas','Link','Criado por','Criado em','Concluído em','Alterações'];
const linhas=S.tarefas.map(t=>[TIPOS[t.tipo].kind,t.descricao,
(t.clientes||[]).map(id=>{const c=cliente(id);return c?c.nome:'';}).join(' | '),
(t.areas||[]).join(' | '), t.processo||'', fmtD(t.data), t.hora||'', t.status, t.prioridade||'Média', t.acao, nomesResp(t).join(' | '),
(t.checklist&&t.checklist.ativo)? (t.checklist.itens.filter(i=>i.ok).length+'/'+t.checklist.itens.length) : '',
t.link||'', t.criadoPor||'', fmtDH(t.criadoEm), t.concluidoEm?fmtDH(t.concluidoEm):'', (t.historico||[]).length]);
const csv=[cab].concat(linhas).map(l=>l.map(c=>'"'+String(c==null?'':c).replace(/"/g,'""')+'"').join(';')).join('\r\n');
baixar('registros-'+new Date().toISOString().slice(0,10)+'.csv',''+csv,'text/csv;charset=utf-8'); toast('CSV gerado.');
}
function zerar(){ if(!confirm('Isso apaga equipe, clientes e todos os registros. Continuar?')) return;
if(!confirm('Tem certeza mesmo? Não há como desfazer.')) return;
localStorage.removeItem(KEY); S=seed(); salvar(); fechar('ov-cfg'); aplicarTema(); aplicarZoom(); redesenhar(); toast('Plataforma zerada.'); }
/* =========================================================
MEMBROS
========================================================= */
function sincronizarAssociadosManual(){
const r = sincronizarAssociados(true);
redesenhar(); renderCfg();
if(r.lidos===0){ toast('Abra a Gestão Financeira uma vez para que a lista fique disponível.'); return; }
if(!r.enviados && !r.importados){ toast('Tudo já está espelhado: '+r.lidos+' cadastro(s) na Gestão Financeira.'); return; }
const partes=[];
if(r.enviados) partes.push(r.enviados+' enviado(s) para a Gestão Financeira');
if(r.importados) partes.push(r.importados+' trazido(s) para a equipe');
toast('Associados sincronizados: '+partes.join(' · ')+'.');
}
function editarMembro(id){
const m = id? membro(id) : null;
if(id && !pode('gerir_equipe') && id!==S.usuario){ toast('Sem permissão.'); return; }
if(!id && !pode('gerir_equipe')){ toast('Sem permissão.'); return; }
UI.membroEdit = id; UI.fotoTmp = m? (m.foto||'') : '';
$('mb-titulo').textContent = m? m.nome : 'Novo integrante';
$('mb-funcao').innerHTML = Object.keys(FUNCOES).map(f=>'').join('');
$('mb-uf').innerHTML = ''+UFS.map(u=>'').join('');
['nome','oab','cpf','pix','email','tel','email2','tel2'].forEach(k=>$('mb-'+k).value = m? (m[k]||'') : '');
$('mb-status').value = m? m.status : 'ativo';
$('mb-escopo').value = m? m.escopo : FUNCOES[Object.keys(FUNCOES)[0]].escopo;
if(!m) aplicarPreset(); else { renderPerms(m.perms); notaFuncao(); }
$('mb-del').style.display = (m && pode('gerir_equipe') && m.id!==S.usuario)?'':'none';
$('mb-convite').style.display = pode('gerir_equipe')?'':'none';
const ro = !pode('gerir_equipe');
document.querySelectorAll('#mb-perms input,#mb-escopo,#mb-funcao,#mb-status').forEach(el=>el.disabled=ro);
travarFinanceiro(); /* a trava por função vale mesmo para quem administra a equipe */
$('mb-err').classList.remove('on');
fotoPrev();
abrir('ov-membro');
}
function renderPerms(perms){
const item = k=>{ const p=PERMS.find(x=>x.k===k);
return ''; };
$('mb-perms').innerHTML = GRUPOS_PERM.map((g,i)=>
'
'+
'
'+esc(g.n)+''+
''+
''+
'
'+
'
'+g.ks.map(item).join('')+'
'+
'
').join('');
contarPerms();
travarFinanceiro();
if(!pode('gerir_equipe')) document.querySelectorAll('#mb-perms input,#mb-perms button').forEach(el=>el.disabled=true);
}
/* Ver Financeiro só faz sentido para Administração e sócios — a caixa fica
travada nas demais funções, para não prometer um acesso que não existe. */
function travarFinanceiro(){
const f = $('mb-funcao') ? $('mb-funcao').value : '';
const liberado = FUNCOES_FINANCEIRO.indexOf(f)>=0;
const el = document.querySelector('#mb-perms input[data-p="ver_financeiro"]');
if(!el) return;
if(!liberado) el.checked=false;
el.disabled = !liberado;
const rot = el.closest('label');
if(rot){
rot.style.opacity = liberado?'':'.5';
rot.title = liberado?'' : 'Disponível apenas para Administração e Advogado(a) sócio(a).';
const pequeno = rot.querySelector('small');
if(pequeno) pequeno.textContent = liberado
? 'Honorários, RPV, precatórios e contas'
: 'restrito à Administração e aos sócios';
}
contarPerms();
}
function marcarGrupo(i,val){
if(!pode('gerir_equipe')) return;
GRUPOS_PERM[i].ks.forEach(k=>{ const el=document.querySelector('#mb-perms input[data-p="'+k+'"]'); if(el && !el.disabled) el.checked=val; });
travarFinanceiro();
}
function contarPerms(){
GRUPOS_PERM.forEach((g,i)=>{
const n = g.ks.filter(k=>{ const el=document.querySelector('#mb-perms input[data-p="'+k+'"]'); return el&&el.checked; }).length;
const el = document.querySelector('#mb-perms .pgc[data-c="'+i+'"]');
if(el){ el.textContent = n+' de '+g.ks.length; el.className = 'pgc'+(n?'':' zero'); }
});
}
function aplicarPreset(){
const f=$('mb-funcao').value, P=FUNCOES[f];
if(!P) return;
renderPerms(P.perms); $('mb-escopo').value=P.escopo;
travarFinanceiro(); notaFuncao();
}
function notaFuncao(){
const f=$('mb-funcao').value;
const fixa = FUNCOES_FIXAS.indexOf(f)>=0;
$('mb-preset-nota').innerHTML = fixa
? (f==='Administração'
? 'Administração é a única função com acesso geral: enxerga todos os painéis, administra a equipe e é a única que pode excluir clientes e registros concluídos.'
: 'Advogado(a) sócio(a) recebe tudo, menos administrar a equipe e excluir clientes e registros já concluídos — isso segue exclusivo da Administração.')
: ''+esc(f)+' não vem com permissões prontas. Marque abaixo, item a item, exatamente o que esta pessoa poderá ver e fazer.';
}
function carregarFoto(e){
const f=e.target.files[0]; if(!f) return;
if(f.size>2*1024*1024){ toast('Foto acima de 2 MB. Escolha uma menor.'); return; }
const r=new FileReader(); r.onload=()=>{ redimensionar(r.result, 220, d=>{ UI.fotoTmp=d; fotoPrev(); }); }; r.readAsDataURL(f);
e.target.value='';
}
function redimensionar(dataUrl,max,cb){
const img=new Image();
img.onload=()=>{ const s=Math.min(1,max/Math.max(img.width,img.height));
const c=document.createElement('canvas'); c.width=Math.round(img.width*s); c.height=Math.round(img.height*s);
c.getContext('2d').drawImage(img,0,0,c.width,c.height); cb(c.toDataURL('image/jpeg',.82)); };
img.onerror=()=>cb(dataUrl); img.src=dataUrl;
}
function limparFoto(){ UI.fotoTmp=''; fotoPrev(); }
function fotoPrev(){
const el=$('mb-foto-prev'); const n=$('mb-nome').value;
if(UI.fotoTmp){ el.style.backgroundImage='url('+UI.fotoTmp+')'; el.textContent=''; }
else { el.style.backgroundImage=''; el.textContent = iniciais(n||'?'); }
}
function coletarMembro(){
return {
nome:($('mb-nome').value||'').trim(), funcao:$('mb-funcao').value, cpf:$('mb-cpf').value,
status:$('mb-status').value, oab:($('mb-oab').value||'').trim(), uf:$('mb-uf').value,
pix:($('mb-pix').value||'').trim(), email:($('mb-email').value||'').trim(),
tel:$('mb-tel').value, email2:($('mb-email2').value||'').trim(), tel2:$('mb-tel2').value,
escopo:$('mb-escopo').value, foto:UI.fotoTmp||'',
perms:(()=>{const o={};document.querySelectorAll('#mb-perms input').forEach(i=>o[i.dataset.p]=i.checked);return o;})()
};
}
function validarMembro(o){
const e=[];
document.querySelector('#ov-membro [id="mb-nome"]').parentNode.classList.toggle('err',!o.nome);
if(!o.nome) e.push('Nome');
document.querySelector('#ov-membro [id="mb-email"]').parentNode.classList.toggle('err',!emailOk(o.email));
if(!emailOk(o.email)) e.push('E-mail principal');
if(o.oab && !o.uf) e.push('UF da OAB');
return e;
}
function salvarMembro(silencioso){
const o=coletarMembro(), err=validarMembro(o);
if(err.length){ const b=$('mb-err'); b.innerHTML='Revise: '+err.map(esc).join(', ')+'.'; b.classList.add('on'); return null; }
const vePainel = PERMS.filter(x=>x.k.indexOf('ver_')===0 && x.k!=='ver_equipe').some(x=>o.perms[x.k]);
if(!vePainel && !confirm('Nenhum painel foi liberado para '+o.nome+'. A pessoa vai abrir a plataforma sem nada para ver.\n\nSalvar assim mesmo?')) return null;
let m;
if(UI.membroEdit){ m=membro(UI.membroEdit); Object.assign(m,o); }
else { m=Object.assign({id:novoId('membro'),criadoEm:agoraISO()},o); S.membros.push(m); UI.membroEdit=m.id; }
salvar();
sincronizarAssociados(true);
if(!silencioso){ fechar('ov-membro'); redesenhar(); toast('Integrante salvo.'); }
return m;
}
function excluirMembro(){
const id=UI.membroEdit; if(!id) return;
const usado = S.tarefas.some(t=>(t.responsaveis||[]).indexOf(id)>=0);
if(usado && !confirm('Este integrante é responsável por registros existentes. Excluir mesmo assim? (Sugestão: marque como Inativo em vez de excluir.)')) return;
if(!usado && !confirm('Excluir este integrante?')) return;
S.membros=S.membros.filter(m=>m.id!==id);
if(S.usuario===id) S.usuario=S.membros[0]?S.membros[0].id:null;
salvar(); fechar('ov-membro'); redesenhar(); toast('Integrante excluído.');
}
function enviarConvite(){
const m = salvarMembro(true); if(!m) return;
if(m.status==='inativo'){ m.status='pendente'; }
if(m.status!=='ativo') m.status='pendente';
m.conviteEm=agoraISO(); salvar();
abrirEmailConvite(m);
fechar('ov-membro'); redesenhar();
}
function reenviar(id){ const m=membro(id); if(!m) return; m.conviteEm=agoraISO(); salvar(); abrirEmailConvite(m); renderCfg(); }
function abrirEmailConvite(m){
const remetente = eu()? eu().nome : 'a Administração';
const assunto = 'Convite para a plataforma de gestão do escritório';
const corpo =
'Olá, '+m.nome.split(' ')[0]+'!\n\n'+
'Você está sendo cadastrado(a) na plataforma de gestão do nosso escritório, onde acompanhamos prazos processuais, audiências, tarefas, perícias, avaliações sociais e atendimentos.\n\n'+
'Seus dados de cadastro:\n'+
'• Nome: '+m.nome+'\n'+
'• Função: '+m.funcao+'\n'+
(m.oab? '• OAB: '+m.oab+(m.uf?'/'+m.uf:'')+'\n' : '')+
'• E-mail de acesso: '+m.email+'\n'+
'• Nível de visualização: '+(m.escopo==='meu'?'Meu acesso (você vê os registros em que atua)':'Acesso completo')+'\n\n'+
'Assim que confirmar o recebimento deste convite, liberamos o seu acesso.\n\n'+
'Qualquer dúvida, é só responder este e-mail.\n\n'+
'Abraço,\n'+remetente;
const url='mailto:'+encodeURIComponent(m.email)+'?subject='+encodeURIComponent(assunto)+'&body='+encodeURIComponent(corpo);
let ok=false;
try{ ok = !!window.open(url,'_blank'); }catch(e){}
if(!ok){ try{ const a=document.createElement('a'); a.href=url; a.target='_blank';
document.body.appendChild(a); a.click(); a.remove(); }catch(e){} }
navigator.clipboard && navigator.clipboard.writeText(corpo).catch(()=>{});
toast('Convite registrado como pendente. Abrimos seu e-mail com o texto pronto (e copiamos para a área de transferência).');
}
/* =========================================================
CLIENTES
========================================================= */
function mascDoc(el){
let v=(el.value||'').replace(/\D/g,'').slice(0,14);
const h=$('cl-doc-hint');
if(v.length<=11){
el.value = v.replace(/(\d{3})(\d)/,'$1.$2').replace(/(\d{3})(\d)/,'$1.$2').replace(/(\d{3})(\d{1,2})$/,'$1-$2');
if(h) h.textContent = v.length===11 ? 'CPF — 11 dígitos.' : 'O formato é reconhecido pela quantidade de dígitos.';
} else {
el.value = v.replace(/^(\d{2})(\d)/,'$1.$2').replace(/^(\d{2})\.(\d{3})(\d)/,'$1.$2.$3')
.replace(/\.(\d{3})(\d)/,'.$1/$2').replace(/(\d{4})(\d)/,'$1-$2');
if(h) h.textContent = 'CNPJ — 14 dígitos.';
}
}
function tipoDoc(v){ const n=String(v||'').replace(/\D/g,'').length; return n===14?'CNPJ':(n===11?'CPF':''); }
function sugereEmail(){
const el=$('cl-email'), d=$('cl-email-drop'), v=el.value||'';
const at=v.indexOf('@');
if(at<0){ d.classList.remove('on'); return; }
const usuario=v.slice(0,at), dominio=v.slice(at+1).toLowerCase();
const achados = EMAIL_DOMS.filter(x=>x.indexOf(dominio)===0 && x!==dominio).slice(0,6);
if(!usuario || !achados.length){ d.classList.remove('on'); return; }
d.innerHTML = achados.map(x=>'').join('');
d.classList.add('on');
}
function usarEmail(v){ $('cl-email').value=v; $('cl-email-drop').classList.remove('on'); $('cl-email').focus(); }
function checaDrive(){
const sem = $('cl-semdrive') && $('cl-semdrive').checked;
const v=($('cl-drive').value||'').trim(), h=$('cl-drive-hint'), b=$('cl-drive-abrir');
if(sem){ h.textContent='Este cliente não tem pasta no Drive.'; h.style.color='';
b.style.opacity='.45'; b.style.pointerEvents='none'; b.href='#'; return; }
const ok = /^(https?:\/\/)?(drive|docs)\.google\.com\//i.test(v);
b.href = linkHref(v) || '#';
b.style.opacity = v ? '' : '.45';
b.style.pointerEvents = v ? '' : 'none';
if(!v){ h.textContent='Cole o link da pasta do cliente no Google Drive.'; h.style.color=''; return; }
h.textContent = ok ? 'Link do Google Drive reconhecido.' : 'Não parece um endereço do Google Drive — confira antes de salvar.';
h.style.color = ok ? 'var(--green)' : 'var(--amber)';
}
function cliqueDrive(ev){
const h = linkHref($('cl-drive').value);
if(!h){ ev.preventDefault(); toast('Cole antes o link da pasta.'); return false; }
ev.currentTarget.href = h; /* a âncora leva o endereço no momento do clique */
aposAbrir(h);
return true;
}
function toggleSemSenha(){
const sem=$('cl-semsenha').checked;
$('cl-senha').disabled=sem; if(sem) $('cl-senha').value='';
$('cl-senha-req').style.display = sem?'none':'';
$('cl-semsenha-w').classList.toggle('on',sem);
document.querySelector('[data-for="cl-senha"]').classList.remove('err');
}
function toggleSemDrive(){
const sem=$('cl-semdrive').checked;
$('cl-drive').disabled=sem; if(sem) $('cl-drive').value='';
$('cl-drive-req').style.display = sem?'none':'';
$('cl-semdrive-w').classList.toggle('on',sem);
document.querySelector('[data-for="cl-drive"]').classList.remove('err');
checaDrive();
}
function toggleSemProc(){
const sem=$('cl-semproc').checked;
['cl-proc','cl-tribunal'].forEach(id=>{ $(id).disabled=sem; if(sem) $(id).value=''; });
$('cl-proc-req').style.display = sem?'none':'';
$('cl-trib-req').style.display = sem?'none':'';
$('cl-semproc-w').classList.toggle('on',sem);
['cl-proc','cl-tribunal'].forEach(id=>{ const f=document.querySelector('[data-for="'+id+'"]'); if(f) f.classList.remove('err'); });
if(sem){ $('cl-status').value='Não iniciado'; }
}
function areaMudou(){
const a=$('cl-area').value, sel=$('cl-acao'), antes=sel.value;
const lista = CLI_ACOES[a] || CLI_ACOES['Outra'];
sel.innerHTML=''+lista.map(x=>'').join('');
if(lista.indexOf(antes)>=0) sel.value=antes;
}
function statusManual(){ UI.cliStatusManual = true; $('cl-status-hint').textContent='Definido manualmente por você.'; }
/* ---------- linha do tempo do processo ---------- */
function abrirColar(){ const d=$('cl-colar'); d.style.display = d.style.display==='none'?'':'none';
if(d.style.display==='') $('cl-colar-txt').focus(); }
function consultarProcesso(){
const url=($('cl-tribunal').value||'').trim();
if(!url){ toast('Informe o link do tribunal para abrir o processo.'); $('cl-tribunal').focus(); return; }
abrirLink(url);
$('cl-colar').style.display='';
setTimeout(()=>$('cl-colar-txt').focus(), 300);
toast('Processo aberto no tribunal. Copie as movimentações e cole no campo abaixo.');
}
function processarColagem(){
const txt=$('cl-colar-txt').value||'';
const linhas = txt.split(/\r?\n/).map(x=>x.trim()).filter(Boolean);
const novos=[];
let atual=null;
linhas.forEach(l=>{
const m = /^(\d{2})[\/\.-](\d{2})[\/\.-](\d{4})\s*(?:[-–—:]\s*)?(.*)$/.exec(l);
if(m){
if(atual && atual.texto) novos.push(atual);
atual = {data:m[3]+'-'+m[2]+'-'+m[1], texto:(m[4]||'').trim(), em:agoraISO()};
} else if(atual){ atual.texto = (atual.texto?atual.texto+' ':'')+l; }
});
if(atual && atual.texto) novos.push(atual);
if(!novos.length){ toast('Não encontrei linhas começando com data (dd/mm/aaaa).'); return; }
UI.movTmp = UI.movTmp || [];
const chave = x => x.data+'|'+x.texto.toLowerCase().slice(0,60);
const jaTem = new Set(UI.movTmp.map(chave));
let add=0;
novos.forEach(x=>{ if(!jaTem.has(chave(x))){ UI.movTmp.push(x); add++; } });
UI.movTmp.sort((a,b)=> (b.data>a.data?1:b.datam.texto.toLowerCase()).join(' | ');
if(/desist[êe]ncia|renúncia ao direito|homologa[çc][ãa]o da desist/.test(txt)) return 'Desistência';
if(/arquiv|baixa definitiva|trânsito em julgado|extin[çc][ãa]o do processo/.test(txt)) return 'Arquivado';
return 'Em andamento';
}
function renderLinha(){
const movs = UI.movTmp || [];
$('cl-mov-n').textContent = movs.length ? movs.length+' movimentação(ões) registrada(s)' : 'Nenhuma movimentação registrada ainda.';
$('cl-linha').innerHTML = movs.length
? '
'+movs.map((m,i)=>
'
'+fmtD(m.data)+'
'+esc(m.texto)+'
'+
'
').join('')+'
'+
''
: '';
}
function removerMov(i){ UI.movTmp.splice(i,1); if(!UI.cliStatusManual) $('cl-status').value=statusPorMovimentos(UI.movTmp); renderLinha(); }
function limparMovs(){ if(!confirm('Apagar toda a linha do tempo deste processo?')) return;
UI.movTmp=[]; if(!UI.cliStatusManual) $('cl-status').value='Não iniciado'; renderLinha(); }
/* ---------- abrir / salvar ---------- */
function editarCliente(id){
if(!pode('gerir_clientes')){ toast('Sem permissão para cadastrar clientes.'); return; }
sincronizarAssociados(true);
const c = id? cliente(id) : null;
UI.clienteEdit = id;
UI.movTmp = c && c.movimentacoes ? JSON.parse(JSON.stringify(c.movimentacoes)) : [];
UI.cliStatusManual = !!(c && c.statusManual);
$('cl-kind').textContent = c ? 'Cliente · '+(c.area||'') : 'Cadastro';
$('cl-titulo').textContent = c? c.nome : 'Novo cliente';
$('cl-area').innerHTML = ''+CLI_AREAS.map(a=>'').join('');
$('cl-origem').innerHTML = ''+CLI_ORIGENS.map(a=>'').join('');
$('cl-estado').innerHTML = ''+ESTADOS.map(u=>'').join('');
$('cl-status').innerHTML = PROC_STATUS.map(x=>'').join('');
$('cl-nome').value = c? c.nome : '';
$('cl-doc').value = c? (c.cpfCnpj||c.cpf||'') : ''; mascDoc($('cl-doc'));
$('cl-senha').value = c? (c.senhaInss||'') : '';
$('cl-semsenha').checked = !!(c && c.semSenhaInss); toggleSemSenha();
$('cl-tel').value = c? (c.tel||'') : '';
$('cl-email').value = c? (c.email||'') : '';
$('cl-area').value = c? (c.area||'') : ''; areaMudou();
$('cl-acao').value = c? (c.tipoAcao||'') : '';
$('cl-origem').value = c? (c.origem||'') : '';
$('cl-estado').value = c? (c.estado||'') : '';
$('cl-contrato').value = c? (c.dataContrato||'') : '';
$('cl-drive').value = c? (c.linkDrive||'') : '';
$('cl-semdrive').checked = !!(c && c.semDrive); toggleSemDrive();
$('cl-proc').value = c? (c.numProcesso||c.processo||'') : '';
$('cl-semproc').checked = !!(c && c.semProcesso); toggleSemProc();
$('cl-tribunal').value = c? (c.tribunal||'') : '';
$('cl-status').value = c? (c.statusProcesso||'Não iniciado') : 'Não iniciado';
$('cl-status-hint').textContent = UI.cliStatusManual ? 'Definido manualmente por você.' : 'Atualizado pelas movimentações; pode ser ajustado à mão.';
$('cl-obs').value = c? (c.obs||'') : '';
$('cl-criado').value = c? ((c.criadoPor||'—')+' · '+fmtDH(c.criadoEm)) : (eu().nome+' · '+fmtDH(agoraISO()));
$('cl-alt').value = c && c.alteradoEm ? (c.alteradoPor||'—')+' · '+fmtDH(c.alteradoEm) : '—';
$('cl-parc-wrap').innerHTML = msHTML('cl-parc','Digite o nome do parceiro…');
const parcList = finCadastro('PARCEIRO');
msInit('cl-parc', (parcList||[]).map(x=>({id:x.id, nome:x.nome, sub:x.area||''})),
((c&&c.parceiros)||[]).map(x=>x.id).filter(x=>x!=null));
if(parcList===null) $('cl-parc-inp').placeholder = 'Abra a Gestão Financeira uma vez para carregar';
$('cl-part-wrap').innerHTML = msHTML('cl-part','Digite o nome de quem atua no caso…');
msInit('cl-part', atribuiveis().map(m=>({id:m.id, nome:m.nome, sub:m.funcao})), (c&&c.participantes)||[]);
$('cl-del').style.display = (c && pode('excluir_protegidos'))?'':'none';
$('cl-hist-wrap').style.display = c?'':'none';
renderHistCliente(c);
renderLinha();
$('cl-colar').style.display='none';
$('cl-err').classList.remove('on');
document.querySelectorAll('#ov-cliente .f').forEach(f=>f.classList.remove('err'));
abrir('ov-cliente');
}
function renderHistCliente(c){
const h = (c && c.historico) || [];
$('cl-hist-n').textContent = h.length? '('+h.length+')' : '';
$('cl-hist-nota').innerHTML = ehAdmin()
? 'Tudo o que muda neste cadastro fica registrado. Como Administração, você pode remover linhas.'
: 'Tudo o que muda neste cadastro fica registrado. Não pode ser editado por este perfil.';
$('cl-hist-list').innerHTML = h.length
? '
'+
''+esc(c.semProcesso?'Sem processo':(c.statusProcesso||'Não iniciado'))+''+
(c.dataContrato?'contrato '+fmtD(c.dataContrato)+'':'')+
(c.numProcesso?'⚖':'')+
((c.movimentacoes||[]).length?'🕘'+c.movimentacoes.length+'':'')+
btnLink(c.linkDrive,'📁 Drive','Abrir a pasta deste cliente no Drive')+
'
';
}
function renderCliKanban(lista){
const st=cliEstado(), d=cliDim(st.grupo);
if(!lista.length){ $('cli-corpo').innerHTML='
Nenhum cliente no recorte
'+
'
Ajuste os filtros acima ou cadastre um novo cliente.