DH
EMPREITEIRA
Gestão de obras

Acesso administrativo preenchido para teste.

EMPREITEIRA
`);w.document.close(); } window.openBackupHub=()=>openSheet('Backup',`

Salve uma cópia dos dados deste aparelho ou restaure um arquivo anterior.

`); window.openSettingsHub=()=>{ let a=get(S.admin,{}); openSheet('Configurações',`
`); document.getElementById('settingsAdmin').onsubmit=e=>{e.preventDefault();put(S.admin,Object.fromEntries(new FormData(e.target)));closeSheet();toastMsg('Configurações salvas')}; } window.exportBackup=()=>{let data={};Object.keys(localStorage).filter(k=>k.startsWith('dh5_')).forEach(k=>data[k]=localStorage.getItem(k));let b=new Blob([JSON.stringify(data,null,2)],{type:'application/json'}),u=URL.createObjectURL(b),a=document.createElement('a');a.href=u;a.download='DH_Empreiteira_backup_'+new Date().toISOString().slice(0,10)+'.json';a.click();setTimeout(()=>URL.revokeObjectURL(u),1000);toastMsg('Backup exportado')}; window.importBackup=file=>{if(!file)return;let r=new FileReader;r.onload=()=>{try{let d=JSON.parse(r.result);Object.entries(d).forEach(([k,v])=>{if(k.startsWith('dh5_'))localStorage.setItem(k,v)});toastMsg('Backup restaurado');render()}catch(e){toastMsg('Backup inválido')}};r.readAsText(file)}; const V11={settings:'dh11_settings',reserve:'dh11_reserve'}; function v11settings(){return get(V11.settings,{reservePct:10,daniloPct:50,davidPct:50})} function v11reserve(){return get(V11.reserve,[])} window.renderMore=function(){if(session.role!=='admin'){more.innerHTML='';return}more.innerHTML=`

Mais

Ferramentas administrativas da DH Empreiteira.

`} window.reports11=()=>openSheet('Relatórios',`

Central de relatórios

`); window.reserve11=()=>{let st=v11settings(),f=get(S.finance),auto=f.filter(x=>x.type==='entrada').reduce((a,x)=>a+(+x.value||0),0)*(+st.reservePct/100),mv=v11reserve(),manual=mv.reduce((a,x)=>a+(x.type==='entrada'?1:-1)*(+x.value||0),0);openSheet('Reserva DH',`
Saldo da Reserva DH${M(auto+manual)}Reserva automática: ${st.reservePct}%

Automática sobre entradas: ${M(auto)}

Ajustes manuais: ${M(manual)}

Histórico

