erros=validarCampos({req:[['name','Nome'],['locality','Localidade'],['community','Comunidade'],['investiture','Investidura'],['expiry','Vencimento']],valores:{name,locality,community,investiture,expiry}}); if(erros.length){UI.toast(erros[0],'e');return;} const cid=State.clienteId; const lim=checkLimite('ministros'); const editId=gv('m-edit-id'); if(!editId&&!lim.ok){PlanUpgrade.showModal('ministros');return;} try{ const ex=editId?State.ministers.find(m=>m.id===editId):null; const rec={id:editId||Util.uid(),name,phone:gv('m-phone'),locality,community,address:gv('m-address'),investiture,expiry,public_uuid:ex?.public_uuid||Util.uuid(),client_id:cid,created_at:ex?.created_at||Util.now()}; await Repo.saveMinister(cid,rec); State.ministers=await Repo.getMinisters(cid); UI.closeModal('ov-minister'); this.render(); UI.toast(editId?'Ministro atualizado!':'Ministro cadastrado!','s'); await Repo.log('ministro-'+(editId?'edit':'add'),State.user?.id||State.user?.uid||'',{name}); }catch(e){UI.toast('Erro ao salvar ministro','e');console.error(e);} }, render(){ const q=gv('m-search').toLowerCase(),fs=gv('m-fstatus'); let list=State.ministers.map(m=>({...m,_st:Util.mStatus(m.expiry),_enf:State.enfermos.filter(e=>e.minister_id===m.id).length})); if(q)list=list.filter(m=>m.name.toLowerCase().includes(q)||(m.community||'').toLowerCase().includes(q)); if(fs)list=list.filter(m=>m._st===fs); list.sort((a,b)=>Util.commOrder(a.community)-Util.commOrder(b.community)||a.name.localeCompare(b.name)); const mc=document.getElementById('minister-count');if(mc)mc.textContent=list.length+' ministros'; document.getElementById('m-tbl').innerHTML=list.length?list.map(m=>` ${m.name} ${m.community||'—'}${m.locality?' · '+m.locality:''} ${Util.fmtD(m.expiry||'')} ${UI.bM(m._st)}
`).join(''):'

Nenhum ministro cadastrado.

'; document.getElementById('m-cards').innerHTML=list.map(m=>`
${m.name}
${UI.bM(m._st)}
${m.community||'—'}${m.locality?' · '+m.locality:''}
📅 Vence: ${Util.fmtD(m.expiry||'')} · ${m._enf} enfermos${m.phone?'
📞 '+m.phone:''}
`).join(''); }, edit(id){ const m=State.ministers.find(x=>x.id===id);if(!m)return; sv('m-edit-id',m.id);sv('m-name',m.name);sv('m-phone',m.phone||'');sv('m-locality',m.locality||''); const stdComm=['Matriz','São José','Nossa Senhora','Santo Antônio']; if(stdComm.includes(m.community)){sv('m-community',m.community);document.getElementById('m-comm-grp').style.display='none';} else{sv('m-community','_custom');sv('m-comm-custom',m.community||'');document.getElementById('m-comm-grp').style.display='';} sv('m-address',m.address||'');sv('m-investiture',m.investiture||'');sv('m-expiry',m.expiry||''); document.getElementById('minister-modal-title').textContent='Editar Ministro'; UI.openModal('ov-minister'); }, async remove(id,name){ UI.confirm('Excluir ministro','Deseja excluir '+name+'? Enfermos vinculados também serão removidos.',async()=>{ await Repo.deleteMinister(State.clienteId,id); State.ministers=await Repo.getMinisters(State.clienteId); State.enfermos=await Repo.getEnfermos(State.clienteId); this.render();UI.toast('Ministro removido','w'); }); }, openPubLink(id){ const m=State.ministers.find(x=>x.id===id);if(!m)return; document.getElementById('pub-link-minister').textContent=m.name; const url=buildMinisterLink(m.public_uuid); const warn=document.getElementById('pub-link-warning'); const urlRow=document.getElementById('pub-link-url-row'); const disp=document.getElementById('pub-link-display'); const shareBtn=document.getElementById('btn-share'); const status=getSiteUrlStatus(); if(status==='local'){warn.style.display='';urlRow.style.display='';disp.textContent='Configure a URL do site acima';} else{warn.style.display='none';urlRow.style.display='none';disp.textContent=url||'—';} if(shareBtn)shareBtn.style.display=navigator.share?'':'none'; UI.openModal('ov-pub-link'); // QR document.getElementById('qr-minister-name').textContent=m.name; document.getElementById('qr-minister-sub').textContent='Portal do Ministro'; document.getElementById('qr-url-display').textContent=url||''; document.getElementById('qr-canvas-area').innerHTML=''; if(url)try{new QRCode(document.getElementById('qr-canvas-area'),{text:url,width:200,height:200,colorDark:'#0a1a16',colorLight:'#ffffff'});}catch{} MinisterCtrl._curLink=url;MinisterCtrl._curMinId=id; }, copyLink(){ const url=MinisterCtrl._curLink;if(!url){UI.toast('Configure a URL do site primeiro','w');return;} navigator.clipboard?.writeText(url).then(()=>UI.toast('Link copiado!','s')).catch(()=>{const ta=document.createElement('textarea');ta.value=url;document.body.appendChild(ta);ta.select();document.execCommand('copy');ta.remove();UI.toast('Link copiado!','s');}); }, shareLink(){ const url=MinisterCtrl._curLink;if(!url||!navigator.share)return; const m=State.ministers.find(x=>x.id===MinisterCtrl._curMinId); navigator.share({title:'Portal MESC',text:'Portal de visitas de '+(m?.name||'ministro'),url}).catch(()=>{}); }, saveSiteUrl(){ const url=document.getElementById('site-url-input')?.value?.trim(); if(!url||!url.startsWith('http')){UI.toast('URL inválida','e');return;} CONFIG.SITE_URL=url;localStorage.setItem('cp_site_url',url); UI.toast('URL salva!','s'); if(MinisterCtrl._curMinId)this.openPubLink(MinisterCtrl._curMinId); } }; /* ═══════════════════════════════════════════════ ENFERMO CTRL ═══════════════════════════════════════════════ */ const EnfermoCtrl={ _mid:null, openForMinister(mid){ this._mid=mid; const m=State.ministers.find(x=>x.id===mid); document.getElementById('enf-modal-title').textContent='Enfermos'; document.getElementById('enf-modal-sub').textContent=m?m.name:'—'; document.getElementById('enf-list-sec').style.display=''; document.getElementById('enf-form-sec').style.display='none'; this.renderList(); UI.openModal('ov-enfermos'); }, renderList(){ const mid=this._mid; const list=State.enfermos.filter(e=>e.minister_id===mid); document.getElementById('enf-cnt-badge').textContent=list.length; document.getElementById('enf-list-container').innerHTML=list.length?list.map(e=>{ const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0]; return`
${e.name} ${e.status==='inativo'?'Inativo':'Ativo'}
${e.address?'📍 '+e.address+(e.reference?' · '+e.reference:'')+'
':''} ${e.phone?'📞 '+e.phone+'
':''} 🕊 ${lv?'Última visita: '+Util.fmtD(lv.date)+'':'Sem visitas'}
${e.status!=='inativo' ?'' :''}
`; }).join(''):'

Nenhum enfermo vinculado.

'; }, showForm(){ sv('e-edit-id','');sv('e-name','');sv('e-address','');sv('e-ref','');sv('e-phone','');sv('e-birth','');sv('e-age','');sv('e-responsible','');sv('e-status','ativo'); const lim=checkLimite('enfermos'); if(!lim.ok){PlanUpgrade.showModal('enfermos');return;} document.getElementById('enf-form-title').textContent='Novo Enfermo'; document.getElementById('enf-list-sec').style.display='none'; document.getElementById('enf-form-sec').style.display=''; }, hideForm(){document.getElementById('enf-list-sec').style.display='';document.getElementById('enf-form-sec').style.display='none';}, editEnfermo(id){ const e=State.enfermos.find(x=>x.id===id);if(!e)return; sv('e-edit-id',e.id);sv('e-name',e.name);sv('e-address',e.address||'');sv('e-ref',e.reference||'');sv('e-phone',e.phone||'');sv('e-birth',e.birthdate||'');sv('e-age',Util.calcAge(e.birthdate));sv('e-responsible',e.responsible||'');sv('e-status',e.status||'ativo'); document.getElementById('enf-form-title').textContent='Editar Enfermo'; document.getElementById('enf-list-sec').style.display='none'; document.getElementById('enf-form-sec').style.display=''; }, async save(){ const name=gv('e-name');if(!name){UI.toast('Informe o nome','e');return;} const cid=State.clienteId,mid=this._mid; const editId=gv('e-edit-id'),ex=editId?State.enfermos.find(x=>x.id===editId):null; const rec={id:editId||Util.uid(),minister_id:mid,client_id:cid,name,address:gv('e-address'),reference:gv('e-ref'),phone:gv('e-phone'),birthdate:gv('e-birth'),responsible:gv('e-responsible'),status:gv('e-status'),created_at:ex?.created_at||Util.now()}; await Repo.saveEnfermo(cid,rec); State.enfermos=await Repo.getEnfermos(cid); this.hideForm();this.renderList(); UI.toast(editId?'Atualizado!':'Adicionado!','s'); }, openBaixa(id,ctx){ const e=State.enfermos.find(x=>x.id===id);if(!e)return; sv('baixa-id',e.id);sv('baixa-ctx',ctx||'admin');sv('baixa-motivo','');sv('baixa-obs',''); document.getElementById('baixa-nome').textContent=e.name; UI.openModal('ov-baixa'); }, async confirmBaixa(){ const id=gv('baixa-id'),motivo=gv('baixa-motivo'),obs=gv('baixa-obs'),ctx=gv('baixa-ctx'); if(!motivo){UI.toast('Selecione o motivo','e');return;} const cid=State.clienteId; const e=State.enfermos.find(x=>x.id===id);if(!e)return; const rec={...e,status:'inativo',motivoBaixa:motivo,obsBaixa:obs,dataBaixa:Util.today(),updated_at:Util.now()}; await Repo.saveEnfermo(cid,rec); State.enfermos=await Repo.getEnfermos(cid); UI.closeModal('ov-baixa'); if(ctx==='portal')MinisterPortal.renderEnfermos(); else this.renderList(); UI.toast('Baixa registrada: '+e.name,'w'); }, async reativar(id,ctx){ const cid=State.clienteId,e=State.enfermos.find(x=>x.id===id);if(!e)return; const rec={...e,status:'ativo',motivoBaixa:'',obsBaixa:'',dataBaixa:'',updated_at:Util.now()}; await Repo.saveEnfermo(cid,rec); State.enfermos=await Repo.getEnfermos(cid); if(ctx==='portal')MinisterPortal.renderEnfermos(); else this.renderList(); UI.toast('Enfermo reativado!','s'); }, populateFilter(){ const sel=document.getElementById('ae-fminister'); sel.innerHTML=''+State.ministers.map(m=>'').join(''); }, renderAll(){ const q=gv('ae-search').toLowerCase(),fm=gv('ae-fminister'); let list=State.enfermos; if(q)list=list.filter(e=>e.name.toLowerCase().includes(q)||(e.address||'').toLowerCase().includes(q)); if(fm)list=list.filter(e=>e.minister_id===fm); list=[...list].sort((a,b)=>a.name.localeCompare(b.name)); const mc=document.getElementById('enf-count');if(mc)mc.textContent=list.length+' enfermos'; document.getElementById('ae-tbl').innerHTML=list.length?list.map((e,i)=>{ const m=State.ministers.find(x=>x.id===e.minister_id); const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0]; return` ${i+1} ${e.name} ${m?m.name:'—'} ${e.phone||'—'} ${lv?Util.fmtD(lv.date):'Sem visitas'} ${e.status==='inativo'?'Inativo':'Ativo'}
${e.status!=='inativo'?'':''}
`; }).join(''):'

Nenhum enfermo encontrado.

'; document.getElementById('ae-cards').innerHTML=list.map(e=>{ const m=State.ministers.find(x=>x.id===e.minister_id); const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0]; return`
${e.name}
${e.status==='inativo'?'Inativo':'Ativo'}
${m?'👤 '+m.name:'—'}${e.phone?'
📞 '+e.phone:''}
🕊 ${lv?'Última: '+Util.fmtD(lv.date):'Sem visitas'}
${e.status!=='inativo'?'':''}
`; }).join(''); } }; /* ═══════════════════════════════════════════════ VISIT CTRL ═══════════════════════════════════════════════ */ const VisitCtrl={ populateSels(){ document.getElementById('v-minister').innerHTML=''+State.ministers.map(m=>'').join(''); document.getElementById('v-enfermo').innerHTML=''; document.getElementById('v-enfermo').disabled=true; }, loadEnfermos(){ const mid=gv('v-minister'),sel=document.getElementById('v-enfermo'); if(!mid){sel.innerHTML='';sel.disabled=true;return;} const list=State.enfermos.filter(e=>e.minister_id===mid&&e.status!=='inativo').sort((a,b)=>a.name.localeCompare(b.name)); sel.innerHTML=''+list.map(e=>'').join(''); sel.disabled=false; }, setToday(){const d=document.getElementById('v-date');if(d&&!d.value)d.value=Util.today();}, async save(){ const mid=gv('v-minister'),eid=gv('v-enfermo'),date=gv('v-date'); if(!mid){UI.toast('Selecione o ministro','e');return;} if(!eid){UI.toast('Selecione o enfermo','e');return;} if(!date){UI.toast('Informe a data','e');return;} const cid=State.clienteId; const visit={id:Util.uid(),client_id:cid,minister_id:mid,enfermo_id:eid,date,time:gv('v-time'),obs:gv('v-obs'),created_at:Util.now()}; try{ if(navigator.onLine){await Repo.saveVisit(cid,visit);State.visits=await Repo.getVisits(cid);UI.toast('Visita registrada!','s');} else{Sync.queue(cid,visit);UI.toast('Salvo offline — sincronizará ao conectar','w');} sv('v-minister','');sv('v-enfermo','');sv('v-time','');sv('v-obs',''); document.getElementById('v-enfermo').disabled=true; this.setToday();this.renderHistory(); }catch(e){UI.toast('Erro ao registrar visita','e');console.error(e);} }, renderHistory(){ const q=gv('vh-search').toLowerCase(),per=parseInt(gv('vh-period')||0); let list=[...State.visits]; if(q)list=list.filter(v=>{ const m=State.ministers.find(x=>x.id===v.minister_id),e=State.enfermos.find(x=>x.id===v.enfermo_id); return(m?.name||'').toLowerCase().includes(q)||(e?.name||'').toLowerCase().includes(q)||(v.obs||'').toLowerCase().includes(q); }); if(per){const cutoff=new Date();cutoff.setDate(cutoff.getDate()-per);list=list.filter(v=>new Date(v.date)>=cutoff);} list.sort((a,b)=>b.date.localeCompare(a.date)); const cont=document.getElementById('vh-container'),tot=document.getElementById('vh-total'); if(!list.length){cont.innerHTML='

Nenhuma visita encontrada.

';tot.textContent='';return;} cont.innerHTML=list.map(v=>{ const m=State.ministers.find(x=>x.id===v.minister_id),e=State.enfermos.find(x=>x.id===v.enfermo_id); return`
${v.date.split('-')[2]}
${Util.mAbbr(v.date)}
${e?e.name:'—'}
${m?m.name:'—'}${v.time?' · '+v.time:''}
${v.obs?'
'+v.obs+'
':''}
`; }).join(''); tot.textContent=list.length+' visita'+(list.length!==1?'s':''); }, async remove(id){ UI.confirm('Excluir visita','Deseja excluir este registro de visita?',async()=>{ await Repo.deleteVisit(State.clienteId,id); State.visits=await Repo.getVisits(State.clienteId); this.renderHistory();UI.toast('Visita removida','w'); }); } }; /* ═══════════════════════════════════════════════ WHITE LABEL CTRL ═══════════════════════════════════════════════ */ const WLCtrl={ async load(){ const cid=State.clienteId;if(!cid)return; const clients=State.clients.length?State.clients:await Repo.getClients(); State.clients=clients; const c=clients.find(x=>x.id===cid); const ip=c?.plan==='premium'; const wl=State.wl||await Repo.getWL(cid); State.wl=wl; const siteEl=document.getElementById('wl-site-url'); if(siteEl)siteEl.value=CONFIG.SITE_URL||''; sv('wl-name',wl.name||''); const colorEl=document.getElementById('wl-color');if(colorEl)colorEl.value=wl.color||'#2d8f77'; sv('wl-logo',wl.logo||''); this.preview(); const note=document.getElementById('wl-plan-note'); if(note)note.innerHTML=ip?'':'
White Label disponível apenas no Plano Premium. Fazer upgrade →
'; }, preview(){ const name=gv('wl-name')||'ConectaPro'; const color=(document.getElementById('wl-color')?.value)||'#2d8f77'; const dotEl=document.getElementById('wl-prev-dot'); const nameEl=document.getElementById('wl-prev-name'); if(dotEl)dotEl.style.background=color; if(nameEl)nameEl.textContent=name; }, previewSiteUrl(){ const url=gv('wl-site-url'); const st=document.getElementById('wl-site-url-status'); if(!url){st.textContent='';return;} if(!url.startsWith('https://')&&!url.startsWith('http://')){st.style.color='var(--err)';st.textContent='⚠ A URL deve começar com https://';return;} st.style.color='var(--ok)';st.textContent='✓ URL válida'; }, async save(){ const cid=State.clienteId;if(!cid)return; const clients=State.clients.length?State.clients:await Repo.getClients(); const c=clients.find(x=>x.id===cid); const ip=c?.plan==='premium'; // Salvar URL do site sempre const siteUrl=gv('wl-site-url'); if(siteUrl&&siteUrl.startsWith('http')){CONFIG.SITE_URL=siteUrl;localStorage.setItem('cp_site_url',siteUrl);} if(!ip){UI.toast('White Label disponível apenas no Premium','w');return;} const wl={name:gv('wl-name')||(c?.name||''),color:(document.getElementById('wl-color')?.value)||'#2d8f77',logo:gv('wl-logo')}; try{ await Repo.saveWL(cid,wl);State.wl=wl; document.getElementById('tb-name-txt').textContent=wl.name||c?.name||'ConectaPro'; document.documentElement.style.setProperty('--brand',wl.color); UI.toast('Configurações salvas!','s'); }catch(e){UI.toast('Erro ao salvar','e');} } }; /* ═══════════════════════════════════════════════ MINISTER PORTAL (role MINISTER logado) ═══════════════════════════════════════════════ */ const MinisterPortal={ async load(){ const mid=State.perfil?.ministerId;if(!mid)return; const m=State.ministers.find(x=>x.id===mid); const mpName=document.getElementById('mp-name'); if(mpName)mpName.textContent=m?(m.community||'')+(m.locality?' · '+m.locality:''):'—'; this.renderEnfermos(); }, renderEnfermos(){ const mid=State.perfil?.ministerId;if(!mid)return; const list=State.enfermos.filter(e=>e.minister_id===mid); const c=document.getElementById('portal-enf-list');if(!c)return; if(!list.length){c.innerHTML='

Nenhum enfermo cadastrado.

';return;} c.innerHTML=list.map(e=>{ const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0]; return`
${e.status==='inativo'?'Inativo':'Ativo'}
${e.name}
${e.address?'📍 '+e.address+(e.reference?' · '+e.reference:''):''} ${e.phone?'
📞 '+e.phone:''} ${e.birthdate?'
🎂 '+Util.fmtD(e.birthdate)+' · '+Util.calcAge(e.birthdate):''}
🕊 ${lv?'Última visita: '+Util.fmtD(lv.date)+'':'Sem visitas'}
${e.status!=='inativo' ?'' :''}
`; }).join(''); }, openEnfermoForm(){ ['pe-edit-id','pe-name','pe-address','pe-ref','pe-phone','pe-birth','pe-age','pe-responsible'].forEach(i=>sv(i,'')); sv('pe-status','ativo'); document.getElementById('pe-modal-title').textContent='Novo Enfermo'; UI.openModal('ov-portal-enf'); }, editEnfermo(id){ const e=State.enfermos.find(x=>x.id===id);if(!e)return; sv('pe-edit-id',e.id);sv('pe-name',e.name);sv('pe-address',e.address||'');sv('pe-ref',e.reference||'');sv('pe-phone',e.phone||'');sv('pe-birth',e.birthdate||'');sv('pe-age',Util.calcAge(e.birthdate));sv('pe-responsible',e.responsible||'');sv('pe-status',e.status||'ativo'); document.getElementById('pe-modal-title').textContent='Editar Enfermo'; UI.openModal('ov-portal-enf'); }, async saveEnfermo(){ const name=gv('pe-name');if(!name){UI.toast('Informe o nome','e');return;} const cid=State.clienteId,mid=State.perfil?.ministerId,editId=gv('pe-edit-id'); const ex=editId?State.enfermos.find(x=>x.id===editId):null; const rec={id:editId||Util.uid(),minister_id:mid,client_id:cid,name,address:gv('pe-address'),reference:gv('pe-ref'),phone:gv('pe-phone'),birthdate:gv('pe-birth'),responsible:gv('pe-responsible'),status:gv('pe-status'),created_at:ex?.created_at||Util.now()}; await Repo.saveEnfermo(cid,rec); State.enfermos=await Repo.getEnfermos(cid); UI.closeModal('ov-portal-enf'); this.renderEnfermos(); UI.toast(editId?'Atualizado!':'Adicionado!','s'); }, loadVisitSel(){ const mid=State.perfil?.ministerId;if(!mid)return; document.getElementById('mv-enfermo').innerHTML=''+State.enfermos.filter(e=>e.minister_id===mid&&e.status!=='inativo').sort((a,b)=>a.name.localeCompare(b.name)).map(e=>'').join(''); document.getElementById('mv-date').value=Util.today(); }, async saveVisit(){ const eid=gv('mv-enfermo'),date=gv('mv-date'); if(!eid){UI.toast('Selecione o enfermo','e');return;} if(!date){UI.toast('Informe a data','e');return;} const cid=State.clienteId,mid=State.perfil?.ministerId; const visit={id:Util.uid(),client_id:cid,minister_id:mid,enfermo_id:eid,date,time:gv('mv-time'),obs:gv('mv-obs'),created_at:Util.now()}; if(navigator.onLine){await Repo.saveVisit(cid,visit);State.visits=await Repo.getVisits(cid);UI.toast('Visita registrada!','s');} else{Sync.queue(cid,visit);UI.toast('Salvo offline — sincronizará ao conectar','w');} sv('mv-enfermo','');sv('mv-time','');sv('mv-obs',''); document.getElementById('mv-date').value=Util.today(); this.renderEnfermos(); } }; /* ═══════════════════════════════════════════════ PUBLIC CTRL (portal sem login, via ?m=UUID) ═══════════════════════════════════════════════ */ let _pubCid=null,_pubMid=null,_pubEnfs=[],_pubVis=[]; const PubCtrl={ async load(cid,mid){ _pubCid=cid;_pubMid=mid; const loadEl=document.getElementById('pub-loading'),errEl=document.getElementById('pub-err'),mainEl=document.getElementById('pub-main'); if(loadEl)loadEl.style.display='';if(errEl)errEl.style.display='none';if(mainEl)mainEl.style.display='none'; try{ const[clients,ministers]=await Promise.all([Repo.getClients(),Repo.getMinisters(cid)]); const client=clients.find(c=>c.id===cid),minister=ministers.find(m=>m.id===mid); if(!client||!minister){if(loadEl)loadEl.style.display='none';if(errEl)errEl.style.display='';return;} const wl=await Repo.getWL(cid); document.getElementById('pub-parish-nm').textContent=(client.plan==='premium'&&wl.name)?wl.name:client.name; document.getElementById('pub-min-name').textContent=minister.name; document.getElementById('pub-min-meta').textContent=[minister.community,minister.locality,minister.phone].filter(Boolean).join(' · '); document.getElementById('pub-avatar').textContent=minister.name.charAt(0).toUpperCase(); [_pubEnfs,_pubVis]=await Promise.all([Repo.getEnfermos(cid),Repo.getVisits(cid)]); this._populateSelects();this.renderEnfs();this.switchTab('visit'); const dateEl=document.getElementById('pub-date');if(dateEl)dateEl.value=Util.today(); this.updatePending();this.updateConnBadge(); if(loadEl)loadEl.style.display='none';if(mainEl)mainEl.style.display=''; }catch(err){console.error(err);if(loadEl)loadEl.style.display='none';if(errEl)errEl.style.display='';} }, _populateSelects(){ const ativos=_pubEnfs.filter(e=>e.minister_id===_pubMid&&e.status!=='inativo'); const opt0=''; const opts=ativos.map(e=>'').join(''); const sv=document.getElementById('pub-enf-sel'),sb=document.getElementById('pub-baixa-sel'); if(sv)sv.innerHTML=opt0+opts;if(sb)sb.innerHTML=opt0+opts; }, switchTab(tab){ ['visit','enf','baixa'].forEach(t=>{ document.getElementById('ptab-'+t)?.classList.toggle('active',t===tab); document.getElementById('ppanel-'+t)?.classList.toggle('active',t===tab); }); }, renderEnfs(){ const list=document.getElementById('pub-enf-list');if(!list)return; const enfs=_pubEnfs.filter(e=>e.minister_id===_pubMid&&e.status!=='inativo'); if(!enfs.length){list.innerHTML='
Nenhum enfermo ativo vinculado a este ministro.
';return;} list.innerHTML=enfs.map(e=>{ const visitas=_pubVis.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date)); const ultima=visitas[0]; return`
${e.name}
${e.address?'📍 '+e.address+(e.reference?' · '+e.reference:'')+'
':''} ${e.phone?'📞 '+e.phone+'
':''} ${e.birthdate?'🎂 '+Util.calcAge(e.birthdate)+'
':''} 🕊 ${ultima?'Última visita: '+Util.fmtD(ultima.date)+'':'Sem visitas registradas'}  ${visitas.length} visita${visitas.length!==1?'s':''}
`; }).join(''); }, async saveVisit(){ const eid=(document.getElementById('pub-enf-sel')||{}).value||''; const date=(document.getElementById('pub-date')||{}).value||''; const time=(document.getElementById('pub-time')||{}).value||''; const obs=(document.getElementById('pub-obs')||{}).value||''; if(!eid){UI.toast('Selecione o enfermo','e');return;} if(!date){UI.toast('Informe a data da visita','e');return;} const visit={id:Util.uid(),client_id:_pubCid,minister_id:_pubMid,enfermo_id:eid,date,time,obs,created_at:Util.now()}; try{ if(navigator.onLine){ const btnEl=document.querySelector('#ppanel-visit .btn-brand'); if(btnEl){btnEl.disabled=true;btnEl.textContent='Salvando...';} await Repo.saveVisit(_pubCid,visit); _pubVis=await Repo.getVisits(_pubCid); if(btnEl){btnEl.disabled=false;btnEl.innerHTML=' Registrar Visita';} UI.toast('✅ Visita registrada com sucesso!','s'); }else{Sync.queue(_pubCid,visit);UI.toast('📴 Salvo offline — sincronizará ao reconectar','w');this.updateConnBadge();} const selEl=document.getElementById('pub-enf-sel');if(selEl)selEl.value=''; const timeEl=document.getElementById('pub-time');if(timeEl)timeEl.value=''; const obsEl=document.getElementById('pub-obs');if(obsEl)obsEl.value=''; this.renderEnfs();this.updatePending(); }catch(err){UI.toast('Erro ao salvar visita. Tente novamente.','e');console.error(err);} }, async saveNewEnfermo(){ const name=(document.getElementById('pub-ne-name')||{}).value||''; const phone=(document.getElementById('pub-ne-phone')||{}).value||''; const erros=validarCampos({req:[['name','Nome do enfermo']],valores:{name:name.trim(),phone}}); if(erros.length){UI.toast(erros[0],'e');return;} const nomNorm=name.trim().toLowerCase(); const foneDigits=phone.replace(/\D/g,''); const jaExiste=_pubEnfs.some(e=>{ if(e.minister_id!==_pubMid)return false; const mesmNome=e.name.toLowerCase()===nomNorm; const eFone=(e.phone||'').replace(/\D/g,''); const mesmFone=foneDigits&&eFone&&eFone===foneDigits; return mesmNome||mesmFone; }); if(jaExiste){UI.toast('Este enfermo já está cadastrado','w');return;} const rec={id:Util.uid(),minister_id:_pubMid,client_id:_pubCid,name:name.trim(),address:(document.getElementById('pub-ne-address')||{}).value||'',reference:(document.getElementById('pub-ne-ref')||{}).value||'',phone,birthdate:(document.getElementById('pub-ne-birth')||{}).value||'',responsible:(document.getElementById('pub-ne-responsible')||{}).value||'',status:'ativo',created_at:Util.now()}; try{ await Repo.saveEnfermo(_pubCid,rec); _pubEnfs=await Repo.getEnfermos(_pubCid); this._populateSelects();this.renderEnfs(); ['pub-ne-name','pub-ne-address','pub-ne-ref','pub-ne-phone','pub-ne-birth','pub-ne-responsible'].forEach(id=>{const el=document.getElementById(id);if(el)el.value='';}); UI.toast(rec.name+' cadastrado com sucesso!','s'); this.switchTab('visit'); const selV=document.getElementById('pub-enf-sel');if(selV)selV.value=rec.id; }catch(err){UI.toast('Erro ao cadastrar enfermo.','e');console.error(err);} }, async saveBaixa(){ const eid=(document.getElementById('pub-baixa-sel')||{}).value||''; const motivo=(document.getElementById('pub-baixa-motivo')||{}).value||''; const obs=(document.getElementById('pub-baixa-obs')||{}).value||''; if(!eid){UI.toast('Selecione o enfermo','e');return;} if(!motivo){UI.toast('Selecione o motivo da baixa','e');return;} const enfermo=_pubEnfs.find(x=>x.id===eid);if(!enfermo){UI.toast('Enfermo não encontrado','e');return;} const atualizado={...enfermo,status:'inativo',motivoBaixa:motivo,obsBaixa:obs,dataBaixa:Util.today(),updated_at:Util.now()}; try{ await Repo.saveEnfermo(_pubCid,atualizado); _pubEnfs=await Repo.getEnfermos(_pubCid); this._populateSelects();this.renderEnfs(); const selEl=document.getElementById('pub-baixa-sel');if(selEl)selEl.value=''; const motEl=document.getElementById('pub-baixa-motivo');if(motEl)motEl.value=''; const obsEl=document.getElementById('pub-baixa-obs');if(obsEl)obsEl.value=''; UI.toast('Baixa registrada: '+enfermo.name,'w');this.switchTab('visit'); }catch(err){UI.toast('Erro ao dar baixa.','e');console.error(err);} }, updateConnBadge(){const badge=document.getElementById('pub-conn-badge');if(badge)badge.style.display=navigator.onLine?'none':'';}, updatePending(){ const pending=Sync.get().filter(q=>q.cid===_pubCid&&q.visit?.minister_id===_pubMid); const areaEl=document.getElementById('pub-pending-area'),cntEl=document.getElementById('pub-pending-cnt'); if(!areaEl)return; if(pending.length>0){if(cntEl)cntEl.textContent=pending.length+' visita'+(pending.length>1?'s':'')+' pendente'+(pending.length>1?'s':'');areaEl.style.display='';} else areaEl.style.display='none'; } }; /* ═══════════════════════════════════════════════ REPORTS ═══════════════════════════════════════════════ */ const Reports={ _hdr(title){const c=State.clients.find(x=>x.id===State.clienteId);const wl=State.wl;const name=(c&&c.plan==='premium'&&wl.name)?wl.name:'ConectaPro';return'
'+(c?c.name:'ConectaPro')+'

'+name+' · Ministros Extraordinários da Sagrada Comunhão

'+title+'
Emitido em
'+Util.nowStr()+'
';}, _foot:t=>'
ConectaPro · Gestão Pastoral'+(t||'')+'
', _rb:st=>{const m={ATIVO:'rba',PRÓXIMO:'rbw',VENCIDO:'rbe'};return''+st+'';}, print(){ const printContent=document.getElementById('print-area').innerHTML; const w=window.open('','_blank','width=900,height=700'); if(!w){UI.toast('Permita pop-ups para imprimir','w');return;} const css='@page{size:A4;margin:14mm}*{box-sizing:border-box;margin:0;padding:0}body{font-family:Georgia,serif;font-size:10.5pt;color:#0d1f1b;background:#fff}.rpt-hdr{display:flex;justify-content:space-between;padding-bottom:12px;border-bottom:2px solid #1a6b5a;margin-bottom:16px}.rl{font-size:7.5pt;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#1a6b5a;font-family:sans-serif}h1{font-size:13pt;color:#0d1f1b;margin:2px 0}.rs{font-size:9pt;color:#6b9087;font-family:sans-serif}.rd{text-align:right;font-size:8pt;color:#8aab9e;font-family:sans-serif}table{width:100%;border-collapse:collapse;margin-bottom:16px}thead th{background:#f0f8f5;padding:7px 10px;font-size:8pt;font-weight:700;text-align:left;text-transform:uppercase;letter-spacing:.06em;color:#1a6b5a;border-bottom:1.5px solid #1a6b5a}tbody td{padding:8px 10px;font-size:9.5pt;border-bottom:1px solid #e2eeeb}tbody tr:last-child td{border-bottom:none}.rbadge{font-size:7.5pt;font-weight:700;padding:2px 7px;border-radius:10px}.rba{background:#d1fae5;color:#065f46}.rbw{background:#fef3c7;color:#92400e}.rbe{background:#fee2e2;color:#991b1b}.rfooter{text-align:center;font-size:8pt;color:#9ab8b0;margin-top:24px;padding-top:12px;border-top:1px solid #e2eeeb;font-family:sans-serif}'; w.document.write(''+printContent+''); w.document.close();setTimeout(()=>{w.print();},600); }, selectMinister(){ const sel=document.getElementById('rpt-m-sel'); sel.innerHTML=State.ministers.map(m=>'').join(''); UI.openModal('ov-rpt-m'); }, doMinister(){const id=gv('rpt-m-sel');if(!id)return;UI.closeModal('ov-rpt-m');this._genMinister(id);}, _genMinister(mid){ const m=State.ministers.find(x=>x.id===mid);if(!m)return; const enfs=State.enfermos.filter(e=>e.minister_id===mid); const vis=State.visits.filter(v=>v.minister_id===mid).sort((a,b)=>b.date.localeCompare(a.date)); let html=this._hdr('Ficha do Ministro'); html+=`
CampoInformação
Nome${m.name}
Comunidade${m.community||'—'}${m.locality?' · '+m.locality:''}
Endereço${m.address||'—'}
Telefone${m.phone||'—'}
Investidura${Util.fmtD(m.investiture||'')}
Vencimento${Util.fmtD(m.expiry||'')} ${this._rb(Util.mStatus(m.expiry))}
Total de Enfermos${enfs.length}
Total de Visitas${vis.length}
`; if(enfs.length){ html+='

Enfermos

'; html+=` ${enfs.map(e=>'').join('')}
NomeEndereçoTelefoneStatus
'+e.name+''+(e.address||'—')+''+(e.phone||'—')+''+(e.status==='inativo'?'Inativo':'Ativo')+'
`; } if(vis.length){ html+='

Últimas Visitas

'; html+=` ${vis.slice(0,30).map(v=>{const e=State.enfermos.find(x=>x.id===v.enfermo_id);return'';}).join('')}
DataEnfermoObservações
'+Util.fmtD(v.date)+(v.time?' '+v.time:'')+''+(e?e.name:'—')+''+(v.obs||'')+'
`; } html+=this._foot('Ficha do Ministro'); document.getElementById('print-area').innerHTML=html; document.getElementById('rpt-title').textContent='Ficha: '+m.name; UI.openModal('ov-report'); }, allMinisters(){ let html=this._hdr('Lista de Ministros'); const sorted=[...State.ministers].sort((a,b)=>Util.commOrder(a.community)-Util.commOrder(b.community)||a.name.localeCompare(b.name)); html+=` ${sorted.map(m=>{const nEnf=State.enfermos.filter(e=>e.minister_id===m.id).length;const st=Util.mStatus(m.expiry);return'';}).join('')}
NomeComunidadeLocalidadeInvestiduraVencimentoStatusEnf.
'+m.name+''+(m.community||'—')+''+(m.locality||'—')+''+Util.fmtD(m.investiture||'')+''+Util.fmtD(m.expiry||'')+''+this._rb(st)+''+nEnf+'
`; html+=this._foot('Total: '+sorted.length+' ministros'); document.getElementById('print-area').innerHTML=html;document.getElementById('rpt-title').textContent='Todos os Ministros';UI.openModal('ov-report'); }, allEnfermos(){ let html=this._hdr('Lista de Enfermos'); const sorted=[...State.enfermos].filter(e=>e.status!=='inativo').sort((a,b)=>a.name.localeCompare(b.name)); html+=` ${sorted.map(e=>{const m=State.ministers.find(x=>x.id===e.minister_id);const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0];return'';}).join('')}
NomeMinistroTelefoneEndereçoÚltima Visita
'+e.name+''+(m?m.name:'—')+''+(e.phone||'—')+''+(e.address||'—')+''+(lv?Util.fmtD(lv.date):'Sem visitas')+'
`; html+=this._foot('Total: '+sorted.length+' enfermos ativos'); document.getElementById('print-area').innerHTML=html;document.getElementById('rpt-title').textContent='Todos os Enfermos';UI.openModal('ov-report'); } }; /* ═══════════════════════════════════════════════ EXPORTS (CSV) ═══════════════════════════════════════════════ */ const Exports={ _toCSV(rows,headers,keys){ const esc=v=>'"'+(String(v||'').replace(/"/g,'""'))+'"'; return[headers.map(esc).join(','),...rows.map(r=>keys.map(k=>esc(r[k])).join(','))].join('\n'); }, _download(name,csv){const a=document.createElement('a');a.href='data:text/csv;charset=utf-8,\uFEFF'+encodeURIComponent(csv);a.download=name;a.click();}, ministeresCSV(){ if(!State.ministers.length){UI.toast('Sem dados para exportar','w');return;} const p=getPlano();if(!p.csvExport){PlanUpgrade.showModal('csvExport');return;} const rows=State.ministers.map(m=>({nome:m.name,comunidade:m.community||'',localidade:m.locality||'',telefone:m.phone||'',endereco:m.address||'',investidura:Util.fmtD(m.investiture||''),vencimento:Util.fmtD(m.expiry||''),status:Util.mStatus(m.expiry),enfermos:State.enfermos.filter(e=>e.minister_id===m.id).length})); const csv=this._toCSV(rows,['Nome','Comunidade','Localidade','Telefone','Endereço','Investidura','Vencimento','Status','Enfermos'],['nome','comunidade','localidade','telefone','endereco','investidura','vencimento','status','enfermos']); const p2=(State.wl?.name||'MESC').replace(/[^a-z0-9]/gi,'_'); this._download('ministros_'+p2+'_'+Util.today()+'.csv',csv); UI.toast('Ministros exportados!','s'); }, enfermosCSV(){ if(!State.enfermos.length){UI.toast('Sem dados para exportar','w');return;} const p=getPlano();if(!p.csvExport){PlanUpgrade.showModal('csvExport');return;} const rows=State.enfermos.map(e=>{const m=State.ministers.find(x=>x.id===e.minister_id);const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0];return{nome:e.name,ministro:m?m.name:'',telefone:e.phone||'',endereco:e.address||'',referencia:e.reference||'',responsavel:e.responsible||'',nascimento:Util.fmtD(e.birthdate||''),status:e.status||'',ultima_visita:lv?Util.fmtD(lv.date):'',motivo_baixa:e.motivoBaixa||''};}); const csv=this._toCSV(rows,['Nome','Ministro','Telefone','Endereço','Referência','Responsável','Nascimento','Status','Última Visita','Motivo Baixa'],['nome','ministro','telefone','endereco','referencia','responsavel','nascimento','status','ultima_visita','motivo_baixa']); const p2=(State.wl?.name||'MESC').replace(/[^a-z0-9]/gi,'_'); this._download('enfermos_'+p2+'_'+Util.today()+'.csv',csv); UI.toast('Enfermos exportados!','s'); }, visitasCSV(){ if(!State.visits.length){UI.toast('Sem dados para exportar','w');return;} const p=getPlano();if(!p.csvExport){PlanUpgrade.showModal('csvExport');return;} const rows=State.visits.map(v=>{const m=State.ministers.find(x=>x.id===v.minister_id),e=State.enfermos.find(x=>x.id===v.enfermo_id);return{data:Util.fmtD(v.date),hora:v.time||'',ministro:m?m.name:'',comunidade:m?m.community||'':'',enfermo:e?e.name:'',endereco:e?e.address||'':'',obs:v.obs||'',registrado:v.created_at?v.created_at.split('T')[0]:''};}); const csv=this._toCSV(rows,['Data','Horário','Ministro','Comunidade','Enfermo','Endereço','Observações','Registrado em'],['data','hora','ministro','comunidade','enfermo','endereco','obs','registrado']); const p2=(State.wl?.name||'MESC').replace(/[^a-z0-9]/gi,'_'); this._download('visitas_'+p2+'_'+Util.today()+'.csv',csv); UI.toast('Visitas exportadas!','s'); }, clientesCSV(){ const list=State.clients;if(!list.length){UI.toast('Sem dados','w');return;} const rows=list.map(c=>({nome:c.name,paroco:c.paroco||'',contato:c.contact||'',email:c.email,plano:c.plan||'',vencimento:Util.fmtD(c.expiry||''),status:Util.cStatus(c),valor:c.value||''})); const csv=this._toCSV(rows,['Paróquia','Pároco','Coordenador','E-mail','Plano','Vencimento','Status','Valor'],['nome','paroco','contato','email','plano','vencimento','status','valor']); this._download('clientes_conectapro_'+Util.today()+'.csv',csv); UI.toast('Clientes exportados!','s'); }, tudo(){ if(!State.ministers.length&&!State.enfermos.length){UI.toast('Sem dados para exportar','w');return;} if(State.ministers.length)this.ministeresCSV(); setTimeout(()=>{if(State.enfermos.length)this.enfermosCSV();},400); setTimeout(()=>{if(State.visits.length)this.visitasCSV();},800); UI.toast('Exportação iniciada — 3 arquivos CSV','s'); } }; /* ═══════════════════════════════════════════════ ONBOARD CTRL ═══════════════════════════════════════════════ */ const OnboardCtrl={ _data:{}, goStep(n){ [1,2,3].forEach(i=>{ document.getElementById('ob-step-'+i)?.classList.toggle('active',i===n); const dot=document.getElementById('ob-dot-'+i); if(dot){dot.classList.toggle('active',i===n);dot.classList.toggle('done',i.
Você tem 14 dias grátis para explorar. Bem-vindo!'; this.goStep(3); }catch(err){UI.toast('Erro ao criar conta: '+(err.message||'Tente novamente'),'e');} finally{if(btn){btn.disabled=false;btn.textContent='Criar minha conta';}} }, async _createAccount(){ const{parish,contact,email,pw,plan}=this._data; const cid=Util.uid(); const trialEnd=new Date();trialEnd.setDate(trialEnd.getDate()+14); const expiry=trialEnd.toISOString().split('T')[0]; const newClient={id:cid,name:parish,paroco:'',contact,email,password:pw,plan,expiry,value:plan==='premium'?149:79,payment:'active',status:'active',trial:true,trialEnd:expiry,wl:{},created_at:Util.now()}; await Repo.saveClient(newClient); await Repo.log('onboard',email,{parish,plan}); State.user={uid:cid,id:cid,email}; State.perfil={role:'ADMIN',name:contact,clienteId:cid,ministerId:null}; State.clienteId=cid;State.clients=[newClient]; }, async enter(){ try{ await AppCtrl.loadData();await AppCtrl.applyWL(); UI.buildMobAdmin();UI.showScreen('app'); await AdminCtrl.render();UI.showPanel('admin-dash'); UI.toast('Bem-vindo ao ConectaPro! 🎉','s'); }catch(err){UI.showScreen('login');} } }; /* ═══════════════════════════════════════════════ PLAN UPGRADE ═══════════════════════════════════════════════ */ const PlanUpgrade={ _msgs:{ministros:'Você atingiu o limite de ministros do Plano Básico.',enfermos:'Você atingiu o limite de enfermos do Plano Básico.',csvExport:'Exportação CSV disponível apenas no Plano Premium.',qrCode:'QR Code disponível apenas no Plano Premium.'}, showModal(recurso){ const el=document.getElementById('upgrade-msg'); if(el)el.textContent=this._msgs[recurso]||'Este recurso está disponível apenas no Plano Premium.'; UI.openModal('ov-upgrade'); }, contact(){ const c=State.clients.find(x=>x.id===State.clienteId); const nome=c?.name||'minha paróquia'; const txt=encodeURIComponent('Olá! Gostaria de fazer upgrade para o Plano Premium do ConectaPro para '+nome+'.'); window.open('https://wa.me/5573991040043?text='+txt,'_blank'); UI.closeModal('ov-upgrade'); } }; /* ═══════════════════════════════════════════════ QR CTRL ═══════════════════════════════════════════════ */ const QRCtrl={ open(){UI.closeModal('ov-pub-link');UI.openModal('ov-qrcode');}, download(){ const canvas=document.querySelector('#qr-canvas-area canvas');if(!canvas){UI.toast('Gere o QR primeiro','w');return;} const a=document.createElement('a');a.download='qrcode-mesc.png';a.href=canvas.toDataURL('image/png');a.click(); }, print(){ const canvas=document.querySelector('#qr-canvas-area canvas');if(!canvas){UI.toast('Gere o QR primeiro','w');return;} const w=window.open('','_blank','width=400,height=500'); if(!w)return; const m=State.ministers.find(x=>x.id===MinisterCtrl._curMinId); w.document.write('

'+(m?.name||'Ministro')+'

Portal de Visitas · ConectaPro MESC

Escaneie para acessar o portal

'); w.document.close();setTimeout(()=>w.print(),500); } }; /* ═══════════════════════════════════════════════ ROUTER — URL ?m=UUID ou #minister/UUID ═══════════════════════════════════════════════ */ function getMinisterToken(){ try{const p=new URLSearchParams(window.location.search);const q=p.get('m');if(q&&q.length>10)return q.trim();}catch{} const h=window.location.hash||''; if(h.startsWith('#minister/')){const t=h.replace('#minister/','').trim();if(t.length>10)return t;} return null; } function buildMinisterLink(publicUUID){ if(CONFIG.SITE_URL&&CONFIG.SITE_URL.startsWith('http')){const base=CONFIG.SITE_URL.replace(/\?.*$/,'').replace(/#.*$/,'');return base+'?m='+publicUUID;} const loc=window.location;const isWeb=loc.protocol==='https:'||loc.protocol==='http:'; if(isWeb){return loc.protocol+'//'+loc.host+loc.pathname+'?m='+publicUUID;} return null; } function getSiteUrlStatus(){ if(CONFIG.SITE_URL&&CONFIG.SITE_URL.startsWith('http'))return'configured'; const p=window.location.protocol;if(p==='https:'||p==='http:')return'web';return'local'; } async function resolveMinisterToken(token){ const re=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; if(!re.test(token))return null; const clients=await Repo.getClients(); for(const c of clients){ const ministers=await Repo.getMinisters(c.id); const m=ministers.find(x=>x.public_uuid===token); if(m)return{cid:c.id,mid:m.id}; } return null; } /* ═══════════════════════════════════════════════ HELPERS ═══════════════════════════════════════════════ */ function togglePw(inputId,btn){ const inp=document.getElementById(inputId);if(!inp)return; const show=inp.type==='password'; inp.type=show?'text':'password'; btn.innerHTML=show ?'' :''; } function checkPwStrength(pw){ const bar=document.getElementById('pw-str-bar'),lbl=document.getElementById('pw-str-lbl'); if(!bar||!lbl)return; if(!pw){bar.style.width='0';lbl.textContent='';return;} let score=0; if(pw.length>=8)score++;if(pw.length>=12)score++; if(/[A-Z]/.test(pw))score++;if(/[0-9]/.test(pw))score++;if(/[^A-Za-z0-9]/.test(pw))score++; const levels=[{w:'15%',c:'var(--err)',t:'Muito fraca'},{w:'30%',c:'var(--err)',t:'Fraca'},{w:'55%',c:'var(--warn)',t:'Regular'},{w:'80%',c:'var(--ok)',t:'Boa'},{w:'100%',c:'var(--ok)',t:'Forte'}]; const l=levels[Math.min(score,4)]; bar.style.width=l.w;bar.style.background=l.c;lbl.style.color=l.c;lbl.textContent=l.t; } /* ═══════════════════════════════════════════════ BOOT ═══════════════════════════════════════════════ */ async function bootApp(){ initOffline(); // Tentar conectar ao Supabase let sb=null; if(SUPABASE_ANON_KEY&&!SUPABASE_ANON_KEY.startsWith('COLE')){ try{ sb=window.supabase.createClient(SUPABASE_URL,SUPABASE_ANON_KEY); // testar conexão await sb.from('mesc_clients').select('id').limit(1); }catch(e){console.warn('Supabase indisponível, usando modo demo',e);sb=null;} } Repo.init(sb); // Verificar portal público via URL const token=getMinisterToken(); if(token){ UI.showScreen('public'); const result=await resolveMinisterToken(token); if(result){await PubCtrl.load(result.cid,result.mid);} else{ const loadEl=document.getElementById('pub-loading'),errEl=document.getElementById('pub-err'); if(loadEl)loadEl.style.display='none';if(errEl)errEl.style.display=''; } } else { UI.showScreen('landing'); } // Remover loader const loader=document.getElementById('app-loader'); if(loader){loader.classList.add('out');setTimeout(()=>{loader.style.display='none';},500);} } // Iniciar document.addEventListener('DOMContentLoaded',bootApp); erros=validarCampos({req:[['name','Nome'],['locality','Localidade'],['community','Comunidade'],['investiture','Investidura'],['expiry','Vencimento']],valores:{name,locality,community,investiture,expiry}}); if(erros.length){UI.toast(erros[0],'e');return;} const lim=checkLimite('ministros');if(!lim.ok){PlanUpgrade.showModal('ministros');return;} const cid=State.clienteId,editId=gv('m-edit-id'); const ex=editId?State.ministers.find(m=>m.id===editId):null; const rec={id:editId||Util.uid(),client_id:cid,name,phone:gv('m-phone'),locality,community,address:gv('m-address'),investiture,expiry,public_uuid:ex?.public_uuid||Util.uuid(),created_at:ex?.created_at||Util.now()}; await Repo.saveMinister(cid,rec); State.ministers=await Repo.getMinisters(cid); UI.closeModal('ov-minister');this.render(); UI.toast(editId?'Ministro atualizado!':'Ministro cadastrado!','s'); }, render(){ const q=gv('m-search').toLowerCase(),fs=gv('m-fstatus'); let list=[...State.ministers].map(m=>({...m,_status:Util.mStatus(m.expiry)})); if(q)list=list.filter(m=>m.name.toLowerCase().includes(q)||(m.community||'').toLowerCase().includes(q)); if(fs)list=list.filter(m=>m._status===fs); list.sort((a,b)=>Util.commOrder(a.community)-Util.commOrder(b.community)||a.name.localeCompare(b.name)); const cnt=document.getElementById('minister-count');if(cnt)cnt.textContent=list.length+' ministro'+(list.length!==1?'s':''); document.getElementById('m-tbl').innerHTML=list.length?list.map(m=>{ const enfs=State.enfermos.filter(e=>e.minister_id===m.id&&e.status!=='inativo').length; return` ${m.name} ${m.community||'—'}
${m.locality||''} ${Util.fmtD(m.expiry)} ${UI.bM(m._status)} ${enfs}
`; }).join(''):'

Nenhum ministro encontrado.

'; document.getElementById('m-cards').innerHTML=list.map(m=>{ const enfs=State.enfermos.filter(e=>e.minister_id===m.id&&e.status!=='inativo').length; return`
${m.name}
${UI.bM(m._status)}
${m.community||'—'} · ${m.locality||''}
Vence: ${Util.fmtD(m.expiry)} · ${enfs} enfermo${enfs!==1?'s':''}
`; }).join(''); }, edit(id){const m=State.ministers.find(x=>x.id===id);if(!m)return;sv('m-edit-id',m.id);sv('m-name',m.name);sv('m-phone',m.phone||'');sv('m-locality',m.locality||'');const stdComms=['Matriz','São José','Nossa Senhora','Santo Antônio'];if(m.community&&!stdComms.includes(m.community)){sv('m-community','_custom');sv('m-comm-custom',m.community);document.getElementById('m-comm-grp').style.display='';}else{sv('m-community',m.community||'');document.getElementById('m-comm-grp').style.display='none';}sv('m-address',m.address||'');sv('m-investiture',m.investiture||'');sv('m-expiry',m.expiry||'');document.getElementById('minister-modal-title').textContent='Editar Ministro';UI.openModal('ov-minister');}, async remove(id,name){ UI.confirm('Excluir ministro','Deseja excluir '+name+'? Os enfermos vinculados também serão removidos.',async()=>{ await Repo.deleteMinister(State.clienteId,id); State.ministers=await Repo.getMinisters(State.clienteId); State.enfermos=await Repo.getEnfermos(State.clienteId); this.render();UI.toast('Ministro removido','w'); }); }, showLink(id){ const m=State.ministers.find(x=>x.id===id);if(!m)return; document.getElementById('pub-link-minister').textContent=m.name; const link=buildMinisterLink(m.public_uuid); const st=getSiteUrlStatus(); document.getElementById('pub-link-warning').style.display=st==='local'?'':'none'; document.getElementById('pub-link-url-row').style.display=st==='local'?'':'none'; document.getElementById('pub-link-display').textContent=link||'Configure a URL do site nas Configurações'; document.getElementById('pub-link-display').dataset.link=link||''; document.getElementById('btn-share').style.display=navigator.share?'':'none'; if(document.getElementById('site-url-input'))document.getElementById('site-url-input').value=CONFIG.SITE_URL||''; UI.openModal('ov-pub-link'); }, copyLink(){const l=document.getElementById('pub-link-display').dataset.link;if(!l){UI.toast('Nenhum link gerado — configure a URL do site','w');return;}navigator.clipboard.writeText(l).then(()=>UI.toast('Link copiado!','s')).catch(()=>UI.toast('Erro ao copiar','e'));}, shareLink(){const l=document.getElementById('pub-link-display').dataset.link;if(!l||!navigator.share)return;navigator.share({title:'Portal do Ministro',url:l}).catch(()=>{});}, saveSiteUrl(){const v=(document.getElementById('site-url-input')?.value||'').trim();if(!v.startsWith('http')){UI.toast('URL inválida — deve começar com https://','e');return;}CONFIG.SITE_URL=v;localStorage.setItem('cp_site_url',v);UI.toast('URL salva!','s');this.showLink(State.ministers.find(m=>document.getElementById('pub-link-minister').textContent===m.name)?.id||State.ministers[0]?.id);} }; /* ═══════════════════════════════════════════════ ENFERMO CTRL ═══════════════════════════════════════════════ */ const EnfermoCtrl={ _mid:null, open(mid){ this._mid=mid; const m=State.ministers.find(x=>x.id===mid); document.getElementById('enf-modal-title').textContent='Enfermos'; document.getElementById('enf-modal-sub').textContent=m?m.name:'—'; document.getElementById('enf-cnt-badge').textContent=State.enfermos.filter(e=>e.minister_id===mid).length; document.getElementById('enf-list-sec').style.display=''; document.getElementById('enf-form-sec').style.display='none'; this._renderList(); UI.openModal('ov-enfermos'); }, _renderList(){ const list=State.enfermos.filter(e=>e.minister_id===this._mid); document.getElementById('enf-list-container').innerHTML=list.length?list.map(e=>{ const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0]; return`
${e.name} ${e.status==='inativo'?'Inativo':'Ativo'}
${e.address?'📍 '+e.address+(e.reference?' · '+e.reference:'')+'
':''} ${e.phone?'📞 '+e.phone+'
':''} ${e.birthdate?'🎂 '+Util.calcAge(e.birthdate)+'
':''} 🕊 ${lv?'Última visita: '+Util.fmtD(lv.date)+'':'Sem visitas'}
${e.status!=='inativo' ?'' :''}
`; }).join(''):'

Nenhum enfermo cadastrado.

'; }, showForm(){sv('e-edit-id','');sv('e-name','');sv('e-address','');sv('e-ref','');sv('e-phone','');sv('e-birth','');sv('e-age','');sv('e-responsible','');sv('e-status','ativo');document.getElementById('enf-form-title').textContent='Novo Enfermo';document.getElementById('enf-list-sec').style.display='none';document.getElementById('enf-form-sec').style.display='';}, hideForm(){document.getElementById('enf-form-sec').style.display='none';document.getElementById('enf-list-sec').style.display='';}, editEnf(id){const e=State.enfermos.find(x=>x.id===id);if(!e)return;sv('e-edit-id',e.id);sv('e-name',e.name);sv('e-address',e.address||'');sv('e-ref',e.reference||'');sv('e-phone',e.phone||'');sv('e-birth',e.birthdate||'');sv('e-age',Util.calcAge(e.birthdate));sv('e-responsible',e.responsible||'');sv('e-status',e.status||'ativo');document.getElementById('enf-form-title').textContent='Editar Enfermo';document.getElementById('enf-list-sec').style.display='none';document.getElementById('enf-form-sec').style.display='';}, async save(){ const name=gv('e-name');if(!name){UI.toast('Informe o nome','e');return;} const lim=checkLimite('enfermos');const editId=gv('e-edit-id');if(!editId&&!lim.ok){PlanUpgrade.showModal('enfermos');return;} const cid=State.clienteId,ex=editId?State.enfermos.find(e=>e.id===editId):null; const rec={id:editId||Util.uid(),client_id:cid,minister_id:this._mid,name,address:gv('e-address'),reference:gv('e-ref'),phone:gv('e-phone'),birthdate:gv('e-birth'),responsible:gv('e-responsible'),status:gv('e-status'),created_at:ex?.created_at||Util.now()}; await Repo.saveEnfermo(cid,rec); State.enfermos=await Repo.getEnfermos(cid); document.getElementById('enf-cnt-badge').textContent=State.enfermos.filter(e=>e.minister_id===this._mid).length; this.hideForm();this._renderList(); MinisterCtrl.render(); UI.toast(editId?'Atualizado!':'Enfermo adicionado!','s'); }, async removeEnf(id,name){ UI.confirm('Excluir enfermo','Deseja excluir '+name+'?',async()=>{ await Repo.deleteEnfermo(State.clienteId,id); State.enfermos=await Repo.getEnfermos(State.clienteId); State.visits=await Repo.getVisits(State.clienteId); this._renderList();MinisterCtrl.render(); UI.toast('Enfermo removido','w'); }); }, openBaixa(id,ctx){ const e=State.enfermos.find(x=>x.id===id);if(!e)return; sv('baixa-id',id);sv('baixa-ctx',ctx);sv('baixa-motivo','');sv('baixa-obs',''); document.getElementById('baixa-nome').textContent=e.name; UI.openModal('ov-baixa'); }, async confirmBaixa(){ const id=gv('baixa-id'),motivo=gv('baixa-motivo'),obs=gv('baixa-obs'),ctx=gv('baixa-ctx'); if(!motivo){UI.toast('Selecione o motivo','e');return;} const e=State.enfermos.find(x=>x.id===id);if(!e)return; const upd={...e,status:'inativo',motivo_baixa:motivo,obs_baixa:obs,data_baixa:Util.today(),updated_at:Util.now()}; await Repo.saveEnfermo(State.clienteId,upd); State.enfermos=await Repo.getEnfermos(State.clienteId); UI.closeModal('ov-baixa'); if(ctx==='admin'){this._renderList();MinisterCtrl.render();} else if(ctx==='portal'){MinisterPortal.renderEnfermos();} UI.toast('Baixa registrada: '+e.name,'w'); }, async reativar(id,ctx){ const e=State.enfermos.find(x=>x.id===id);if(!e)return; const upd={...e,status:'ativo',motivo_baixa:null,obs_baixa:null,data_baixa:null}; await Repo.saveEnfermo(State.clienteId,upd); State.enfermos=await Repo.getEnfermos(State.clienteId); if(ctx==='admin'){this._renderList();} else if(ctx==='portal'){MinisterPortal.renderEnfermos();} UI.toast('Enfermo reativado!','s'); }, populateFilter(){ const sel=document.getElementById('ae-fminister'); sel.innerHTML=''+State.ministers.map(m=>'').join(''); }, renderAll(){ const q=gv('ae-search').toLowerCase(),fm=gv('ae-fminister'); let list=[...State.enfermos]; if(q)list=list.filter(e=>e.name.toLowerCase().includes(q)||(e.address||'').toLowerCase().includes(q)); if(fm)list=list.filter(e=>e.minister_id===fm); list.sort((a,b)=>a.name.localeCompare(b.name)); const cnt=document.getElementById('enf-count');if(cnt)cnt.textContent=list.length+' enfermo'+(list.length!==1?'s':''); document.getElementById('ae-tbl').innerHTML=list.map((e,i)=>{ const m=State.ministers.find(x=>x.id===e.minister_id); const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0]; return` ${i+1} ${e.name} ${m?m.name:'—'} ${e.phone||'—'} ${lv?Util.fmtD(lv.date):'Nunca'} ${e.status==='inativo'?'Inativo':'Ativo'}
${e.status!=='inativo'?'':''}
`; }).join(''); document.getElementById('ae-cards').innerHTML=list.map(e=>{ const m=State.ministers.find(x=>x.id===e.minister_id); const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0]; return`
${e.name}
${e.status==='inativo'?'Inativo':'Ativo'}
${m?m.name:'—'}
${lv?'Última visita: '+Util.fmtD(lv.date):'Sem visitas'}
${e.status!=='inativo'?'':''}
`; }).join(''); } }; /* ═══════════════════════════════════════════════ VISIT CTRL ═══════════════════════════════════════════════ */ const VisitCtrl={ populateSels(){ document.getElementById('v-minister').innerHTML=''+State.ministers.map(m=>'').join(''); document.getElementById('v-enfermo').innerHTML=''; document.getElementById('v-enfermo').disabled=true; }, loadEnfermos(){ const mid=gv('v-minister'); const sel=document.getElementById('v-enfermo'); sel.innerHTML=''+State.enfermos.filter(e=>e.minister_id===mid&&e.status!=='inativo').sort((a,b)=>a.name.localeCompare(b.name)).map(e=>'').join(''); sel.disabled=!mid; }, setToday(){const d=document.getElementById('v-date');if(d&&!d.value)d.value=Util.today();}, async save(){ const mid=gv('v-minister'),eid=gv('v-enfermo'),date=gv('v-date'); if(!mid){UI.toast('Selecione o ministro','e');return;} if(!eid){UI.toast('Selecione o enfermo','e');return;} if(!date){UI.toast('Informe a data','e');return;} const cid=State.clienteId; const visit={id:Util.uid(),client_id:cid,minister_id:mid,enfermo_id:eid,date,time:gv('v-time'),obs:gv('v-obs'),created_at:Util.now()}; await Repo.saveVisit(cid,visit); State.visits=await Repo.getVisits(cid); sv('v-minister','');sv('v-enfermo','');sv('v-time','');sv('v-obs',''); document.getElementById('v-enfermo').disabled=true; this.setToday();this.renderHistory(); UI.toast('Visita registrada!','s'); }, renderHistory(){ const q=gv('vh-search').toLowerCase(),per=gv('vh-period'); let list=[...State.visits]; if(q)list=list.filter(v=>{const m=State.ministers.find(x=>x.id===v.minister_id);const e=State.enfermos.find(x=>x.id===v.enfermo_id);return (m?.name||'').toLowerCase().includes(q)||(e?.name||'').toLowerCase().includes(q);}); if(per){const cutoff=new Date();cutoff.setDate(cutoff.getDate()-parseInt(per));list=list.filter(v=>new Date(v.date)>=cutoff);} const cont=document.getElementById('vh-container'); cont.innerHTML=list.length?list.map(v=>{ const m=State.ministers.find(x=>x.id===v.minister_id); const e=State.enfermos.find(x=>x.id===v.enfermo_id); return`
${v.date?v.date.split('-')[2]:'—'}
${v.date?Util.mAbbr(v.date):''}
${e?e.name:'Enfermo removido'}
${m?m.name:'—'}${v.time?' · '+v.time:''}
${v.obs?'
'+v.obs+'
':''}
`; }).join(''):'

Nenhuma visita encontrada.

'; const tot=document.getElementById('vh-total');if(tot)tot.textContent=list.length?list.length+' visita'+(list.length!==1?'s':''):''; }, async remove(id){ UI.confirm('Excluir visita','Deseja remover este registro de visita?',async()=>{ await Repo.deleteVisit(State.clienteId,id); State.visits=await Repo.getVisits(State.clienteId); this.renderHistory();UI.toast('Visita removida','w'); }); } }; /* ═══════════════════════════════════════════════ WHITE LABEL ═══════════════════════════════════════════════ */ const WLCtrl={ async load(){ const wl=await Repo.getWL(State.clienteId);State.wl=wl; sv('wl-name',wl.name||'');sv('wl-color',wl.color||'#2d8f77');sv('wl-logo',wl.logo||''); sv('wl-site-url',CONFIG.SITE_URL||''); const c=State.clients.find(x=>x.id===State.clienteId); const ip=c?.plan==='premium'; document.getElementById('wl-plan-note').innerHTML=ip?'':`
White Label disponível apenas no Plano Premium. Upgrade
`; const inp=document.querySelector('#panel-admin-settings .fi[id="wl-name"]');if(inp)inp.disabled=!ip; const colorInp=document.getElementById('wl-color');if(colorInp)colorInp.disabled=!ip; this.preview();this.previewSiteUrl(); }, preview(){ const name=gv('wl-name')||'ConectaPro',color=gv('wl-color')||'#2d8f77'; const pd=document.getElementById('wl-prev-dot');const pn=document.getElementById('wl-prev-name'); if(pd)pd.style.background=color;if(pn)pn.textContent=name; }, previewSiteUrl(){ const url=gv('wl-site-url'); const st=document.getElementById('wl-site-url-status'); if(!st)return; if(!url){st.textContent='';return;} if(url.startsWith('https://')){st.innerHTML='✓ URL válida (HTTPS)';} else if(url.startsWith('http://')){st.innerHTML='⚠ HTTP — use HTTPS para funcionar no celular';} else{st.innerHTML='✗ URL inválida';} }, async save(){ const c=State.clients.find(x=>x.id===State.clienteId); if(c?.plan!=='premium'){UI.toast('White Label disponível apenas no Premium','w');return;} const siteUrl=gv('wl-site-url'); if(siteUrl){CONFIG.SITE_URL=siteUrl;localStorage.setItem('cp_site_url',siteUrl);} const wl={name:gv('wl-name'),color:gv('wl-color')||'#2d8f77',logo:gv('wl-logo')}; await Repo.saveWL(State.clienteId,wl);State.wl=wl; document.documentElement.style.setProperty('--brand',wl.color); document.getElementById('tb-name-txt').textContent=wl.name||c?.name||'ConectaPro'; document.getElementById('site-url-alert').style.display=CONFIG.SITE_URL?'none':''; UI.toast('Configurações salvas!','s'); } }; /* ═══════════════════════════════════════════════ MINISTER PORTAL (logado) ═══════════════════════════════════════════════ */ const MinisterPortal={ async load(){ const mid=State.perfil?.ministerId;if(!mid)return; const m=State.ministers.find(x=>x.id===mid); const pn=document.getElementById('mp-name');if(pn)pn.textContent=m?m.name+' · '+m.community:'—'; }, renderEnfermos(){ const mid=State.perfil?.ministerId;if(!mid)return; const list=State.enfermos.filter(e=>e.minister_id===mid); const c=document.getElementById('portal-enf-list'); if(!list.length){c.innerHTML='

Nenhum enfermo cadastrado.

';return;} c.innerHTML=list.map(e=>{ const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0]; return`
${e.status==='inativo'?'Inativo':'Ativo'}
${e.name}
${e.address?'📍 '+e.address+(e.reference?' · '+e.reference:''):''} ${e.phone?'
📞 '+e.phone:''} ${e.birthdate?'
🎂 '+Util.fmtD(e.birthdate)+' · '+Util.calcAge(e.birthdate):''}
🕊 ${lv?'Última visita: '+Util.fmtD(lv.date)+'':'Sem visitas'}
${e.status!=='inativo'?'':''}
`; }).join(''); }, openEnfermoForm(){['pe-edit-id','pe-name','pe-address','pe-ref','pe-phone','pe-birth','pe-age','pe-responsible'].forEach(i=>sv(i,''));sv('pe-status','ativo');document.getElementById('pe-modal-title').textContent='Novo Enfermo';UI.openModal('ov-portal-enf');}, editEnfermo(id){const e=State.enfermos.find(x=>x.id===id);if(!e)return;sv('pe-edit-id',e.id);sv('pe-name',e.name);sv('pe-address',e.address||'');sv('pe-ref',e.reference||'');sv('pe-phone',e.phone||'');sv('pe-birth',e.birthdate||'');sv('pe-age',Util.calcAge(e.birthdate));sv('pe-responsible',e.responsible||'');sv('pe-status',e.status||'ativo');document.getElementById('pe-modal-title').textContent='Editar Enfermo';UI.openModal('ov-portal-enf');}, async saveEnfermo(){ const name=gv('pe-name');if(!name){UI.toast('Informe o nome','e');return;} const cid=State.clienteId,mid=State.perfil?.ministerId,editId=gv('pe-edit-id'),ex=editId?State.enfermos.find(x=>x.id===editId):null; const rec={id:editId||Util.uid(),client_id:cid,minister_id:mid,name,address:gv('pe-address'),reference:gv('pe-ref'),phone:gv('pe-phone'),birthdate:gv('pe-birth'),responsible:gv('pe-responsible'),status:gv('pe-status'),created_at:ex?.created_at||Util.now()}; await Repo.saveEnfermo(cid,rec);State.enfermos=await Repo.getEnfermos(cid); UI.closeModal('ov-portal-enf');this.renderEnfermos(); UI.toast(editId?'Atualizado!':'Adicionado!','s'); }, loadVisitSel(){ const mid=State.perfil?.ministerId;if(!mid)return; document.getElementById('mv-enfermo').innerHTML=''+State.enfermos.filter(e=>e.minister_id===mid&&e.status!=='inativo').sort((a,b)=>a.name.localeCompare(b.name)).map(e=>'').join(''); const d=document.getElementById('mv-date');if(d&&!d.value)d.value=Util.today(); }, async saveVisit(){ const eid=gv('mv-enfermo'),date=gv('mv-date'); if(!eid){UI.toast('Selecione o enfermo','e');return;}if(!date){UI.toast('Informe a data','e');return;} const cid=State.clienteId,mid=State.perfil?.ministerId; const visit={id:Util.uid(),client_id:cid,minister_id:mid,enfermo_id:eid,date,time:gv('mv-time'),obs:gv('mv-obs'),created_at:Util.now()}; if(navigator.onLine){await Repo.saveVisit(cid,visit);State.visits=await Repo.getVisits(cid);UI.toast('Visita registrada!','s');} else{Sync.queue(cid,visit);UI.toast('Salvo offline — sincronizará ao conectar','w');} sv('mv-enfermo','');sv('mv-time','');sv('mv-obs',''); const d=document.getElementById('mv-date');if(d)d.value=Util.today(); this.renderEnfermos(); } }; /* ═══════════════════════════════════════════════ PUBLIC PORTAL — sem login ═══════════════════════════════════════════════ */ let _pubCid=null,_pubMid=null,_pubEnfs=[],_pubVis=[]; const PubCtrl={ async load(cid,mid){ _pubCid=cid;_pubMid=mid; const loadEl=document.getElementById('pub-loading'),errEl=document.getElementById('pub-err'),mainEl=document.getElementById('pub-main'); if(loadEl)loadEl.style.display='';if(errEl)errEl.style.display='none';if(mainEl)mainEl.style.display='none'; try{ const[clients,ministers]=await Promise.all([Repo.getClients(),Repo.getMinisters(cid)]); const client=clients.find(c=>c.id===cid),minister=ministers.find(m=>m.id===mid); if(!client||!minister){if(loadEl)loadEl.style.display='none';if(errEl)errEl.style.display='';return;} const wl=await Repo.getWL(cid); document.getElementById('pub-parish-nm').textContent=(client.plan==='premium'&&wl.name)?wl.name:client.name; document.getElementById('pub-min-name').textContent=minister.name; document.getElementById('pub-min-meta').textContent=[minister.community,minister.locality,minister.phone].filter(Boolean).join(' · '); document.getElementById('pub-avatar').textContent=minister.name.charAt(0).toUpperCase(); [_pubEnfs,_pubVis]=await Promise.all([Repo.getEnfermos(cid),Repo.getVisits(cid)]); this._populateSelects();this.renderEnfs();this.switchTab('visit'); const dateEl=document.getElementById('pub-date');if(dateEl)dateEl.value=Util.today(); this.updatePending();this.updateConnBadge(); if(loadEl)loadEl.style.display='none';if(mainEl)mainEl.style.display=''; }catch(err){console.error(err);if(loadEl)loadEl.style.display='none';if(errEl)errEl.style.display='';} }, _populateSelects(){ const ativos=_pubEnfs.filter(e=>e.minister_id===_pubMid&&e.status!=='inativo'); const opt0=''; const opts=ativos.map(e=>'').join(''); const sv=document.getElementById('pub-enf-sel'),sb=document.getElementById('pub-baixa-sel'); if(sv)sv.innerHTML=opt0+opts;if(sb)sb.innerHTML=opt0+opts; }, switchTab(tab){ ['visit','enf','baixa'].forEach(t=>{ const tb=document.getElementById('ptab-'+t),pn=document.getElementById('ppanel-'+t); if(tb)tb.classList.toggle('active',t===tab);if(pn)pn.classList.toggle('active',t===tab); }); }, renderEnfs(){ const list=document.getElementById('pub-enf-list');if(!list)return; const enfs=_pubEnfs.filter(e=>e.minister_id===_pubMid&&e.status!=='inativo'); if(!enfs.length){list.innerHTML='
Nenhum enfermo ativo vinculado a este ministro.
';return;} list.innerHTML=enfs.map(e=>{ const visitas=_pubVis.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date)); const ultima=visitas[0]; return`
${e.name}
${e.address?'📍 '+e.address+(e.reference?' · '+e.reference:'')+'
':''} ${e.phone?'📞 '+e.phone+'
':''} ${e.birthdate?'🎂 '+Util.calcAge(e.birthdate)+'
':''} 🕊 ${ultima?'Última visita: '+Util.fmtD(ultima.date)+'':'Sem visitas'}  ${visitas.length} visita${visitas.length!==1?'s':''}
`; }).join(''); }, async saveVisit(){ const eid=(document.getElementById('pub-enf-sel')||{}).value||''; const date=(document.getElementById('pub-date')||{}).value||''; const time=(document.getElementById('pub-time')||{}).value||''; const obs=(document.getElementById('pub-obs')||{}).value||''; if(!eid){UI.toast('Selecione o enfermo','e');return;}if(!date){UI.toast('Informe a data da visita','e');return;} const visit={id:Util.uid(),client_id:_pubCid,minister_id:_pubMid,enfermo_id:eid,date,time,obs,created_at:Util.now()}; try{ const btnEl=document.querySelector('#ppanel-visit .btn-brand'); if(navigator.onLine){ if(btnEl){btnEl.disabled=true;btnEl.textContent='Salvando...';} await Repo.saveVisit(_pubCid,visit);_pubVis=await Repo.getVisits(_pubCid); if(btnEl){btnEl.disabled=false;btnEl.innerHTML=' Registrar Visita';} UI.toast('✅ Visita registrada com sucesso!','s'); }else{Sync.queue(_pubCid,visit);UI.toast('📴 Salvo offline — sincronizará ao reconectar','w');this.updateConnBadge();} const selEl=document.getElementById('pub-enf-sel'),timeEl=document.getElementById('pub-time'),obsEl=document.getElementById('pub-obs'); if(selEl)selEl.value='';if(timeEl)timeEl.value='';if(obsEl)obsEl.value=''; this.renderEnfs();this.updatePending(); }catch(err){UI.toast('Erro ao salvar visita.','e');console.error(err);} }, async saveNewEnfermo(){ const name=(document.getElementById('pub-ne-name')||{}).value||''; const phone=(document.getElementById('pub-ne-phone')||{}).value||''; const erros=validarCampos({req:[['name','Nome do enfermo']],valores:{name:name.trim(),phone}}); if(erros.length){UI.toast(erros[0],'e');return;} const nomNorm=name.trim().toLowerCase(),foneDigits=phone.replace(/\D/g,''); const jaExiste=_pubEnfs.some(e=>{if(e.minister_id!==_pubMid)return false;const mesmNome=e.name.toLowerCase()===nomNorm;const eFone=(e.phone||'').replace(/\D/g,'');return mesmNome||(foneDigits&&eFone&&eFone===foneDigits);}); if(jaExiste){UI.toast('Este enfermo já está cadastrado','w');return;} const rec={id:Util.uid(),client_id:_pubCid,minister_id:_pubMid,name:name.trim(),address:(document.getElementById('pub-ne-address')||{}).value||'',reference:(document.getElementById('pub-ne-ref')||{}).value||'',phone,birthdate:(document.getElementById('pub-ne-birth')||{}).value||'',responsible:(document.getElementById('pub-ne-responsible')||{}).value||'',status:'ativo',created_at:Util.now()}; try{ await Repo.saveEnfermo(_pubCid,rec);_pubEnfs=await Repo.getEnfermos(_pubCid); this._populateSelects();this.renderEnfs(); ['pub-ne-name','pub-ne-address','pub-ne-ref','pub-ne-phone','pub-ne-birth','pub-ne-responsible'].forEach(id=>{const el=document.getElementById(id);if(el)el.value='';}); UI.toast(rec.name+' cadastrado com sucesso!','s'); this.switchTab('visit'); const selV=document.getElementById('pub-enf-sel');if(selV)selV.value=rec.id; }catch(err){UI.toast('Erro ao cadastrar enfermo.','e');console.error(err);} }, async saveBaixa(){ const eid=(document.getElementById('pub-baixa-sel')||{}).value||''; const motivo=(document.getElementById('pub-baixa-motivo')||{}).value||''; const obs=(document.getElementById('pub-baixa-obs')||{}).value||''; if(!eid){UI.toast('Selecione o enfermo','e');return;}if(!motivo){UI.toast('Selecione o motivo','e');return;} const enfermo=_pubEnfs.find(x=>x.id===eid);if(!enfermo){UI.toast('Enfermo não encontrado','e');return;} const atualizado={...enfermo,status:'inativo',motivo_baixa:motivo,obs_baixa:obs,data_baixa:Util.today(),updated_at:Util.now()}; try{ await Repo.saveEnfermo(_pubCid,atualizado);_pubEnfs=await Repo.getEnfermos(_pubCid); this._populateSelects();this.renderEnfs(); const selEl=document.getElementById('pub-baixa-sel'),motEl=document.getElementById('pub-baixa-motivo'),obsEl=document.getElementById('pub-baixa-obs'); if(selEl)selEl.value='';if(motEl)motEl.value='';if(obsEl)obsEl.value=''; UI.toast('Baixa registrada: '+enfermo.name,'w');this.switchTab('visit'); }catch(err){UI.toast('Erro ao dar baixa.','e');console.error(err);} }, updateConnBadge(){const b=document.getElementById('pub-conn-badge');if(b)b.style.display=navigator.onLine?'none':'';}, updatePending(){ const pending=Sync.get().filter(q=>q.cid===_pubCid&&q.visit.minister_id===_pubMid); const areaEl=document.getElementById('pub-pending-area'),cntEl=document.getElementById('pub-pending-cnt'); if(!areaEl)return; if(pending.length>0){if(cntEl)cntEl.textContent=pending.length+' visita'+(pending.length>1?'s':'')+' pendente'+(pending.length>1?'s':'');areaEl.style.display='';} else{areaEl.style.display='none';} } }; /* ═══════════════════════════════════════════════ REPORTS ═══════════════════════════════════════════════ */ const Reports={ _hdr(title){const c=State.clients.find(x=>x.id===State.clienteId);const wl=State.wl;const name=(c&&c.plan==='premium'&&wl.name)?wl.name:'ConectaPro';return'
'+(c?c.name:'ConectaPro')+'

'+name+' · Ministros da Sagrada Comunhão

'+title+'
Emitido em
'+Util.nowStr()+'
';}, _foot:()=>'
ConectaPro · Gestão Pastoral
', print(){ const printContent=document.getElementById('print-area').innerHTML; const w=window.open('','_blank','width=900,height=700'); if(!w){UI.toast('Permita pop-ups para imprimir','w');return;} w.document.write('Relatório MESC'+printContent+''); w.document.close();setTimeout(()=>{w.print();},400); }, selectMinister(){ const sel=document.getElementById('rpt-m-sel'); sel.innerHTML=State.ministers.map(m=>'').join(''); UI.openModal('ov-rpt-m'); }, doMinister(){ const mid=gv('rpt-m-sel');if(!mid){UI.toast('Selecione o ministro','e');return;} UI.closeModal('ov-rpt-m');this.ministerPDF(mid); }, ministerPDF(mid){ const m=State.ministers.find(x=>x.id===mid);if(!m)return; const enfs=State.enfermos.filter(e=>e.minister_id===mid); let html=this._hdr('Ficha do Ministro — '+Util.nowStr()); html+=`

${m.name}

`; html+=`
Comunidade${m.community||'—'}Localidade${m.locality||'—'}
Telefone${m.phone||'—'}Investidura${Util.fmtD(m.investiture)}
Vencimento${Util.fmtD(m.expiry)}Status${Util.mStatus(m.expiry)}
`; html+=`

Enfermos (${enfs.length})

`; html+=``; enfs.forEach(e=>{const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0];html+=``;}); html+='
NomeEndereçoTelefoneÚltima Visita
${e.name}${e.address||'—'}${e.phone||'—'}${lv?Util.fmtD(lv.date):'—'}
'+this._foot(); document.getElementById('print-area').innerHTML=html; document.getElementById('rpt-title').textContent='Ficha: '+m.name; UI.openModal('ov-report'); }, allMinisters(){ let html=this._hdr('Todos os Ministros'); html+=``; const sorted=[...State.ministers].sort((a,b)=>Util.commOrder(a.community)-Util.commOrder(b.community)||a.name.localeCompare(b.name)); sorted.forEach(m=>{const n=State.enfermos.filter(e=>e.minister_id===m.id&&e.status!=='inativo').length;html+=``;}); html+='
NomeComunidadeInvestiduraVencimentoStatusEnfermos
${m.name}${m.community||'—'}${Util.fmtD(m.investiture)}${Util.fmtD(m.expiry)}${Util.mStatus(m.expiry)}${n}
'+this._foot(); document.getElementById('print-area').innerHTML=html; document.getElementById('rpt-title').textContent='Todos os Ministros'; UI.openModal('ov-report'); }, allEnfermos(){ let html=this._hdr('Todos os Enfermos'); html+=``; State.enfermos.slice().sort((a,b)=>a.name.localeCompare(b.name)).forEach(e=>{const m=State.ministers.find(x=>x.id===e.minister_id);const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0];html+=``;}); html+='
NomeMinistroEndereçoÚltima VisitaStatus
${e.name}${m?m.name:'—'}${e.address||'—'}${lv?Util.fmtD(lv.date):'—'}${e.status==='inativo'?'Inativo':'Ativo'}
'+this._foot(); document.getElementById('print-area').innerHTML=html;document.getElementById('rpt-title').textContent='Todos os Enfermos';UI.openModal('ov-report'); } }; /* ═══════════════════════════════════════════════ EXPORTS CSV ═══════════════════════════════════════════════ */ const Exports={ _toCSV(rows,headers,keys){const esc=v=>'"'+String(v===null||v===undefined?'':v).replace(/"/g,'""')+'"';return[headers.map(esc).join(','),...rows.map(r=>keys.map(k=>esc(r[k])).join(','))].join('\n');}, _download(name,csv){const blob=new Blob(['\ufeff'+csv],{type:'text/csv;charset=utf-8'});const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=name;a.click();URL.revokeObjectURL(url);}, ministeresCSV(){ const p=getPlano();if(!p.csvExport){PlanUpgrade.showModal('csvExport');return;} const rows=State.ministers.map(m=>({nome:m.name,comunidade:m.community||'',localidade:m.locality||'',telefone:m.phone||'',investidura:Util.fmtD(m.investiture),vencimento:Util.fmtD(m.expiry),status:Util.mStatus(m.expiry),enfermos:State.enfermos.filter(e=>e.minister_id===m.id&&e.status!=='inativo').length})); const csv=this._toCSV(rows,['Nome','Comunidade','Localidade','Telefone','Investidura','Vencimento','Status','Enfermos Ativos'],['nome','comunidade','localidade','telefone','investidura','vencimento','status','enfermos']); this._download('ministros_mesc_'+Util.today()+'.csv',csv);UI.toast('Ministros exportados!','s'); }, enfermosCSV(){ const p=getPlano();if(!p.csvExport){PlanUpgrade.showModal('csvExport');return;} const rows=State.enfermos.map(e=>{const m=State.ministers.find(x=>x.id===e.minister_id);const lv=State.visits.filter(v=>v.enfermo_id===e.id).sort((a,b)=>b.date.localeCompare(a.date))[0];return{nome:e.name,ministro:m?m.name:'',endereco:e.address||'',referencia:e.reference||'',telefone:e.phone||'',nascimento:Util.fmtD(e.birthdate),responsavel:e.responsible||'',status:e.status==='inativo'?'Inativo':'Ativo',ultima_visita:lv?Util.fmtD(lv.date):'—'};}); const csv=this._toCSV(rows,['Nome','Ministro','Endereço','Referência','Telefone','Nascimento','Responsável','Status','Última Visita'],['nome','ministro','endereco','referencia','telefone','nascimento','responsavel','status','ultima_visita']); this._download('enfermos_mesc_'+Util.today()+'.csv',csv);UI.toast('Enfermos exportados!','s'); }, visitasCSV(){ const p=getPlano();if(!p.csvExport){PlanUpgrade.showModal('csvExport');return;} const rows=State.visits.map(v=>{const m=State.ministers.find(x=>x.id===v.minister_id);const e=State.enfermos.find(x=>x.id===v.enfermo_id);return{data:Util.fmtD(v.date),hora:v.time||'',ministro:m?m.name:'',comunidade:m?m.community:'',enfermo:e?e.name:'',endereco:e?e.address||'':'',obs:v.obs||'',registrado:v.created_at?v.created_at.split('T')[0]:'—'};}); const csv=this._toCSV(rows,['Data','Horário','Ministro','Comunidade','Enfermo','Endereço','Observações','Registrado em'],['data','hora','ministro','comunidade','enfermo','endereco','obs','registrado']); this._download('visitas_mesc_'+Util.today()+'.csv',csv);UI.toast('Visitas exportadas!','s'); }, clientesCSV(){ const rows=State.clients.map(c=>({nome:c.name,paroco:c.paroco||'',contato:c.contact||'',email:c.email,plano:c.plan==='premium'?'Premium':'Básico',vencimento:Util.fmtD(c.expiry||''),valor:'R$ '+(c.value||0),status:Util.cStatus(c)})); const csv=this._toCSV(rows,['Paróquia','Pároco','Contato','E-mail','Plano','Vencimento','Valor','Status'],['nome','paroco','contato','email','plano','vencimento','valor','status']); this._download('clientes_conectapro_'+Util.today()+'.csv',csv);UI.toast('Clientes exportados!','s'); }, tudo(){ if(!State.ministers.length&&!State.enfermos.length){UI.toast('Sem dados para exportar','w');return;} if(State.ministers.length)this.ministeresCSV(); setTimeout(()=>{if(State.enfermos.length)this.enfermosCSV();},400); setTimeout(()=>{if(State.visits.length)this.visitasCSV();},800); UI.toast('Exportação iniciada — 3 arquivos CSV','s'); } }; /* ═══════════════════════════════════════════════ ONBOARD CTRL ═══════════════════════════════════════════════ */ const OnboardCtrl={ _data:{}, goStep(n){[1,2,3].forEach(i=>{const step=document.getElementById('ob-step-'+i),dot=document.getElementById('ob-dot-'+i);if(step)step.classList.toggle('active',i===n);if(dot){dot.classList.toggle('active',i===n);dot.classList.toggle('done',i.
Você tem 14 dias grátis para explorar. Bem-vindo!'; this.goStep(3); }catch(err){UI.toast('Erro ao criar conta: '+(err.message||'Tente novamente'),'e');} finally{if(btn){btn.disabled=false;btn.textContent='Criar minha conta';}} }, async _createAccount(){ const{parish,contact,email,pw,plan}=this._data; const cid=Util.uid(); const trialEnd=new Date();trialEnd.setDate(trialEnd.getDate()+14); const expiry=trialEnd.toISOString().split('T')[0]; const newClient={id:cid,name:parish,paroco:'',contact,email,password:pw,plan,expiry,value:plan==='premium'?149:79,payment:'active',status:'active',wl:{},trial:true,trial_end:expiry,created_at:Util.now()}; await Repo.saveClient(newClient); await Repo.log('onboard',email,{parish,plan}); State.user={uid:cid,id:cid,email}; State.perfil={role:'ADMIN',name:contact,clienteId:cid,ministerId:null}; State.clienteId=cid;State.clients=[newClient]; }, async enter(){ try{ await AppCtrl.loadData();await AppCtrl.applyWL(); UI.buildMobAdmin();UI.showScreen('app'); await AdminCtrl.render();UI.showPanel('admin-dash'); UI.toast('Bem-vindo ao ConectaPro! 🎉','s'); }catch(err){UI.showScreen('login');} } }; /* ═══════════════════════════════════════════════ PLAN UPGRADE ═══════════════════════════════════════════════ */ const PlanUpgrade={ _msgs:{ministros:'Você atingiu o limite de ministros do Plano Básico.',enfermos:'Você atingiu o limite de enfermos do Plano Básico.',csvExport:'Exportação CSV está disponível apenas no Plano Premium.',qrCode:'QR Code está disponível apenas no Plano Premium.'}, showModal(recurso){const msg=this._msgs[recurso]||'Este recurso está disponível apenas no Plano Premium.';const el=document.getElementById('upgrade-msg');if(el)el.textContent=msg;UI.openModal('ov-upgrade');}, contact(){const c=State.clients.find(x=>x.id===State.clienteId);const nome=c?.name||'minha paróquia';const txt=encodeURIComponent('Olá! Gostaria de fazer upgrade para o Plano Premium do ConectaPro para '+nome+'.');window.open('https://wa.me/5573991040043?text='+txt,'_blank');UI.closeModal('ov-upgrade');} }; /* ═══════════════════════════════════════════════ QR CODE ═══════════════════════════════════════════════ */ const QRCtrl={ _url:'',_min:'', open(){ const link=document.getElementById('pub-link-display')?.dataset.link||''; const minName=document.getElementById('pub-link-minister')?.textContent||''; if(!link){UI.toast('Nenhum link disponível','w');return;} this._url=link;this._min=minName; document.getElementById('qr-minister-name').textContent=minName; document.getElementById('qr-minister-sub').textContent='Portal do Ministro'; document.getElementById('qr-url-display').textContent=link; const area=document.getElementById('qr-canvas-area');area.innerHTML=''; try{new QRCode(area,{text:link,width:200,height:200,colorDark:'#0d1a16',colorLight:'#ffffff',correctLevel:QRCode.CorrectLevel.M});}catch(e){area.innerHTML='

Erro ao gerar QR Code

';} UI.openModal('ov-qrcode'); }, download(){ const canvas=document.querySelector('#qr-canvas-area canvas');if(!canvas){UI.toast('Gere o QR Code primeiro','w');return;} const a=document.createElement('a');a.download='qr_'+this._min.replace(/\s/g,'_')+'.png';a.href=canvas.toDataURL('image/png');a.click(); }, print(){ const canvas=document.querySelector('#qr-canvas-area canvas');if(!canvas)return; const w=window.open('','_blank');if(!w)return; w.document.write('QR Code - '+this._min+'

'+this._min+'

Portal do Ministro MESC · ConectaPro


'+this._url+'

'); w.document.close();setTimeout(()=>w.print(),300); } }; /* ═══════════════════════════════════════════════ ROUTER — ?m=UUID ou #minister/UUID ═══════════════════════════════════════════════ */ function getMinisterToken(){ try{const p=new URLSearchParams(window.location.search);const q=p.get('m');if(q&&q.length>10)return q.trim();}catch{} const hash=window.location.hash||''; if(hash.startsWith('#minister/')){const t=hash.replace('#minister/','').trim();if(t.length>10)return t;} return null; } function buildMinisterLink(publicUUID){ if(CONFIG.SITE_URL&&CONFIG.SITE_URL.startsWith('http')){const base=CONFIG.SITE_URL.replace(/\?.*$/,'').replace(/#.*$/,'');return base+'?m='+publicUUID;} const loc=window.location;const isWeb=loc.protocol==='https:'||loc.protocol==='http:'; if(isWeb){const base=loc.protocol+'//'+loc.host+loc.pathname;return base+'?m='+publicUUID;} return null; } function getSiteUrlStatus(){ if(CONFIG.SITE_URL&&CONFIG.SITE_URL.startsWith('http'))return'configured'; const p=window.location.protocol; if(p==='https:'||p==='http:')return'web'; return'local'; } async function resolveMinisterToken(token){ const re=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; if(!re.test(token))return null; const clients=await Repo.getClients(); for(const c of clients){ const ministers=await Repo.getMinisters(c.id); const m=ministers.find(x=>x.public_uuid===token); if(m)return{cid:c.id,mid:m.id}; } return null; } /* ═══════════════════════════════════════════════ HELPERS: togglePw, checkPwStrength ═══════════════════════════════════════════════ */ function togglePw(inputId,btn){ const inp=document.getElementById(inputId);if(!inp)return; const isText=inp.type==='text';inp.type=isText?'password':'text'; const svg=btn?.querySelector('svg');if(svg)svg.style.opacity=isText?'1':'.4'; } function checkPwStrength(pw){ const bar=document.getElementById('pw-str-bar'),lbl=document.getElementById('pw-str-lbl'); if(!bar||!lbl)return; let s=0,text='',color=''; if(pw.length>=8)s++;if(pw.length>=12)s++;if(/[A-Z]/.test(pw))s++;if(/[0-9]/.test(pw))s++;if(/[^A-Za-z0-9]/.test(pw))s++; if(!pw){bar.style.width='0';lbl.textContent='';return;} if(s<=1){text='Fraca';color='var(--err)';}else if(s<=2){text='Razoável';color='var(--warn)';}else if(s<=3){text='Boa';color='var(--brand)';}else{text='Forte';color='var(--ok)';} bar.style.width=(s/5*100)+'%';bar.style.background=color;lbl.style.color=color;lbl.textContent=text; } /* ═══════════════════════════════════════════════ BOOT ═══════════════════════════════════════════════ */ async function bootApp(){ initOffline(); // Inicializa Supabase se a chave foi configurada let sbClient=null; if(SUPABASE_ANON_KEY&&!SUPABASE_ANON_KEY.startsWith('COLE')){ try{sbClient=supabase.createClient(SUPABASE_URL,SUPABASE_ANON_KEY);}catch(e){console.warn('Supabase init failed:',e);} } Repo.init(sbClient); // Verifica link público const token=getMinisterToken(); if(token){ UI.showScreen('public'); const resolved=await resolveMinisterToken(token); if(resolved)await PubCtrl.load(resolved.cid,resolved.mid); else{const errEl=document.getElementById('pub-err'),loadEl=document.getElementById('pub-loading');if(loadEl)loadEl.style.display='none';if(errEl)errEl.style.display='';} } else { // Sessão ativa? if(sbClient){ const{data:{session}}=await sbClient.auth.getSession(); if(session){ const perfil=await Repo.getProfile(session.user.id); if(perfil){ State.user=session.user;State.perfil=perfil;State.clienteId=perfil.clienteId; const clients=await Repo.getClients();State.clients=clients; HubCtrl.show(); const loader=document.getElementById('app-loader');if(loader){loader.classList.add('out');setTimeout(()=>loader.style.display='none',400);} return; } } } UI.showScreen('landing'); } const loader=document.getElementById('app-loader'); if(loader){loader.classList.add('out');setTimeout(()=>loader.style.display='none',400);} } // Registrar service worker para PWA (opcional) if('serviceWorker' in navigator){ navigator.serviceWorker.register('/sw.js').catch(()=>{}); } window.addEventListener('load',bootApp);