${mv.length?mv.slice().reverse().map(x=>`
${E(x.description)}
${E(x.date)}
${x.type==='entrada'?'+':'−'} ${M(x.value)}
`).join(''):'
Nenhuma movimentação manual.
'}
`)}; window.reserveMove11=()=>openSheet('Movimentação da Reserva',`
`); window.pickR11=b=>{document.querySelectorAll('[data-r11]').forEach(x=>x.classList.remove('on'));b.classList.add('on');window.r11type=b.dataset.r11} document.addEventListener('submit',e=>{if(e.target.id!=='reserve11form')return;e.preventDefault();let x=Object.fromEntries(new FormData(e.target)),a=v11reserve();x.type=window.r11type||'entrada';a.push(x);put(V11.reserve,a);toastMsg('Movimentação salva');reserve11()}); window.partners11=()=>{let st=v11settings();openSheet('Sócios',`
D
Danilo
Financeiro e administração · ${st.daniloPct}%
D
David
Sócio · ${st.davidPct}%
`)}; window.settings11=()=>{let st=v11settings();openSheet('Configurações',`
`)}; document.addEventListener('submit',e=>{if(e.target.id!=='settings11form')return;e.preventDefault();let x=Object.fromEntries(new FormData(e.target));if(+x.daniloPct + +x.davidPct!==100){toastMsg('Danilo + David deve somar 100%');return}put(V11.settings,x);closeSheet();toastMsg('Configurações salvas')}); window.clientReport11=()=>{let cs=get(S.clients);openSheet('Relatório do cliente',`
`)}; document.addEventListener('submit',e=>{if(e.target.id!=='cr11')return;e.preventDefault();let x=Object.fromEntries(new FormData(e.target)),c=get(S.clients).find(c=>c.id===x.clientId),net=(+x.labor||0)-(+x.discounts||0),w=window.open('','_blank');w.document.write(`Relatório Cliente

DH EMPREITEIRA

RELATÓRIO DO CLIENTE

${E(c?.name||'Cliente')}

${E(x.title||'')}

Valor acordado: ${M(x.labor)}

Abatimentos: ${M(x.discounts)}

Saldo: ${M(net)}

${E(x.notes||'')}

`);w.document.close()}); window.monthly11=()=>{let ws=get(S.works),f=get(S.finance),r=f.filter(x=>x.type==='entrada').reduce((a,x)=>a+ +x.value,0),d=f.filter(x=>x.type==='saida').reduce((a,x)=>a+ +x.value,0),w=window.open('','_blank');w.document.write(`Relatório Mensal

DH EMPREITEIRA

RELATÓRIO MENSAL

Financeiro

Entradas ${M(r)} · Despesas ${M(d)} · Saldo ${M(r-d)}

Obras

${ws.map(x=>``).join('')}
${E(x.title)}${E(x.status)}${+x.progress||0}%
`);w.document.close()}; window.workFinance11=()=>{let ws=get(S.works),f=get(S.finance);openSheet('Financeiro por obra',ws.length?ws.map(w=>{let z=f.filter(x=>x.workId===w.id),r=z.filter(x=>x.type==='entrada').reduce((a,x)=>a+ +x.value,0),d=z.filter(x=>x.type==='saida').reduce((a,x)=>a+ +x.value,0);return `
${E(w.title)}
Entradas ${M(r)} · Despesas ${M(d)}
Saldo ${M(r-d)}
`}).join(''):'
Nenhuma obra cadastrada.
')}; window.newFinance=function(){window.f11={type:'entrada',workId:''};financeForm11()}; window.financeForm11=function(){let draft=window.f11||{type:'entrada',workId:''},wn=(get(S.works).find(w=>w.id===draft.workId)||{}).title||'Selecione uma obra';openSheet('Novo lançamento',`
`);document.getElementById('fin11form').onsubmit=e=>{e.preventDefault();let x=Object.fromEntries(new FormData(e.target)),a=get(S.finance);x.id=Date.now().toString();x.type=window.f11.type;x.workId=window.f11.workId;a.push(x);put(S.finance,a);closeSheet();toastMsg('Lançamento salvo');renderFinance();renderHome()}}; window.pickF11=b=>{document.querySelectorAll('[data-f11]').forEach(x=>x.classList.remove('on'));b.classList.add('on');window.f11.type=b.dataset.f11} window.pickWork11=()=>{let ws=get(S.works);openSheet('Selecionar obra',`${ws.map(w=>``).join('')}`)}; window.chooseWork11=id=>{window.f11.workId=id;financeForm11()}; /* V12 — telas internas premium */ window.openClientForm=function(){openSheet('Cadastrar cliente',`

Preencha os dados do cliente para começar.

`)}; document.addEventListener('submit',e=>{if(e.target.id!=='client12')return;e.preventDefault();let x=Object.fromEntries(new FormData(e.target)),a=get(S.clients);x.id=Date.now().toString();a.push(x);put(S.clients,a);closeSheet();toastMsg('Cliente cadastrado');renderClients()}); /* Financeiro geral */ window.renderFinance=function(){ let fs=get(S.finance); if(session.role==='client'){let ws=clientWorks(),total=ws.reduce((a,w)=>a+(+w.value||0),0);finance.innerHTML=`

Financeiro

Informações das suas obras
Valor contratado
${M(total)}
`;return} let ent=fs.filter(x=>x.type==='entrada').reduce((a,x)=>a+(+x.value||0),0),des=fs.filter(x=>x.type==='saida').reduce((a,x)=>a+(+x.value||0),0),st=v11settings(),res=ent*(+st.reservePct||10)/100,dt=davidTotals?davidTotals():{due:0},saldo=ent-des; finance.innerHTML=`

Financeiro Geral

Controle todas as entradas, despesas e o saldo da empresa.
Total de entradas${M(ent)}
Total de despesas${M(des)}
Saldo do mês${M(saldo)}
Reserva DH (${st.reservePct}%) ›${M(res)}
A receber (David) ›${M(dt.due||0)}
Saldo disponível${M(saldo-res)}

Movimentações

${fs.length?fs.slice().reverse().map((x,i)=>`
${E(x.description)}${E(x.date||'')} · ${E(x.workId?(get(S.works).find(w=>w.id===x.workId)||{}).title||'Obra':'Geral')}
${x.type==='entrada'?'+':'−'} ${M(x.value)}
`).join(''):'
Nenhum lançamento.
'}
`; }; window.finance12=t=>{window.f11={type:t,workId:''};financeForm11()}; window.editFin12=(id,idx)=>{let a=get(S.finance),x=id?a.find(v=>v.id===id):a[idx];if(!x)return;openSheet('Editar lançamento',`
`);document.getElementById('editFin12Form').onsubmit=e=>{e.preventDefault();Object.assign(x,Object.fromEntries(new FormData(e.target)));put(S.finance,a);closeSheet();toastMsg('Lançamento atualizado');renderFinance()}}; window.delFin12=(id,idx)=>{let a=get(S.finance),x=id?a.find(v=>v.id===id):a[idx];openSheet('Excluir lançamento',`

Deseja excluir ${E(x?.description||'este lançamento')}?

`)}; window.confirmDelFin12=(id,idx)=>{let a=get(S.finance);a=id?a.filter(v=>v.id!==id):a.filter((_,i)=>i!==idx);put(S.finance,a);closeSheet();toastMsg('Lançamento excluído');renderFinance()}; /* Reserva DH premium */ window.reserveMove11=function(){window.r11type='entrada';openSheet('Movimentação da Reserva DH',`

Registre entradas ou retiradas da reserva da DH Empreiteira.

ⓘ A Reserva DH corresponde a ${v11settings().reservePct}% dos serviços.
Use esta área para ajustes, transferências ou outras movimentações relacionadas à reserva.
`)}; window.pickR12=b=>{document.querySelectorAll('[data-r12]').forEach(x=>x.classList.remove('on'));b.classList.add('on');window.r11type=b.dataset.r12} document.addEventListener('submit',e=>{if(e.target.id!=='reserve12')return;e.preventDefault();let x=Object.fromEntries(new FormData(e.target)),a=v11reserve();x.id=Date.now().toString();x.type=window.r11type||'entrada';a.push(x);put(V11.reserve,a);toastMsg('Movimentação salva');reserve11()}); /* Fotos da evolução em tela cheia: torna qualquer foto de update clicável após renderização */ window.openPhoto12=(src,cap='')=>{let d=document.createElement('div');d.className='light12';d.innerHTML=`
${E(cap)}
`;d.querySelector('.x').onclick=()=>d.remove();d.onclick=e=>{if(e.target===d)d.remove()};document.body.appendChild(d)}; function wirePhotos12(){document.querySelectorAll('#works img,#evolution img').forEach(img=>{if(img.dataset.v12)return;img.dataset.v12='1';img.classList.add('photo12');img.addEventListener('click',()=>openPhoto12(img.src,img.alt||'Foto da evolução da obra'))})} new MutationObserver(()=>wirePhotos12()).observe(document.body,{childList:true,subtree:true}); /* Financeiro de obra: injeta controles editáveis quando a aba financeiro da ficha é aberta */ function enhanceWorkFinance12(){let root=document.querySelector('.ui-sheet');if(!root)return;let h=[...root.querySelectorAll('h2,h3')].find(x=>/Financeiro da obra/i.test(x.textContent));if(!h||root.querySelector('.wf12'))return;let box=h.closest('.card')||h.parentElement;let workTitle=[...root.querySelectorAll('h2,h3,b')].find(x=>x.textContent&&x.textContent.trim()!=='Financeiro da obra')?.textContent||'';let w=get(S.works).find(x=>x.title===workTitle)||get(S.works)[0];if(!w)return;let f=get(S.finance).filter(x=>x.workId===w.id),r=f.filter(x=>x.type==='entrada').reduce((a,x)=>a+ +x.value,0),d=f.filter(x=>x.type==='saida').reduce((a,x)=>a+ +x.value,0),st=v11settings();let el=document.createElement('div');el.className='wf12';el.innerHTML=`
Valor contratado${M(w.value||0)}
Reserva DH (${st.reservePct}%)${M((+w.value||0)*st.reservePct/100)}
Recebimentos${M(r)}
Despesas${M(d)}
Saldo atual${M(r-d)}
`;box.appendChild(el)} new MutationObserver(()=>enhanceWorkFinance12()).observe(document.body,{childList:true,subtree:true}); /* ===== DH EMPREITEIRA 13.0 — REVISÃO GERAL ===== */ /* Nova obra: sem nome manual e endereço puxado do cliente */ window.openNewWork=function(){ workDraft={clientId:'',type:'Somente mão de obra',status:'Planejada'}; openSheet('Nova obra',newWorkHTML()); bindNewWork(); }; window.newWorkHTML=function(){ let cs=get(S.clients),c=cs.find(x=>x.id===workDraft.clientId); return `

Cadastre a obra usando os dados já existentes do cliente.

${c?`
⌖ Endereço da obra: ${E(c.address||'Endereço não cadastrado no cliente')}
`:''}
${['Planejada','Em execução','Pausada','Concluída'].map((x,i)=>``).join('')}
ⓘ O endereço é puxado automaticamente do cadastro do cliente. Para alterá-lo, edite a ficha do cliente.
`; }; window.workProgress13=d=>{workDraft.progress=Math.max(0,Math.min(100,(+workDraft.progress||0)+d));let a=document.getElementById('workProgress13'),b=document.getElementById('workProgress13Input');if(a)a.textContent=workDraft.progress+'%';if(b)b.value=workDraft.progress}; window.bindNewWork=function(){ let f=document.getElementById('newWorkForm');if(!f)return; f.onsubmit=e=>{e.preventDefault();if(!workDraft.clientId){toastMsg('Selecione um cliente');return} let x=Object.fromEntries(new FormData(e.target)),clients=get(S.clients),c=clients.find(z=>z.id===workDraft.clientId),a=get(S.works); x.id=Date.now().toString();x.clientId=workDraft.clientId;x.type=workDraft.type;x.status=workDraft.status; x.progress=Math.max(0,Math.min(100,+x.progress||0));x.title='Obra — '+(c?.name||'Cliente');x.address=c?.address||''; a.push(x);put(S.works,a);closeSheet();toastMsg('Obra cadastrada');render(); }}; window.setWorkChoice=function(k,v){ let f=document.getElementById('newWorkForm'),vals=f?Object.fromEntries(new FormData(f)):{}; workDraft[k]=v;if(k!=='progress')workDraft.progress=+(vals.progress||workDraft.progress||0); document.getElementById('uiSheet').innerHTML=`

Nova obra

${newWorkHTML()}`; bindNewWork();let nf=document.getElementById('newWorkForm');Object.entries(vals).forEach(([a,b])=>{if(nf.elements[a]&&a!=='progress')nf.elements[a].value=b}); }; window.chooseClient=function(id){workDraft.clientId=id;openNewWorkKeep()}; window.openNewWorkKeep=function(){document.getElementById('uiSheet').innerHTML=`

Nova obra

${newWorkHTML()}`;bindNewWork()}; /* Ficha do cliente com abas realmente funcionais */ window.openClientDetail=function(id,tab='dados'){ let c=get(S.clients).find(x=>x.id===id);if(!c)return; let ws=get(S.works).filter(w=>w.clientId===id),f=get(S.finance).filter(x=>ws.some(w=>w.id===x.workId)); let tabs=`
${[['dados','Dados'],['obras','Obras'],['financeiro','Financeiro'],['docs','Documentos']].map(x=>``).join('')}
`; let body=''; if(tab==='dados')body=`

Dados do cliente

Telefone: ${E(c.phone||'—')}

E-mail: ${E(c.email||'—')}

Endereço: ${E(c.address||'—')}

Usuário: ${E(c.login||'—')}

`; if(tab==='obras')body=ws.length?ws.map(w=>``).join(''):'
Nenhuma obra deste cliente.
'; if(tab==='financeiro'){let r=f.filter(x=>x.type==='entrada').reduce((a,x)=>a+ +x.value,0),d=f.filter(x=>x.type==='saida').reduce((a,x)=>a+ +x.value,0);body=`
Recebimentos${M(r)}
Despesas${M(d)}
`} if(tab==='docs')body=`

Documentos

Relatórios e documentos vinculados ao cliente aparecerão aqui.
`; openSheet(c.name,tabs+body+`
${tab==='dados'?``:''}
`); }; window.editClient13=function(id){let a=get(S.clients),c=a.find(x=>x.id===id);openSheet('Editar cliente',`
`);document.getElementById('editClient13Form').onsubmit=e=>{e.preventDefault();Object.assign(c,Object.fromEntries(new FormData(e.target)));put(S.clients,a);/* sincroniza endereço nas obras */let wa=get(S.works);wa.filter(w=>w.clientId===id).forEach(w=>{w.address=c.address||'';w.title='Obra — '+c.name});put(S.works,wa);toastMsg('Cliente atualizado');openClientDetail(id)}}; /* Financeiro da obra totalmente ajustável */ window.drawDetailBody=function(w,c){ let b=document.getElementById('detailBody'),ups=get(S.updates).filter(x=>x.workId===w.id),st=v11settings(); if(currentDetailTab==='resumo') b.innerHTML=`

Progresso geral da obra

${+w.progress||0}%

Resumo

Cliente${E(c?.name||'')}
Endereço${E(c?.address||w.address||'—')}
Tipo${E(w.type)}
Valor contratado${M(w.value)}
Status${E(w.status)}
`; if(currentDetailTab==='evolucao') b.innerHTML=`

Progresso geral da obra

${+w.progress||0}%

Atualizações

${session.role==='admin'?``:''}
${ups.length?timeline(ups):'
Ainda não há atualizações desta obra.
'}
`; if(currentDetailTab==='financeiro'){ let f=get(S.finance).filter(x=>x.workId===w.id),r=f.filter(x=>x.type==='entrada').reduce((a,x)=>a+ +x.value,0),d=f.filter(x=>x.type==='saida').reduce((a,x)=>a+ +x.value,0),reserve=(+w.value||0)*(+st.reservePct||10)/100; b.innerHTML=`

Financeiro da obra

${session.role==='admin'?``:''}
Valor contratado${M(w.value)}
Reserva DH (${st.reservePct}%)${M(reserve)}
Recebimentos${M(r)}
Despesas${M(d)}
Saldo financeiro${M(r-d)}
A receber${M(Math.max(0,(+w.value||0)-r))}
${session.role==='admin'?`
`:''}

Lançamentos desta obra

${f.length?f.slice().reverse().map(x=>`
${E(x.description)}${E(x.date||'')}
${x.type==='entrada'?'+':'−'} ${M(x.value)}${session.role==='admin'?``:''}
`).join(''):'
Nenhum lançamento vinculado a esta obra.
'}
`; } if(currentDetailTab==='docs') b.innerHTML=`

Documentos

`; wirePhotos12(); }; window.financeWork13=(id,type)=>{window.f11={type,workId:id};financeForm11()}; window.editWorkValue13=function(id){let a=get(S.works),w=a.find(x=>x.id===id);openSheet('Editar valores da obra',`
A Reserva DH será recalculada automaticamente conforme o percentual definido nas Configurações.
`);document.getElementById('workValue13').onsubmit=e=>{e.preventDefault();w.value=+new FormData(e.target).get('value')||0;put(S.works,a);closeSheet();toastMsg('Valor atualizado');drawWorkDetail()}}; window.editWork13=function(id){let a=get(S.works),w=a.find(x=>x.id===id);openSheet('Editar obra',`
`);document.getElementById('editWork13Form').onsubmit=e=>{e.preventDefault();Object.assign(w,Object.fromEntries(new FormData(e.target)));put(S.works,a);closeSheet();toastMsg('Obra atualizada');drawWorkDetail()}}; /* Fotos da evolução: abrir grande sem sair do app */ window.timeline=function(a){return ``}; /* Atualizar obra sem select nativo cinza */ window.editProgress=function(id){let a=get(S.works),w=a.find(x=>x.id===id);if(!w)return;window.editStatus13=w.status;window.editPct13=Math.round((+w.progress||0)/5)*5;openSheet('Atualizar obra',`
${['Planejada','Em execução','Pausada','Concluída'].map((x,i)=>``).join('')}
${window.editPct13}%
`);document.getElementById('progress13').onsubmit=e=>{e.preventDefault();w.status=window.editStatus13;w.progress=window.editPct13;put(S.works,a);closeSheet();toastMsg('Obra atualizada');render()}}; window.pickEditStatus13=b=>{document.querySelectorAll('[data-es13]').forEach(x=>x.classList.remove('on'));b.classList.add('on');window.editStatus13=b.dataset.es13}; window.editPctBy13=d=>{window.editPct13=Math.max(0,Math.min(100,window.editPct13+d));document.getElementById('editPct13').textContent=window.editPct13+'%'}; /* Corrige títulos/endereço antigos na exibição, sem apagar dados */ window.workCard=function(w){let c=get(S.clients).find(x=>x.id===w.clientId),cl=w.status==='Concluída'?'done':w.status==='Pausada'?'pause':'',title='Obra — '+(c?.name||'Cliente'),addr=c?.address||w.address||'Endereço não cadastrado';return `
${E(title)}
${E(addr)}
${E(w.status)} ${E(w.type)}
${+w.progress||0}% concluído
`};