// ══════════════════════════════════════════════════════════ // REDIL — Sistema de Gestión v5.1 // ══════════════════════════════════════════════════════════ var APP={user:null,mod:'',charts:{},hermanos:[],contactos:[],formUrl:'',formUrlPublic:'',config:{}}; const TIPO_REUNION=['Mixta (Reunión Regular)','Niños','Jóvenes','Mujeres','Profesionales']; const METODO_REPORTE=['Digital','Físico']; var SESSION_KEY='ir_sess_v51',SESSION_USER_KEY='ir_user_v51',SESSION_TIME_KEY='ir_time_v51'; var LOGO_KEY='ir_logo_v5',TOTALAPP_LOGO_KEY='ir_totalapp_logo_v5'; // ── DICCIONARIO DE TÍTULOS DE MÓDULOS ────────────────────── var TITLES = { dashboard: 'Dashboard General', reportes: 'Reporte de Grupos', reporteDigital: 'Reporte Digital', formulario: 'Formulario Externo', generador: 'Generador de Reportes', hermanos: 'Hermanos Líderes', cargaMasiva: 'Carga Masiva', supervisores: 'Supervisores de Sector', pastores: 'Pastores de Zona', ayudapastor: 'Ayuda de Pastor', seguimientos: 'Seguimientos', privilegios: 'Privilegios', diezmos: 'Control de Diezmos', gastos: 'Control de Gastos', inventario: 'Inventario', insumos: 'Insumos', envio: 'Centro de Envíos', contactos: 'Tabla de Contactos', usuarios: 'Usuarios', bitacora: 'Bitácora de Accesos', configuracion: 'Configuración', cronograma: 'Cronograma', bautizos: 'Bautizos en Agua' }; function saveSession(token,user,inactMin){[localStorage,sessionStorage].forEach(function(s){try{s.setItem(SESSION_KEY,token);s.setItem(SESSION_USER_KEY,JSON.stringify(user));s.setItem(SESSION_TIME_KEY,Date.now().toString());s.setItem('ir_inact_min',String(inactMin||60));}catch(e){}});} function getToken(){return localStorage.getItem(SESSION_KEY)||sessionStorage.getItem(SESSION_KEY);} function permisoEditar(){return APP.user&&APP.user.PuedeEditar!=='NO';} function clearSession(){var token=localStorage.getItem(SESSION_KEY)||sessionStorage.getItem(SESSION_KEY);if(token)try{call('destroySession',{token:token},function(){});}catch(e){}[SESSION_KEY,SESSION_USER_KEY,SESSION_TIME_KEY,'ir_inact_min'].forEach(function(k){try{localStorage.removeItem(k);sessionStorage.removeItem(k);}catch(e){}});} function getInactMin(){return parseInt(localStorage.getItem('ir_inact_min')||'60',10)||60;} function call(action,payload,cb){ payload=payload||{}; var token=localStorage.getItem(SESSION_KEY)||sessionStorage.getItem(SESSION_KEY); if(token && !payload.token) payload.token=token; try{ var url=window.location.origin+'/api/dispatch'; fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:action,payload:payload,token:token})}) .then(function(r){return r.json();}) .then(function(r){if(cb)cb(null,r);}) .catch(function(e){if(cb)cb(e,null);try{toast('Error: '+(e.message||e),'er');}catch(ex){}}); }catch(e){if(cb)cb(e,null);} } function g(id){var el=document.getElementById(id);return el?(el.value!==undefined?el.value:el.textContent):'';} function esc(s){return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,''');} function num(v){return parseFloat(String(v).replace(/[^0-9.\-]/g,''))||0;} function fmtQ(v){return 'Q '+Number(v||0).toLocaleString('es-GT',{minimumFractionDigits:2,maximumFractionDigits:2});} function fmtD(s){if(!s)return'--';try{var d=new Date(s+'T12:00:00');return d.toLocaleDateString('es-GT');}catch(e){return s;}} function fmtF(s){if(!s)return'';try{var d=new Date(s+'T12:00:00');return d.toLocaleDateString('es-GT');}catch(e){return s;}} function today(){var d=new Date();return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');} function todayStr(){return today();} function fom(){var d=new Date();return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-01';} function mesNombre(s){if(!s)return'';var m=['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'];try{return m[new Date(s+'T12:00:00').getMonth()];}catch(e){return s;}} function exportXLS(data,name,file){loading(true);call('exportExcel',{data:data,nombreHoja:name,nombreArchivo:file},function(_,res){loading(false);if(res&&res.ok){var url=res.xlsxUrl||res.url;window.open(url,'_blank');}else toast((res&&res.msg)||'Error al exportar','er');});} function toast(msg,type,ms){type=type||'in';ms=ms||3400;var w=document.getElementById('toastWrap');if(!w)return;var t=document.createElement('div');var icons={ok:'fa-check-circle',er:'fa-times-circle',wa:'fa-exclamation-triangle',in:'fa-info-circle'};var colors={ok:'#27ae60',er:'#e74c3c',wa:'#e8a020',in:'#2563a8'};t.style.cssText='display:flex;align-items:center;gap:10px;background:#fff;border-radius:10px;padding:12px 16px;margin-bottom:8px;box-shadow:0 4px 20px rgba(0,0,0,.12);border-left:4px solid '+colors[type]+';font-size:13px;max-width:380px';t.innerHTML=''+esc(msg)+'';w.appendChild(t);setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t);},ms);} function loading(s){var el=document.getElementById('gLoad');if(el)el.classList.toggle('show',!!s);} // ── DIÁLOGO PERSONALIZADO (reemplaza confirm() nativo) ──────── let _confirmCb=null; function customConfirm(msg,cb,opts){ opts=opts||{}; const title=opts.title||(APP&&APP.config&&APP.config.nombre?APP.config.nombre:'REDIL'); const icon=opts.icon||'fa-question-circle'; const color=opts.color||'var(--pr,#1a3a5c)'; let ov=document.getElementById('customConfirmOv'); if(!ov){ ov=document.createElement('div'); ov.id='customConfirmOv'; ov.style.cssText='display:none;position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.55);align-items:center;justify-content:center;'; ov.innerHTML=`

`; document.body.appendChild(ov); } _confirmCb=cb; document.getElementById('cfd_msg').textContent=msg; document.getElementById('cfd_title').textContent=title; const iWrap=document.getElementById('cfd_iconWrap'); if(iWrap){iWrap.style.background=opts.danger?'rgba(231,76,60,.12)':'rgba(26,58,92,.1)';iWrap.innerHTML=``;} ov.style.display='flex'; } function _resolveConfirm(val){ const ov=document.getElementById('customConfirmOv'); if(ov)ov.style.display='none'; if(_confirmCb){const cb=_confirmCb;_confirmCb=null;cb(val);} } function applyLogoToUI(url){if(!url)return;['loginLogoWrap','sidebarLogoWrap'].forEach(function(id){var el=document.getElementById(id);if(el)el.innerHTML='Logo';});} function applyBrandLogoToUI(url){if(!url)return;var el=document.getElementById('sidebarLogoWrap');if(el)el.innerHTML='Brand';} function applyTotalAppLogoToUI(url){if(!url)return;var el=document.getElementById('totalAppLogoWrap');if(el)el.innerHTML='Total App GT';} function initLogo(){var s=localStorage.getItem(LOGO_KEY);if(s)applyLogoToUI(s);var b=localStorage.getItem(TOTALAPP_LOGO_KEY);if(b)applyTotalAppLogoToUI(b);} function uploadLogo(){openModal('Cambiar Logo','
'+(localStorage.getItem(LOGO_KEY)?'':'')+'

Clic para subir logo

','');} function previewLg(input){var file=input.files[0];if(!file)return;var reader=new FileReader();reader.onload=function(e){window._pendLogo=e.target.result;var p=document.getElementById('lgPrev');if(p)p.innerHTML='';};reader.readAsDataURL(file);} function saveLogo(){if(!window._pendLogo){closeModal();return;}localStorage.setItem(LOGO_KEY,window._pendLogo);applyLogoToUI(window._pendLogo);call('saveConfig',{logo_url:window._pendLogo},function(_,r){if(!r||!r.ok)toast('Logo local, no se pudo guardar en servidor','wa');});window._pendLogo=null;closeModal();toast('Logo actualizado','ok');} function clearLogo(){localStorage.removeItem(LOGO_KEY);['loginLogoWrap','sidebarLogoWrap'].forEach(function(id){var el=document.getElementById(id);if(el)el.innerHTML='';});call('saveConfig',{logo_url:''},function(_,r){});closeModal();toast('Logo removido','wa');} function uploadBrandLogo(){openModal('Logo Empresa','

Subir logo empresa

','');} function previewBrandLg(input){var file=input.files[0];if(!file)return;var reader=new FileReader();reader.onload=function(e){window._pendBrandLogo=e.target.result;var p=document.getElementById('bLgPreview');if(p)p.innerHTML='';};reader.readAsDataURL(file);} function saveBrandLogo(){if(!window._pendBrandLogo){closeModal();return;}localStorage.setItem(TOTALAPP_LOGO_KEY,window._pendBrandLogo);applyTotalAppLogoToUI(window._pendBrandLogo);window._pendBrandLogo=null;closeModal();toast('Logo actualizado','ok');} function clearBrandLogo(){localStorage.removeItem(TOTALAPP_LOGO_KEY);var el=document.getElementById('totalAppLogoWrap');if(el)el.innerHTML='Total App GT';closeModal();toast('Logo removido','wa');} function openModal(title,body,foot,size){var ov=document.getElementById('modalOv'),box=document.getElementById('modalBox'),tEl=document.getElementById('modalTitle'),bEl=document.getElementById('modalBody'),fEl=document.getElementById('modalFoot');if(tEl)tEl.textContent=title;if(bEl)bEl.innerHTML=body||'';if(fEl)fEl.innerHTML=foot||'';if(box)box.style.maxWidth=size==='xl'?'920px':'';if(ov)ov.classList.add('open');} function closeModal(){var ov=document.getElementById('modalOv');if(ov)ov.classList.remove('open');} function onOvClick(e){if(e.target===document.getElementById('modalOv'))closeModal();} function openSidebar(){var s=document.getElementById('sidebar'),o=document.getElementById('sbOv');if(s)s.classList.add('open');if(o)o.classList.add('show');} function closeSidebar(){var s=document.getElementById('sidebar'),o=document.getElementById('sbOv');if(s)s.classList.remove('open');if(o)o.classList.remove('show');} function togglePw(){var i=document.getElementById('loginPass'),e=document.getElementById('eyeIco');if(!i||!e)return;i.type=(i.type==='password')?'text':'password';e.className=(i.type==='password')?'fas fa-eye':'fas fa-eye-slash';} function aplicarNombreSistema(nombre){ if(!nombre)return; var lt=document.getElementById('loginTitle');if(lt)lt.textContent=nombre; var at=document.getElementById('sbAppTitle'),sub=document.getElementById('sbAppSub'); if(at){ var parts=nombre.split(' '); if(parts.length>=2){ at.innerHTML=''+esc(parts[0])+''+esc(parts.slice(1).join(' '))+''; } else { at.innerHTML=''+esc(nombre)+''; } } document.title=nombre+' - REDIL'; if(!APP.config)APP.config={};APP.config.nombre=nombre; } var _actTimer=null, _actListenersSet=false; function resetActivityTimer(){if(_actTimer)clearTimeout(_actTimer);if(!APP.user)return;_actTimer=setTimeout(function(){logout(true);},getInactMin()*60*1000);} function setupActivityTracking(){if(_actListenersSet)return;_actListenersSet=true;['click','keydown','mousemove','touchstart','scroll'].forEach(function(ev){document.addEventListener(ev,resetActivityTimer,{passive:true});});} setupActivityTracking(); function handleLogin(e){ if(e)e.preventDefault(); var _sh=document.getElementById('appShell');if(_sh)_sh.style.display='none'; var emailEl=document.getElementById('loginEmail'),passEl=document.getElementById('loginPass'); var errEl=document.getElementById('loginErr'),txtEl=document.getElementById('loginTxt'),loadEl=document.getElementById('loginLoad'); var email=emailEl?emailEl.value.trim():'',pass=passEl?passEl.value:''; if(!email||!pass){if(errEl)errEl.textContent='Ingresa correo y contrasena';return;} if(txtEl)txtEl.style.display='none'; if(loadEl)loadEl.style.display='inline'; if(errEl)errEl.textContent=''; call('login',{email:email,password:pass},function(err,res){ if(txtEl)txtEl.style.display='inline'; if(loadEl)loadEl.style.display='none'; if(err){if(errEl)errEl.textContent='Error: '+(err.message||String(err));return;} if(!res||!res.ok){if(errEl)errEl.textContent=(res&&res.msg)||'Credenciales incorrectas';return;} APP.user=res.user; saveSession(res.token,res.user,(res.user&&res.user.inactMin)||60); // Registrar acceso en bitácora try{ call('registrarAcceso',{ usuario:res.user.nombre||res.user.email, email:res.user.email, rol:res.user.isPropietario?'Propietario':(res.user.rol||'Usuario'), accion:'Login', ip:'', detalles:navigator.userAgent.substring(0,120) },function(){}); }catch(e){} try{localStorage.removeItem('ir_last_mod');}catch(e){} mostrarApp(res.user); }); } function mostrarApp(user){ var loginEl=document.getElementById('loginScreen'); if(loginEl){loginEl.classList.remove('active');loginEl.style.display='none';} var shellEl=document.getElementById('appShell'); if(shellEl){shellEl.style.display='flex';} var sbN=document.getElementById('sbName'),sbR=document.getElementById('sbRol'),tbU=document.getElementById('tbUser'); if(sbN)sbN.textContent=(user&&user.nombre)||'Usuario'; if(sbR)sbR.textContent=(user&&user.isPropietario)?'Propietario':((user&&user.rol)||''); if(tbU)tbU.textContent=(user&&user.nombre)||'Usuario'; try{buildSidebarMenu((user&&user.menu)||[],!!(user&&user.isPropietario));}catch(e){console.error('menu:',e);} call('getConfig',{},function(_,cfg){if(!cfg||!cfg.ok)return;APP.config=cfg;APP.formUrl=cfg.formUrl||'';APP.formUrlPublic=cfg.formUrlPublic||''; if(cfg.nombre){aplicarNombreSistema(cfg.nombre);} if(cfg.logo_url||cfg.logoUrl){ var _lu=cfg.logo_url||cfg.logoUrl; var _imgT=''; var _lw=document.getElementById('loginLogoWrap');if(_lw)_lw.innerHTML=_imgT; var _sw=document.getElementById('sidebarLogoWrap');if(_sw)_sw.innerHTML=_imgT; }if(cfg.nombre)try{if(window.aplicarNombreSistema)aplicarNombreSistema(cfg.nombre);}catch(e){}try{if(cfg.menuConfig)initMenuState(cfg.menuConfig);}catch(e){}try{applyGlobalMenuConfig(cfg.menuConfig,user&&user.isPropietario);}catch(e){}aplicarTema(cfg);}); call('getHermanos',{},function(_,h){APP.hermanos=Array.isArray(h)?h:[];}); call('getContactos',{},function(_,c){APP.contactos=Array.isArray(c)?c:[];}); setTimeout(function(){ try{var hm=(window.location.hash||'').replace('#','').trim();var lm='';try{lm=localStorage.getItem('ir_last_mod')||'';}catch(ex){} var tgt=hm||lm||'dashboard';var menu=(user&&user.menu)||[]; if(RENDERS[tgt]&&(tgt==='dashboard'||menu.indexOf(tgt)>=0))nav(tgt,true);else nav('dashboard',true); }catch(ex){nav('dashboard',true);} },300); if(!window._hlSet){window._hlSet=true;window.addEventListener('hashchange',function(){var h=(window.location.hash||'').replace('#','').trim();if(h&&RENDERS[h]&&APP.mod!==h)nav(h,true);});} resetActivityTimer(); setTimeout(initAIBot, 800); } function showApp(user){mostrarApp(user);} function iniciarApp(user){mostrarApp(user);} function logout(auto){ if(!auto){ customConfirm('¿Desea cerrar la sesión actual?',function(ok){if(ok)_doLogout(false);},{icon:'fa-sign-out-alt'}); return; } _doLogout(true); } function _doLogout(auto){ if(_actTimer)clearTimeout(_actTimer); clearSession();APP.user=null; var shell=document.getElementById('appShell'),login=document.getElementById('loginScreen'),cnt=document.getElementById('content'); if(shell)shell.style.display='none'; if(login){login.style.display='flex';login.classList.add('active');} try{document.querySelectorAll('.mod').forEach(function(m){m.parentNode.removeChild(m);});}catch(ex){} if(cnt)cnt.innerHTML=''; APP.mod='';APP.charts={}; try{if(window.history&&window.history.replaceState)window.history.replaceState(null,'',window.location.pathname);}catch(ex){} if(auto)toast('Sesion expirada. Vuelve a ingresar.','wa',4000); } // A. ELIMINAR PARPADEO: Oculta el login de forma inmediata si hay sesión activa en la caché function tryRestoreSession(){ var token=localStorage.getItem(SESSION_KEY)||sessionStorage.getItem(SESSION_KEY); var userRaw=localStorage.getItem(SESSION_USER_KEY)||sessionStorage.getItem(SESSION_USER_KEY); var lastTime=parseInt(localStorage.getItem(SESSION_TIME_KEY)||'0'); if(!token||!userRaw)return false; if(Date.now()-lastTime>getInactMin()*60*1000){clearSession();return false;} // Ocultamos la tarjeta de login instantáneamente antes de que parpadee var s=document.getElementById('appShell'), l=document.getElementById('loginScreen'); if(s) s.style.display='flex'; if(l){ l.style.display='none'; l.classList.remove('active'); } try{var user=JSON.parse(userRaw); call('validateSession',{token:token},function(err,res){ if(err||!res||!res.ok){ clearSession(); if(s) s.style.display='none'; if(l){ l.style.display='flex'; l.classList.add('active'); } return; } localStorage.setItem(SESSION_TIME_KEY,Date.now().toString());localStorage.setItem(SESSION_USER_KEY,JSON.stringify(res.user));APP.user=res.user;mostrarApp(res.user); });return true; }catch(ex){clearSession();return false;} } function nav(name,forceRefresh){ if(!name)name='dashboard'; if(!RENDERS[name]){if(RENDERS['dashboard'])name='dashboard';else return;} APP.mod=name; try{localStorage.setItem('ir_last_mod',name);if(window.history&&window.history.replaceState)window.history.replaceState(null,'','#'+name);}catch(ex){} var tEl=document.getElementById('tbTitle');if(tEl)tEl.textContent=TITLES[name]||name; document.querySelectorAll('.ni').forEach(function(el){el.classList.toggle('active',el.dataset.mod===name);}); if(window.innerWidth<=880)closeSidebar(); var cnt=document.getElementById('content');if(!cnt)return; if(forceRefresh){var od=document.getElementById('mod_'+name);if(od)od.parentNode.removeChild(od);} var el=document.getElementById('mod_'+name);var isNew=!el; if(isNew){ el=document.createElement('div');el.id='mod_'+name;el.className='mod'; el.innerHTML='
'; cnt.appendChild(el); } document.querySelectorAll('.mod').forEach(function(m){m.classList.remove('active');}); el.classList.add('active'); if(isNew){requestAnimationFrame(function(){requestAnimationFrame(function(){ try{RENDERS[name](el);} catch(err){el.innerHTML='

Error: '+err.message+'

';console.error('[nav]',name,err);} });});} } function reloadMod(name){ var el=document.getElementById('mod_'+name); if(el){el.innerHTML='';if(RENDERS&&RENDERS[name])try{RENDERS[name](el);}catch(e){el.innerHTML='
Error: '+e.message+'
';}} } // ── MENÚ ────────────────────────────────────────────────── var ALL_MENU=[ {id:'dashboard',label:'Dashboard',icon:'fa-tachometer-alt',section:'PRINCIPAL'}, {id:'reportes',label:'Reporte de Grupos',icon:'fa-file-alt',section:'REPORTES'}, {id:'reporteDigital',label:'Reporte Digital',icon:'fa-mobile-alt',section:'REPORTES'}, {id:'formulario',label:'Formulario Externo',icon:'fa-clipboard-list',section:'REPORTES'}, {id:'generador',label:'Generador Reportes',icon:'fa-file-invoice',section:'REPORTES'}, {id:'hermanos',label:'Hermanos Lideres',icon:'fa-user-tie',section:'GESTION'}, {id:'cargaMasiva',label:'Carga Masiva',icon:'fa-file-upload',section:'GESTION'}, {id:'supervisores',label:'Supervisores de Sector',icon:'fa-user-shield',section:'GESTION'}, {id:'pastores',label:'Pastores de Zona',icon:'fa-church',section:'GESTION'}, {id:'ayudapastor',label:'Ayuda de Pastor',icon:'fa-hands-helping',section:'GESTION'}, {id:'seguimientos',label:'Seguimientos',icon:'fa-hands-helping',section:'GESTION'}, {id:'privilegios',label:'Privilegios',icon:'fa-crown',section:'GESTION'}, {id:'diezmos',label:'Control Diezmos',icon:'fa-coins',section:'FINANZAS'}, {id:'gastos',label:'Control de Gastos',icon:'fa-receipt',section:'FINANZAS'}, {id:'inventario',label:'Inventario',icon:'fa-boxes',section:'RECURSOS'}, {id:'insumos',label:'Insumos',icon:'fa-spray-can',section:'RECURSOS'}, {id:'envio',label:'Centro de Envios',icon:'fa-paper-plane',section:'COMUNICACIONES'}, {id:'contactos',label:'Tabla de Contactos',icon:'fa-address-book',section:'COMUNICACIONES'}, {id:'bautizos',label:'Bautizos en Agua',icon:'fa-water',section:'GESTION'}, {id:'cuadre',label:'Cuadre Dominical',icon:'fa-calculator',section:'FINANZAS'}, {id:'usuarios',label:'Usuarios',icon:'fa-user-cog',section:'ADMIN'}, {id:'bitacora',label:'Bitacora Accesos',icon:'fa-clipboard-list',section:'ADMIN'}, {id:'configuracion',label:'Configuracion',icon:'fa-cog',section:'ADMIN'}, ]; // 2. CORRECCIÓN DEL MENÚ: Evita que los módulos se oculten por arrays vacíos function buildSidebarMenu(allowedIds,isPropietario){ const nav=document.getElementById('sbNav');if(!nav)return; // Corrección lógica de cortocircuito para arreglar el menú oculto const allowed=new Set((allowedIds && allowedIds.length > 0) ? allowedIds : ALL_MENU.map(m=>m.id)); if(!isPropietario){ allowed.delete('configuracion'); if(!APP.user||!(APP.user.isPropietario||APP.user.puedeVerBitacora)){ allowed.delete('bitacora'); } } let lastSection='';let html=''; ALL_MENU.forEach(m=>{ if(!allowed.has(m.id)) return; if(m.section!==lastSection){html+=`
${m.section}
`;lastSection=m.section;} const extra=m.id==='configuracion'?' style="border-left:3px solid var(--ac)"':(m.id==='bitacora'?' style="border-left:3px solid var(--inf)"':''); html+=`${m.label}`; }); nav.innerHTML=html; } // 3. FUNCIÓN COMPLEMENTARIA: Activa los interruptores de módulos en Configuración function toggleMenu(id) { if(_menuState[id] === undefined) _menuState[id] = true; _menuState[id] = !_menuState[id]; const tog = document.getElementById('mt_' + id); const lbl = document.getElementById('mts_' + id); if(tog) { tog.style.background = _menuState[id] ? 'var(--ok)' : 'var(--err)'; const kn = tog.querySelector('.tog-knob'); if(kn) { kn.style.right = _menuState[id] ? '3px' : 'auto'; kn.style.left = _menuState[id] ? 'auto' : '3px'; } } if(lbl) { lbl.textContent = _menuState[id] ? 'Activo' : 'Inactivo'; lbl.style.color = _menuState[id] ? 'var(--ok)' : 'var(--err)'; } } // ══════════════════════════════════════════════════════════ // DASHBOARD — v5.2 con 4 gráficas dinámicas // ══════════════════════════════════════════════════════════ let _dashData=null; function renderDashboard(el){ el.innerHTML=`

Dashboard General

${Array(9).fill('
').join('')}
Asistencia & Ofrendas
Grupos vs Meta
Convertidos & Reconciliados
Manuales vs Digitales
Seguimientos
Asistencia por Distrito
Hermanos por Distrito
Tipo de Reunión
Stock Bajo
Últimos Reportes
Últimos Seguimientos
Cronogramas
`; recargarDash(); } function recargarDash(){ var rangoEl=document.getElementById('dashRango'); var rango=(rangoEl?rangoEl.value:'')||'mes'; const lbl=document.getElementById('dashRangoLbl'); if(lbl)lbl.textContent='('+{hoy:'Hoy',semana:'Esta semana',mes:'Este mes',trimestre:'Trimestre'}[rango]+')'; call('getDashboard',{rango},(err,d)=>{ if(!d||!d.ok)return; _dashData=d; buildDashKPIs(d); buildDashCharts(d); buildDashRecientes(d); // Stock bajo const stEl=document.getElementById('dashStock'); if(stEl){ var sb=d.stockBajo||[]; stEl.innerHTML=sb.length?sb.map(function(x){return '
'+esc(x.articulo)+' 🔴 '+x.cantidad+'/'+x.minimo+'
'+esc(x.categoria||'')+' · '+esc(x.unidad)+'
';}).join(''):'

Stock normal

'; } // Cronogramas const crEl=document.getElementById('dashCron'); if(crEl){ var cron=d.proxCron||[]; var dias=['Lunes','Jueves','Domingo_Mañana','Domingo_Tarde']; var diasLbl={Lunes:'Lun','Jueves':'Jue','Domingo_Mañana':'Dom 🕐','Domingo_Tarde':'Dom 🕥'}; crEl.innerHTML=cron.length?cron.map(function(c){ var parts=dias.filter(function(d){return c[d]==='SI'||c[d]==='1';}); var prv=c['Privilegio']||c['Servicio']||''; return '
'+esc(c['Hermano']||c['Area']||'')+'
'+(prv?'🔹 '+esc(prv)+' · ':'')+parts.map(function(d){return diasLbl[d]||d;}).join(', ')+'
'+esc(c['Codigo']||'')+'
'; }).join(''):'

Sin asignaciones

'; } }); // Seguimientos recientes en paralelo call('getSeguimientos',{},(_, segs)=>{ const el=document.getElementById('dashSeg');if(!el)return; const data=Array.isArray(segs)?segs.slice(-6).reverse():[]; const cls={Completado:'bg',Pendiente:'bo','En Proceso':'bi',Cancelado:'bgr'}; el.innerHTML=data.length?data.map(s=>`
${esc(s['Persona'])} ${esc(s['Estado'])}
${esc(s['Tipo'])} · ${esc(s['Responsable'])} · ${fmtD(s['Fecha'])}
`).join(''):`

Sin seguimientos

`; }); } function buildDashKPIs(d){ const sg=document.getElementById('dashStats');if(!sg)return; var metaG=parseInt((APP.config&&APP.config.metaGrupos)||d.metaGrupos||407); var pct=metaG>0?Math.round(d.gruposRealizados/metaG*100):0; // Actualizar título gráfica con meta real const titleMeta=document.getElementById('titleGruposMeta'); if(titleMeta)titleMeta.innerHTML=' Grupos Realizados vs Meta ('+metaG+') '; sg.innerHTML=[ {l:'Líderes',v:d.lideres,i:'fa-user-tie',c:''}, {l:'Grupos '+({hoy:'Hoy',semana:'Semana',mes:'Mes',trimestre:'Trim.'}[document.getElementById('dashRango')?.value||'mes']),v:d.reportesMes,i:'fa-home',c:'o'}, {l:'Meta: '+metaG,v:pct+'%',i:'fa-bullseye',c:pct>=100?'g':'t'}, {l:'Asistencia',v:d.asistencia,i:'fa-users',c:'g'}, {l:'Ofrenda',v:fmtQ(d.ofTotal),i:'fa-hand-holding-usd',c:'p'}, {l:'Convertidos',v:d.convertidos||0,i:'fa-heart',c:'i'}, {l:'Reconciliados',v:d.reconciliados||0,i:'fa-handshake',c:'t'}, {l:'Seguimientos',v:d.segTotal||0,i:'fa-tasks',c:'p'}, {l:'Pendientes',v:d.pendientes||0,i:'fa-exclamation-triangle',c:'r'}, {l:'Cronogramas',v:(d.proxCron||[]).length,i:'fa-calendar-alt',c:'t'}, ].map(s=>`
${s.v}
${s.l}
`).join(''); } function buildDashCharts(d){ const g=d.grafica||[]; const labels=g.map(x=>x.mes); const col1='#6366f1',col2='#f59e0b',col3='#10b981',col4='#8b5cf6',col5='#ef4444',col6='#06b6d4',col7='#ec4899',col8='#84cc16'; const colsReu=['#6366f1','#f59e0b','#10b981','#ec4899','#06b6d4']; ['chAsist','chGrupos','chConv','chTipo','chSeg','chDistAsist','chDistHnos','chReuniones'].forEach(id=>{ if(APP.charts[id]){APP.charts[id].destroy();delete APP.charts[id];} }); // Helper: datalabels plugin config var dlBar={id:'datalabels',afterDraw:function(chart){var ctx=chart.ctx;chart.data.datasets.forEach(function(ds,i){var meta=chart.getDatasetMeta(i);meta.data.forEach(function(bar,idx){var val=ds.data[idx];if(val>0){ctx.fillStyle='#1a3a5c';ctx.font='10px Arial';ctx.textAlign='center';ctx.textBaseline='bottom';ctx.fillText(val,bar.x,bar.y-4);}});});}}; var dlPie={id:'datalabels',afterDraw:function(chart){var ctx=chart.ctx;chart.data.datasets.forEach(function(ds){var total=ds.data.reduce(function(a,b){return a+b;},0);var meta=chart.getDatasetMeta(0);meta.data.forEach(function(arc,i){var val=ds.data[i];if(val>0){var angle=(arc.startAngle+arc.endAngle)/2;var x=arc.x+Math.cos(angle)*arc.outerRadius*0.65;var y=arc.y+Math.sin(angle)*arc.outerRadius*0.65;ctx.fillStyle='#fff';ctx.font='bold 12px Arial';ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillText(val,x,y);}});});}}; // 1. Asistencia & Ofrendas const c1=document.getElementById('chAsist'); if(c1)APP.charts.chAsist=new Chart(c1,{type:'bar',data:{labels,datasets:[ {label:'Asistencia',data:g.map(x=>x.asistencia),backgroundColor:col1,borderRadius:5,yAxisID:'y'}, {label:'Ofrendas Q',data:g.map(x=>x.ofrendas),type:'line',borderColor:col2,backgroundColor:'rgba(232,160,32,.08)',tension:.4,fill:true,pointRadius:4,pointBackgroundColor:col2,yAxisID:'y1'}, ]},options:dashOpts('y','y1'),plugins:[dlBar]}); // 2. Grupos realizados vs meta const c2=document.getElementById('chGrupos'); if(c2)APP.charts.chGrupos=new Chart(c2,{type:'bar',data:{labels,datasets:[ {label:'Grupos Realizados',data:g.map(x=>x.grupos),backgroundColor:col1,borderRadius:5}, {label:'Meta ('+parseInt((APP.config&&APP.config.metaGrupos)||d.metaGrupos||407)+')',data:g.map(function(){return parseInt((APP.config&&APP.config.metaGrupos)||d.metaGrupos||407);}),type:'line',borderColor:col5,borderDash:[6,3],borderWidth:2,pointRadius:0,fill:false}, ]},options:dashOpts(),plugins:[dlBar]}); // 3. Convertidos & Reconciliados (desde Seguimientos) const c3=document.getElementById('chConv'); if(c3)APP.charts.chConv=new Chart(c3,{type:'bar',data:{labels,datasets:[ {label:'Convertidos',data:g.map(x=>x.convertidos||0),backgroundColor:col3,borderRadius:5}, {label:'Reconciliados',data:g.map(x=>x.reconciliados||0),backgroundColor:col4,borderRadius:5}, ]},options:dashOpts(),plugins:[dlBar]}); // 4. Manuales vs Digitales const c4=document.getElementById('chTipo'); if(c4)APP.charts.chTipo=new Chart(c4,{type:'bar',data:{labels,datasets:[ {label:'Manuales',data:g.map(x=>x.manuales||0),backgroundColor:col1,borderRadius:5}, {label:'Digitales',data:g.map(x=>x.digitales||0),backgroundColor:col6,borderRadius:5}, ]},options:dashOpts(),plugins:[dlBar]}); // 5. Seguimientos por estado (dona) — TODOS los estados const c5=document.getElementById('chSeg'); if(c5){ var segData=[d.segPendientes||0,d.segEnProceso||0,d.segCompletados||0,d.segCancelados||0]; var segTotal=segData.reduce(function(a,b){return a+b;},0); if(segTotal>0){ c5.style.display='block'; APP.charts.chSeg=new Chart(c5,{type:'doughnut',data:{labels:['⚠ Pendientes','🔄 En Proceso','✓ Completados','✕ Cancelados'],datasets:[{data:segData,backgroundColor:['#f59e0b','#3b82f6','#10b981','#ef4444'],borderWidth:0}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:10},boxWidth:12}}}},plugins:[dlPie]}); } else { c5.style.display='none'; var p=c5.parentElement;if(p){var m=p.querySelector('.empty-r');if(!m){m=document.createElement('div');m.style.cssText='text-align:center;padding:20px;color:var(--tx3);font-size:13px';p.style.position='relative';p.appendChild(m);}m.innerHTML='Sin seguimientos';m.className='empty-r';} } } // 6. Asistencia por Distrito const c6=document.getElementById('chDistAsist'); var da=(d.asistenciaDistrito||[]); if(c6&&da.length){ APP.charts.chDistAsist=new Chart(c6,{type:'bar',data:{labels:da.map(function(x){return 'D'+x.distrito;}),datasets:[{label:'Asistencia',data:da.map(function(x){return x.asistencia;}),backgroundColor:['#6366f1','#f59e0b','#10b981','#8b5cf6','#06b6d4','#ef4444','#ec4899','#84cc16'],borderRadius:5}]},options:dashOpts(),plugins:[dlBar]}); } // 7. Hermanos por Distrito (dona) const c7=document.getElementById('chDistHnos'); var hd=(d.hermanosDistrito||[]); if(c7&&hd.length){ var colores=['#6366f1','#f59e0b','#10b981','#8b5cf6','#06b6d4','#ef4444','#ec4899','#84cc16']; APP.charts.chDistHnos=new Chart(c7,{type:'doughnut',data:{labels:hd.map(function(x){return 'Distrito '+x.distrito;}),datasets:[{data:hd.map(function(x){return x.count;}),backgroundColor:hd.map(function(_,i){return colores[i%colores.length];}),borderWidth:0}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:9},boxWidth:10}}}},plugins:[dlPie]}); } else if(c7){ c7.parentElement.innerHTML='
Sin datos
'; } // 8. Tipo de Reunion por Mes (barras agrupadas) const c8=document.getElementById('chReuniones'); var gr=(d.graficaReuniones||[]); if(c8&&gr.length){ var reuLabels=gr.map(function(x){return x.mes;}); var reuTipos=['Mixta (Reunión Regular)','Niños','Jóvenes','Mujeres','Profesionales']; APP.charts.chReuniones=new Chart(c8,{type:'bar',data:{labels:reuLabels,datasets:reuTipos.map(function(t,i){return{label:t,data:gr.map(function(x){return x[t]||0;}),backgroundColor:colsReu[i],borderRadius:3,barPercentage:0.85};})}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:9},boxWidth:10}}},scales:{x:{stacked:false,grid:{display:false}},y:{stacked:false,beginAtZero:true,grid:{color:'rgba(0,0,0,.04)'}},}},plugins:[dlBar]}); } else if(c8){ c8.parentElement.innerHTML='
Sin datos de reuniones
'; } } function dashOpts(y1id,y2id){ const scales={x:{grid:{display:false}}}; if(y1id)scales[y1id]={beginAtZero:true,grid:{color:'rgba(0,0,0,.04)'}}; else scales.y={beginAtZero:true,grid:{color:'rgba(0,0,0,.04)'}}; if(y2id)scales[y2id]={beginAtZero:true,position:'right',grid:{display:false}}; return{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:10},boxWidth:12}}},scales}; } function buildDashRecientes(d){ const re=document.getElementById('dashRec');if(!re)return; const recs=d.recientes||[]; re.innerHTML=recs.length?recs.map(r=>{ var met=r['Reporte']||(r['Tipo de Reporte']==='Reporte Digital'?'Digital':r['Tipo de Reporte']==='Reporte Físico'?'Físico':''); var tr=r['Tipo de Reporte']||''; if(tr==='Reporte Digital'||tr==='Reporte Físico')tr='—'; return `
${esc(r['Lider'])}
${fmtD(r['Fecha'])} · ${met||'—'} · ${esc(tr)}
${r['Ofrenda Recibida']==='Pendiente'||r['Ofrenda Recibida']===''?'⚠':'✓'}
`; }).join(''):`

Sin reportes

`; } // Imprimir gráfica individual function imprimirGrafica(canvasId,titulo){ const c=document.getElementById(canvasId);if(!c){toast('Gráfica no encontrada','wa');return;} const img=c.toDataURL('image/png'); const rango=document.getElementById('dashRango')?.value||'mes'; const w=window.open('','_blank','width=900,height=600'); if(!w){toast('Activa ventanas emergentes para imprimir','wa',5000);return;} w.document.write(`${titulo}

📊 ${titulo}

Iglesia Restauración · Período: ${rango} · ${new Date().toLocaleDateString('es-GT')}
`); w.document.close(); } // Imprimir todas las gráficas juntas function imprimirDash(){ if(!_dashData){toast('Carga el dashboard primero','wa');return;} const ids=[['chAsist','Asistencia & Ofrendas'],['chGrupos','Grupos vs Meta'],['chConv','Convertidos & Reconciliados'],['chTipo','Manuales vs Digitales'],['chSeg','Seguimientos'],['chDistAsist','Asistencia x Distrito'],['chDistHnos','Hermanos x Distrito'],['chReuniones','Tipo de Reunión por Mes']]; const imgs=ids.map(([id,t])=>{const c=document.getElementById(id);return c?{img:c.toDataURL('image/png'),t}:null;}).filter(Boolean); const d=_dashData;const rango=document.getElementById('dashRango')?.value||'mes'; const w=window.open('','_blank','width=1100,height=800'); if(!w){toast('Activa ventanas emergentes para imprimir','wa',5000);return;} w.document.write(`Dashboard — Iglesia Restauración

📊 Dashboard — Iglesia Restauración

Período: ${rango} · Generado: ${new Date().toLocaleDateString('es-GT')} ${new Date().toLocaleTimeString('es-GT')}
${d.reportesMes} grupos · ${d.asistencia} asistencia · Q${Number(d.ofTotal).toFixed(2)}
${d.lideres}
Líderes
${d.reportesMes}
Grupos
${d.metaGrupos>0?Math.round(d.gruposRealizados/d.metaGrupos*100):0}%
Meta
${d.asistencia}
Asistencia
Q${Number(d.ofTotal).toFixed(2)}
Ofrenda
${d.convertidos||0}
Convertidos
${d.reconciliados||0}
Reconciliados
${d.pendientes||0}
Ofrenda Pend.
${(d.proxCron||[]).length}
Cronogramas
${imgs.map(x=>`

${x.t}

`).join('')}
`); w.document.close(); setTimeout(function(){w.print();w.onafterprint=function(){w.close();};},500); } // ══════════════════════════════════════════════════════════ // REPORTE DE GRUPOS // ══════════════════════════════════════════════════════════ // REPORTE DE GRUPOS (con tabs Todos / Pendientes) // ══════════════════════════════════════════════════════════ let _rep=[];let _repTab='todos';let _qRep=''; function renderReportes(el){ el.innerHTML=`

Reporte de Grupos

${permisoEditar()?``:''}
FechaCódigoLíderSup.SectorAnfitriónH.IniH.FinAGFHAmNOfIgOfBusOfTotTotalReporteOfrendaAcciones
`; _repTab='todos';loadRep(); } function switchRepTab(tab){ _repTab=tab; ['todos','pendientes'].forEach(t=>{ const btn=document.getElementById('tab_'+t);if(!btn)return; const active=t===tab; btn.style.background=active?'#fff':'var(--bg3)'; btn.style.color=active?'var(--pr)':'var(--tx2)'; btn.style.borderBottom=active?'2px solid var(--pr)':'2px solid transparent'; }); renderRepTable(_rep); } function abrirFiltroAsistencia(){ const panel=document.getElementById('panelAsistencia'); if(!panel) return; const isOpen = panel.style.display==='block'; panel.style.display = isOpen ? 'none' : 'block'; if(!isOpen){ // Poblar selects con datos frescos if(!APP.hermanos||!APP.hermanos.length){ call('getHermanos',{},function(_,h){ APP.hermanos=Array.isArray(h)?h:[]; poblarSelectsFiltro(); }); } else { poblarSelectsFiltro(); } } } function poblarSelectsFiltro(){ var h=APP.hermanos||[]; var pzEl=document.getElementById('faPastorZona'); var saEl=document.getElementById('faArea'); var ssEl=document.getElementById('faSector'); var lEl=document.getElementById('faLider'); var curPZ=pzEl?pzEl.value:''; var curSA=saEl?saEl.value:''; var curSS=ssEl?ssEl.value:''; var curL=lEl?lEl.value:''; // Pastor de Zona (nivel más alto) var pzList=[...new Set(h.map(function(x){return x['Pastor Zona']||'';}).filter(Boolean))].sort(); if(pzEl) pzEl.innerHTML=''+pzList.map(function(p){return ''+esc(p)+'';}).join(''); // Ayuda de Pastor / Sup Área (filtra por PZ si está seleccionado) var hSA=curPZ?h.filter(function(x){return x['Pastor Zona']===curPZ;}):h; var saList=[...new Set(hSA.map(function(x){return x['Sup AreaL']||'';}).filter(Boolean))].sort(); if(saEl) saEl.innerHTML=''+saList.map(function(s){return ''+esc(s)+'';}).join(''); // Sup Sector (filtra por PZ y SA) var hSS=h; if(curPZ) hSS=hSS.filter(function(x){return x['Pastor Zona']===curPZ;}); if(curSA) hSS=hSS.filter(function(x){return x['Sup AreaL']===curSA;}); var ssList=[...new Set(hSS.map(function(x){return x['Sup SectorL']||'';}).filter(Boolean))].sort(); if(ssEl) ssEl.innerHTML=''+ssList.map(function(s){return ''+esc(s)+'';}).join(''); // Líderes (filtra por todos los niveles) var hL=h; if(curPZ) hL=hL.filter(function(x){return x['Pastor Zona']===curPZ;}); if(curSA) hL=hL.filter(function(x){return x['Sup AreaL']===curSA;}); if(curSS) hL=hL.filter(function(x){return x['Sup SectorL']===curSS;}); hL=hL.slice().sort(function(a,b){return (a['NombreL']||'').localeCompare(b['NombreL']||'');}); if(lEl) lEl.innerHTML=''+hL.map(function(x){return '';}).join(''); } // ── BÚSQUEDA EN ASISTENCIA HOY ── function buscarAsistencia(q){ q = q.trim().toLowerCase(); const el = document.getElementById('faSearchRes'); if(!q || q.length < 2){ if(el)el.style.display='none'; return; } const h = APP.hermanos||[]; var pzSet={}, areaSet={}, sectorSet={}, lids=[]; const seen = {}; h.forEach(function(x){ var pz = (x['Pastor Zona']||'').toLowerCase(); var area = (x['Sup AreaL']||'').toLowerCase(); var sector = (x['Sup SectorL']||'').toLowerCase(); var nom = (x['NombreL']||'').toLowerCase(); var cod = (x['CodigoL']||'').toLowerCase(); if(seen[x['CodigoL']]) return; if(pz.includes(q) && !pzSet[x['Pastor Zona']]){ pzSet[x['Pastor Zona']]=true; seen[x['CodigoL']]=true; } if(area.includes(q) && !areaSet[x['Sup AreaL']]){ areaSet[x['Sup AreaL']]=true; seen[x['CodigoL']]=true; } if(sector.includes(q) && !sectorSet[x['Sup SectorL']]){ sectorSet[x['Sup SectorL']]=true; seen[x['CodigoL']]=true; } if(nom.includes(q) || cod.includes(q)){ lids.push(x); seen[x['CodigoL']]=true; } }); var html = ''; Object.keys(pzSet).forEach(function(n){ var c = h.filter(function(x){ return x['Pastor Zona']===n; }).length; html += '
🏛 '+esc(n)+' ('+c+' líderes)
'; }); Object.keys(areaSet).forEach(function(n){ var c = h.filter(function(x){ return x['Sup AreaL']===n; }).length; html += '
👤 '+esc(n)+' ('+c+' líderes)
'; }); Object.keys(sectorSet).forEach(function(n){ var c = h.filter(function(x){ return x['Sup SectorL']===n; }).length; html += '
📋 '+esc(n)+' ('+c+' líderes)
'; }); lids.forEach(function(x){ html += '
🙋 '+esc(x['NombreL'])+' ('+esc(x['CodigoL'])+')
'; }); if(!html){ el.style.display='none'; return; } el.innerHTML = html; el.style.display = 'block'; } function seleccionarAsistencia(type, value){ document.getElementById('faSearchRes').style.display='none'; document.getElementById('faSearch').value = value; document.getElementById('faPastorZona').value = ''; document.getElementById('faArea').value = ''; document.getElementById('faSector').value = ''; document.getElementById('faLider').value = ''; var h = APP.hermanos||[]; if(type === 'pz'){ document.getElementById('faPastorZona').value = value; } else if(type === 'area'){ var her = h.find(function(x){ return x['Sup AreaL']===value; }); if(her && her['Pastor Zona']) document.getElementById('faPastorZona').value = her['Pastor Zona']; document.getElementById('faArea').value = value; } else if(type === 'sector'){ var her = h.find(function(x){ return x['Sup SectorL']===value; }); if(her){ if(her['Pastor Zona']) document.getElementById('faPastorZona').value = her['Pastor Zona']; if(her['Sup AreaL']) document.getElementById('faArea').value = her['Sup AreaL']; } document.getElementById('faSector').value = value; } else if(type === 'lider'){ var her = h.find(function(x){ return x['CodigoL']===value; }); if(her){ if(her['Pastor Zona']) document.getElementById('faPastorZona').value = her['Pastor Zona']; if(her['Sup AreaL']) document.getElementById('faArea').value = her['Sup AreaL']; if(her['Sup SectorL']) document.getElementById('faSector').value = her['Sup SectorL']; } document.getElementById('faLider').value = value; } cascadeFiltros(); aplicarFiltroAsistencia(); } // Cerrar búsqueda al hacer clic fuera document.addEventListener('click', function(e){ var el = document.getElementById('faSearchRes'); if(el && !e.target.closest('#faSearch') && !e.target.closest('#faSearchRes')) el.style.display='none'; }); // ── RENDER JERARQUÍA (PZ → Área → Líder) ── function renderJerarquiaAsistencia(repDia, pendientes, hermanos){ // Construir lista plana: cada reporte es una fila, cada líder sin reporte es otra var items=[]; // 1. Agregar todos los reportes del ciclo repDia.forEach(function(r){ var h=hermanos.find(function(x){return String(x['CodigoL'])===String(r['Codigo']||r['CodigoL']||'')||String(x['NombreL']||'').trim().toLowerCase()===String(r['Lider']||r['Datos de Lider']||'').trim().toLowerCase();}); if(!h)h={}; var ofR=(r['Ofrenda Recibida']||'').trim(); var ok=ofR==='SI'||ofR==='Recibida'; items.push({h:h,r:r,ok:ok,tipo:'rep'}); }); // 2. Agregar líderes sin reporte pendientes.forEach(function(h){ items.push({h:h,r:null,ok:false,tipo:'pend'}); }); // Ordenar por PZ → Area → Fecha (reportes) → Nombre (pendientes) items.sort(function(a,b){ var pzA=a.h['Pastor Zona']||'',pzB=b.h['Pastor Zona']||''; if(pzA!==pzB)return pzA.localeCompare(pzB); var arA=a.h['Sup AreaL']||'',arB=b.h['Sup AreaL']||''; if(arA!==arB)return arA.localeCompare(arB); if(a.tipo!==b.tipo)return a.tipo==='rep'?-1:1; if(a.tipo==='rep'&&b.tipo==='rep'){ var dA=a.r['Fecha']||'',dB=b.r['Fecha']||''; if(dA!==dB)return dA.localeCompare(dB); var hA=a.r['Hora Inicio']||'',hB=b.r['Hora Inicio']||''; return hA.localeCompare(hB); } return (a.h['NombreL']||'').localeCompare(b.h['NombreL']||''); }); var html='',curPZ='',curArea=''; var pzOK=0,pzTotal=0,areaOK=0,areaTotal=0; function cerrarPZ(){ if(curPZ==='')return ''; var p=pzTotal>0?Math.round(pzOK/pzTotal*100):0; var pc=p>=50?'var(--ok)':'var(--err)'; return '
✓ '+pzOK+'/'+pzTotal+' ('+p+'%)
'; } for(var i=0;i'; } if(area!==curArea){ if(curArea!=='')html+=''; curArea=area;areaOK=0;areaTotal=0; html+='
'; html+='
👤 '+esc(area)+'
'; } if(it.tipo==='rep'){ var r=it.r,agf=r['Asistencia Grupo Familiar']||0,ofVal=r['Ofrenda Total']||''; var ofR=(r['Ofrenda Recibida']||'').trim(); var metodo=r['Reporte']||(r['Tipo de Reporte']==='Reporte Digital'?'Digital':r['Tipo de Reporte']==='Reporte Físico'?'Físico':''); var reunionTipo=r['Tipo de Reporte']||''; if(reunionTipo==='Reporte Digital'||reunionTipo==='Reporte Físico')reunionTipo='Mixta'; var fechaCorta=r['Fecha']?r['Fecha'].substring(5):''; var bg=it.ok?'#f0fff4':'#fffbe6'; var ico=it.ok?'':''; var ofBadge=ofR==='SI'||ofR==='Recibida' ?'Ofrenda ✓' :'Ofrenda ⏳'; var etiqMetodo=metodo==='Digital'?'D':'F'; html+='
' +'
'+ico+etiqMetodo+' '+esc(h['NombreL']||'')+' '+esc(h['CodigoL']||'')+' · '+fechaCorta+' '+ofBadge+'
' +'
AGF: '+agf+(ofVal?' · Q '+Number(ofVal).toFixed(2):'')+' · '+esc(reunionTipo)+'
'; pzOK+=it.ok?1:0;pzTotal++; } else { html+='
' +'
'+esc(h['NombreL'])+' Sin reporte '+esc(h['CodigoL'])+'
' +'
'; pzTotal++; } } if(curPZ!=='')html+=cerrarPZ(); return html||'
Sin líderes en este filtro
'; } function cascadeFiltros(){ poblarSelectsFiltro(); } function aplicarFiltroAsistencia(){ const fecha = document.getElementById('faFecha')?.value || today(); const pz = document.getElementById('faPastorZona')?.value || ''; const area = document.getElementById('faArea')?.value || ''; const sector= document.getElementById('faSector')?.value || ''; const lider = document.getElementById('faLider')?.value || ''; const res = document.getElementById('faResultado'); if(!res) return; // Calcular ciclo semanal (Lunes a Domingo) basado en la fecha seleccionada const dObj = new Date(fecha + 'T12:00:00'); const day = dObj.getDay(); // 0-Dom, 1-Lun, ..., 6-Sab // Ajuste para que la semana empiece el lunes (lunes=0) const diff = dObj.getDate() - day + (day === 0 ? -6 : 1); const dStart = new Date(dObj); dStart.setDate(diff); const dEnd = new Date(dStart); dEnd.setDate(dStart.getDate() + 6); const fStart = dStart.toISOString().split('T')[0]; const fEnd = dEnd.toISOString().split('T')[0]; // Mostrar spinner mientras carga del servidor res.innerHTML = '

Cargando datos del servidor...

'; // Cargar reportes del ciclo (Lunes 00:01 a Domingo 23:59) const filtros = {desde: fStart, hasta: fEnd}; if(pz) filtros.pastorZona = pz; if(area) filtros.supArea = area; if(sector) filtros.supSector = sector; call('getReportes', filtros, function(_, d){ const repDia = Array.isArray(d) ? d : []; // Determinar lista de líderes según jerarquía seleccionada let lideresFiltro = APP.hermanos; if(pz) lideresFiltro = lideresFiltro.filter(h=>(h['Pastor Zona']||'')===pz); if(area) lideresFiltro = lideresFiltro.filter(h=>(h['Sup AreaL']||'')===area); if(sector) lideresFiltro = lideresFiltro.filter(h=>(h['Sup SectorL']||'')===sector); if(lider) lideresFiltro = lideresFiltro.filter(h=>h['CodigoL']===lider); // Quien reportó esta semana const codesSemana = new Set(repDia.map(function(r){return String(r['Codigo']||r['CodigoL']||'');})); const namesSemana = new Set(repDia.map(function(r){return String(r['Lider']||r['Datos de Lider']||'').trim().toLowerCase();}).filter(Boolean)); // KPIs por REPORTE (no por líder) const totalReportes = repDia.length; const completos = repDia.filter(function(r){return (r['Ofrenda Recibida']||'').trim()==='SI'||r['Ofrenda Recibida']==='Recibida';}); const soloReporte = repDia.filter(function(r){return (r['Ofrenda Recibida']||'').trim()!=='SI'&&r['Ofrenda Recibida']!=='Recibida';}); // Líderes sin ningún reporte esta semana (para KPIs de cobertura) const pendientes = lideresFiltro.filter(function(h){ var cod=String(h['CodigoL']);var nom=String(h['NombreL']||'').trim().toLowerCase(); return !(codesSemana.has(cod)||namesSemana.has(nom)); }); const lideresReportaron = lideresFiltro.filter(function(h){ var cod=String(h['CodigoL']);var nom=String(h['NombreL']||'').trim().toLowerCase(); return codesSemana.has(cod)||namesSemana.has(nom); }); const totalLideres = lideresFiltro.length; const pctCobertura = totalLideres > 0 ? Math.round(lideresReportaron.length/totalLideres*100) : 0; const barColor = pctCobertura>=80?'var(--ok)':pctCobertura>=50?'var(--ac)':'var(--err)'; const labelFiltro = [ pz ? '🏛 '+pz : '', area ? '👤 '+area : '', sector? '📋 '+sector: '', lider ? '🙋 Líder: '+lider : '' ].filter(Boolean).join(' → ') || 'Todos los líderes'; res.innerHTML = `
📊 Ciclo de Reporte (Lun-Dom): ${fmtD(fStart)} al ${fmtD(fEnd)} · ${labelFiltro}
${completos.length}
✓ Reporte+Ofrenda
${soloReporte.length}
📱 Sin ofrenda
${pendientes.length}
⏳ Sin Reporte
${totalLideres}
Total líderes
${pctCobertura}%
Cobertura
${renderJerarquiaAsistencia(repDia, pendientes, lideresFiltro)}
Generado: ${new Date().toLocaleTimeString('es-GT')}
`; }); // cierre getReportes (semana) } function imprimirAsistenciaHoy(fecha,area,sector){ const res=document.getElementById('faResultado'); if(!res||!res.innerHTML.trim()){toast('Aplica el filtro primero','wa');return;} const appNom=(APP.config&&APP.config.nombre)||'Iglesia Restauración'; const h='' +'

📋 Asistencia de Líderes — '+appNom+'

' +'

'+fmtD(fecha)+(area?' · Área: '+esc(area):'')+(sector?' · Sector: '+esc(sector):'')+'

' +res.innerHTML +'setTimeout(()=>{window.print();setTimeout(()=>window.close(),400);},350);<'+'/scr'+'ipt>'; abrirVentanaImpresion(h); } function loadRep(){ var f={desde:g('rD')||fom(),hasta:g('rH')||today()}; var l=g('rL'),ss=g('rSS'),pz=g('rPZ'),di=g('rDist'),zo=g('rZona'); if(l)f.lider=l;if(ss)f.supSector=ss;if(pz)f.pastorZona=pz; if(di)f.distrito=di;if(zo)f.zona=zo;if(g('rPend'))f.pendientes=true; var ck='rpt_'+f.desde+'_'+f.hasta+(f.pastorZona?'_pz:'+f.pastorZona:'')+(f.supSector?'_ss:'+f.supSector:'')+(f.distrito?'_di:'+f.distrito:'')+(f.lider?'_ld:'+f.lider:''); var cached=sessionStorage.getItem(ck); var v=parseInt(sessionStorage.getItem('rpt_ver')||'0'); if(cached&&Date.now()-parseInt(sessionStorage.getItem(ck+'_t')||'0')<30000){ try{var cd=JSON.parse(cached);if(cd.v===v){_rep=cd.d;renderRepTable(_rep);renderRepRes(_rep);return;}}catch(e){} } loading(true);call('getReportes',f,function(_,d){loading(false);_rep=Array.isArray(d)?d:[];try{sessionStorage.setItem(ck,JSON.stringify({v:d.v||v,d:_rep}));sessionStorage.setItem(ck+'_t',Date.now());}catch(e){}var pend=_rep.filter(function(r){return r['Ofrenda Recibida']==='Pendiente'||r['Ofrenda Recibida']==='';});var bt=document.getElementById('badge_todos'),bp=document.getElementById('badge_pendientes');if(bt)bt.textContent=_rep.length;if(bp)bp.textContent=pend.length;renderRepTable(_rep);renderRepRes(_rep);}); } function limpiarFiltrosRep(){ ['rDist','rPZ','rSA','rSS','rL'].forEach(id=>{ const el=document.getElementById(id); if(el)el.value=''; }); const fi=document.getElementById('filtrosActivos'); if(fi)fi.style.display='none'; loadRep(); } function actualizarSelectores(){loadRep();} function getVisibleRep(){ let d=_repTab==='pendientes'?_rep.filter(r=>r['Ofrenda Recibida']==='Pendiente'||r['Ofrenda Recibida']===''):_rep; if(_qRep)d=d.filter(r=>JSON.stringify(r).toLowerCase().includes(_qRep.toLowerCase())); // Filtros adicionales var rReu=document.getElementById('rReu');if(rReu&&rReu.value)d=d.filter(function(r){var t=r['Tipo de Reporte']||'';if(t==='Reporte Digital'||t==='Reporte Físico')t='Mixta (Reunión Regular)';return t===rReu.value;}); var rMet=document.getElementById('rMetodo');if(rMet&&rMet.value)d=d.filter(function(r){var m=r['Reporte']||(r['Tipo de Reporte']==='Reporte Digital'?'Digital':r['Tipo de Reporte']==='Reporte Físico'?'Físico':'');return m===rMet.value;}); return d; } function renderRepRes(d){ const el=document.getElementById('repRes');if(!el)return; const pend=d.filter(r=>r['Ofrenda Recibida']==='Pendiente'||r['Ofrenda Recibida']==='').length; const res={total:d.length,asist:d.reduce((s,r)=>s+num(r['Asistencia Grupo Familiar']),0),ofIg:d.reduce((s,r)=>s+num(r['Ofrenda Iglesia']),0),ofBus:d.reduce((s,r)=>s+num(r['Ofrenda Bus']),0),ofTot:d.reduce((s,r)=>s+num(r['Ofrenda Total']),0),tot:d.reduce((s,r)=>s+num(r['Total']),0)}; el.innerHTML=[{l:'Reportes',v:res.total,c:''},{l:'Asist. GF',v:res.asist,c:'g'},{l:'Ofr. Iglesia',v:fmtQ(res.ofIg),c:'o'},{l:'Ofr. Bus',v:fmtQ(res.ofBus),c:'p'},{l:'Ofr. Total',v:fmtQ(res.ofTot),c:'r'},{l:'Total Cultos',v:res.tot,c:'i'},{l:'⚠ Pendientes',v:pend,c:'r'}].map(s=>`
${s.v}
${s.l}
`).join(''); } function renderRepTable(){ const data=getVisibleRep(); const tb=document.getElementById('repTb');if(!tb)return; if(!data.length){ const msg=_repTab==='pendientes'?'Sin reportes con ofrenda pendiente 👍':'No hay reportes en este período'; tb.innerHTML=`

${msg}

`; return; } tb.innerHTML=data.map(r=>{ const isPend=r['Ofrenda Recibida']==='Pendiente'||r['Ofrenda Recibida']===''; var metodo=r['Reporte']||(r['Tipo de Reporte']==='Reporte Digital'?'Digital':r['Tipo de Reporte']==='Reporte Físico'?'Físico':''); var reunionTipo=r['Tipo de Reporte']||''; if(reunionTipo==='Reporte Digital'||reunionTipo==='Reporte Físico')reunionTipo='Mixta (Reunión Regular)'; return ` ${fmtD(r['Fecha'])} ${esc(r['Codigo'])} ${esc(r['Lider'])} ${esc(r['Sup Sector'])}${esc(r['Anfitrion'])} ${esc(r['Hora Inicio'])}${esc(r['Hora Final'])} ${r['Asistencia Grupo Familiar']||0} ${r['Hnos']||0}${r['Amigos']||0}${r['Niños']||0} ${r['Ofrenda Iglesia']||''}${r['Ofrenda Bus']||''} ${r['Ofrenda Total']||''} ${r['Total']||0} ${metodo||'—'}
${esc(reunionTipo)} ${isPend ?`` :`✓ Recibida` }
${permisoEditar()?` `:''}
`; }).join(''); } function filterRep(q){_qRep=q;renderRepTable();} // CONFIRMAR OFRENDA — UN BOTÓN, SIN PEDIR MONTO function confirmarOfrendaRapido(id,lider){ openModal('Confirmar Ofrenda Recibida',`
💰

${esc(lider)}

¿Confirmas que recibiste físicamente la ofrenda de este grupo familiar?

El monto ya fue registrado en el reporte digital. Solo confirma la recepción física.
`, ` `); } function ejecutarConfirmacion(id){ loading(true); call('guardarOfrendaRecibida',{id},(_,res)=>{ loading(false); if(res?.ok){toast('✓ Ofrenda marcada como recibida','ok');closeModal();loadRep();} else toast('Error: '+(res?.msg||''),'er'); }); } function repForm(r){ r=r||{}; const hOpts=APP.hermanos.map(h=>``).join(''); const tSeg=['Convertido','Reconciliación','Visita Programada','Bautizo en Agua','Sanidad','Oración','Otro']; const tReu=TIPO_REUNION; const tMetodo=METODO_REPORTE; // Seguimientos dinámicos — solo 1 inicial, se agregan con botón + let segsHTML = '
'; // Calcular cuántos seguimientos editables mostrar (si editando con datos, mostrar todos los que tengan valores) let initialSeg = 1; for (let i = 10; i >= 2; i--) { let nk = i === 1 ? 'Nombre Persona' : 'Nombre Persona ' + i; if (r[nk]) { initialSeg = i; break; } } let tSegOpts = tSeg.map(t=>``).join(''); for (let i = 1; i <= initialSeg; i++) { let nk = i === 1 ? 'Nombre Persona' : 'Nombre Persona ' + i; let tk = i === 1 ? 'Tipo de Seguimiento' : 'Tipo de Seguimiento ' + i; let nv = esc(r[nk]||''); let tv = r[tk]||''; let selOpts = '' + tSeg.map(t=>``).join(''); segsHTML += `
`; } segsHTML += '
'; _segRepCount = initialSeg; return `
Búsqueda por Código de Líder
Info del Líder (auto-completado)
Datos de la Reunión
Asistencia
Asistencia a cultos en la iglesia
Ofrendas
Seguimientos (Agregar manualmente · máx 10)
${segsHTML}
`; } // ── BÚSQUEDA DE LÍDER POR TECLADO (para rf_codigo_input) ────── function filtrarLiderInput(val){ var sel=document.getElementById('rf_codigo'); if(!sel)return; var q=val.toLowerCase().trim(); if(!q){sel.value='';return;} var opts=sel.options; var bestMatch = null; for(var i=1;i=2) buscarLider(val); } function buscarLiderSel(val){ document.getElementById('rf_codigo_input').value=val; buscarLider(val); } function buscarLider(codigo){ const st=document.getElementById('codStatus');if(!codigo){if(st)st.textContent='';return;} // Buscar primero en APP.hermanos local const h=APP.hermanos.find(x=>String(x['CodigoL'])===String(codigo)); const fill=(hd)=>{ if(st){st.className='cod-status cod-ok';st.innerHTML=' '+esc(hd['NombreL']);} // Llenar TODOS los campos del formulario con datos del líder [['rf_lider',hd['NombreL']||''],['rf_supSector',hd['Sup SectorL']||hd['SupSector']||''],['rf_supArea',hd['Sup AreaL']||hd['SupArea']||''],['rf_pastorZona',hd['Pastor Zona']||hd['PastorZona']||''],['rf_anfitrion',hd['Anfitrion']||hd['Anfitrion']||''],['rf_dir',hd['Direccion']||hd['Direccion']||'']].forEach(function(pair){ var id=pair[0],val=pair[1]; var el=document.getElementById(id); if(el)el.value=val||''; }); }; if(h){fill(h);return;} // Si no está en local, buscar en el servidor call('getHermanoByCodigo',{codigo:codigo},function(_,res){ if(res&&res.ok&&res.data)fill(res.data); else if(st){st.className='cod-status cod-err';st.innerHTML=' Código no encontrado';} }); } function calcAsist(){const h=num(g('rf_hnos')),a=num(g('rf_amigos')),n=num(g('rf_ninos'));const el=document.getElementById('rf_agf');if(el)el.value=h+a+n;} function calcTotal(){const m=num(g('rf_martes')),j=num(g('rf_jueves')),d=num(g('rf_domingo')),o=num(g('rf_otros'));const el=document.getElementById('rf_total');if(el)el.value=m+j+d+o;} function calcOfrenda(){const i=num(g('rf_ofIg')),b=num(g('rf_ofBus'));const el=document.getElementById('rf_ofTot');if(el)el.value=(i+b).toFixed(2);} function mFoot(saveFn,label){ label=label||'Guardar'; return '' +''; } function mFootXL(saveFn,label){ label=label||'Guardar'; return '' +''; } function newReporte(){openModal('Nuevo Reporte de Grupo',repForm(),"",'xl');} function editRep(id){ const r=_rep.find(x=>x['ID']===id); if(!r)return; openModal( 'Editar Reporte', repForm(r), ``, 'xl' ); setTimeout(()=>buscarLider(r['Codigo']),100); } function viewRep(id){ const r=_rep.find(x=>x['ID']===id);if(!r)return; const isPend=r['Ofrenda Recibida']==='Pendiente'||r['Ofrenda Recibida']===''; var metodo=r['Reporte']||(r['Tipo de Reporte']==='Reporte Digital'?'Digital':r['Tipo de Reporte']==='Reporte Físico'?'Físico':''); var reunionTipo=r['Tipo de Reporte']||''; if(reunionTipo==='Reporte Digital'||reunionTipo==='Reporte Físico')reunionTipo='Mixta (Reunión Regular)'; const isDigital=metodo==='Digital'; const campos=[ ['📅 Fecha',fmtD(r['Fecha'])],['⏰ Hora',r['Hora Inicio']+' → '+r['Hora Final']], ['🔑 Código',r['Codigo']],['👤 Líder',r['Lider']], ['🏘 Sup. Sector',r['Sup Sector']],['🗺 Ayuda de Pastor',r['Sup Area']], ['🏛 Pastor de Zona',r['Pastor Zona']], ['🏠 Anfitrión',r['Anfitrion']],['📍 Dirección',r['Direccion']], ['📋 Reporte',metodo],['📋 Tipo Reunión',reunionTipo], ]; const asist=[ ['Total AGF',r['Asistencia Grupo Familiar']],['Hermanos',r['Hnos']], ['Amigos',r['Amigos']],['Niños',r['Niños']], ['Total Cultos',r['Total']],['Martes',r['Martes']], ['Jueves',r['Jueves']],['Domingo',r['Domingo']], ]; const ofr=[ ['Ofrenda Iglesia','Q '+r['Ofrenda Iglesia']], ['Ofrenda Bus','Q '+r['Ofrenda Bus']], ['Ofrenda Total','Q '+r['Ofrenda Total']], ['Ofrenda Recibida',isPend?'⚠ PENDIENTE':r['Ofrenda Recibida']], ]; const segs=[[r['Nombre Persona'],r['Tipo de Seguimiento']],[r['Nombre Persona 2'],r['Tipo de Seguimiento 2']],[r['Nombre Persona 3'],r['Tipo de Seguimiento 3']],[r['Nombre Persona 4'],r['Tipo de Seguimiento 4']],[r['Nombre Persona 5'],r['Tipo de Seguimiento 5']],[r['Nombre Persona 6'],r['Tipo de Seguimiento 6']],[r['Nombre Persona 7'],r['Tipo de Seguimiento 7']],[r['Nombre Persona 8'],r['Tipo de Seguimiento 8']],[r['Nombre Persona 9'],r['Tipo de Seguimiento 9']],[r['Nombre Persona 10'],r['Tipo de Seguimiento 10']]].filter(s=>s[0]); openModal('Detalle del Reporte — '+esc(r['Lider']), `
${isDigital?'
Reporte Digital — enviado por el líder desde el formulario externo
':''} ${isPend?'
Ofrenda pendiente de entrega física
':''}
Información General
${campos.map(([k,v])=>`
${k}
${esc(v)||'—'}
`).join('')}
Asistencia
${asist.map(([k,v])=>`
${esc(v)||0}
${k}
`).join('')}
Ofrendas
${ofr.map(([k,v])=>`
${k}
${esc(v)||'—'}
`).join('')}
${segs.length?`
Seguimientos (${segs.length})
${segs.map(([nom,tipo])=>`
${esc(nom)}
${esc(tipo)||'—'}
`).join('')}
`:''} ${permisoEditar()?`
Editar info del líder
`:''}
`, ` ${isPend?``:''} ${permisoEditar()?``:''}` ,'xl'); } function toggleEditLider(id){ const p=document.getElementById('editLiderPanel_'+id); const ico=document.getElementById('editLiderIco_'+id); if(p){const open=p.style.display==='none';p.style.display=open?'block':'none';if(ico)ico.className=open?'fas fa-chevron-up':'fas fa-chevron-down';} } function guardarEditLider(id){ const r=_rep.find(x=>x['ID']===id);if(!r)return; const updates={...r,Anfitrion:document.getElementById('el_anf_'+id)?.value||r['Anfitrion'],Direccion:document.getElementById('el_dir_'+id)?.value||r['Direccion'],'Hora Inicio':document.getElementById('el_hi_'+id)?.value||r['Hora Inicio'],'Hora Final':document.getElementById('el_hf_'+id)?.value||r['Hora Final'],token:getToken()}; loading(true);call('saveReporte',updates,(_,res)=>{loading(false);if(res?.ok){toast('Cambios guardados','ok');loadRep();}else toast('Error guardando','er');}); } // ── Seguimientos dinámicos para repForm ────────────── let _segRepCount=1; function addSegRep(){ if(_segRepCount>=10){toast('Máximo 10 seguimientos','wa');return;} _segRepCount++; const c=document.getElementById('segRepContainer'); if(!c)return; const i=_segRepCount; const d=document.createElement('div'); d.id='sri_'+i; d.style.cssText='background:var(--bg3);border-radius:8px;padding:10px;margin-bottom:8px;position:relative;display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end'; d.innerHTML='' +'
' +'
'; c.appendChild(d); if(_segRepCount>=10)document.getElementById('segAddBtn')?.style.setProperty('display','none'); } function removeSegRep(i){ const d=document.getElementById('sri_'+i); if(d){d.remove();_segRepCount--;} const addBtn=document.getElementById('segAddBtn'); if(addBtn)addBtn.style.display=''; } function saveRepForm(id){ const data={'ID':id||'','Datos de Lider':g('rf_lider'),'Codigo':g('rf_codigo'),'Lider':g('rf_lider'),'Sup Sector':g('rf_supSector'),'Sup Area':g('rf_supArea'),'Pastor Zona':g('rf_pastorZona'),'Fecha':g('rf_fecha'),'Anfitrion':g('rf_anfitrion'),'Direccion':g('rf_dir'),'Hora Inicio':g('rf_hInicio'),'Hora Final':g('rf_hFinal'),'Hnos':g('rf_hnos'),'Amigos':g('rf_amigos'),'Niños':g('rf_ninos'),'Asistencia Grupo Familiar':g('rf_agf'),'Ofrenda Iglesia':g('rf_ofIg'),'Ofrenda Bus':g('rf_ofBus'),'Ofrenda Total':g('rf_ofTot'),'Martes':g('rf_martes'),'Jueves':g('rf_jueves'),'Domingo':g('rf_domingo'),'Otros':g('rf_otros'),'Total':g('rf_total'),'Reporte':g('rf_metodo'),'Tipo de Reporte':g('rf_tipo')}; // Recolectar seguimientos dinámicamente (los que estén visibles) const segItems=document.querySelectorAll('#segRepContainer > div'); let idx=1; segItems.forEach(function(el){ const num=parseInt(el.id.replace('sri_','')); const nom=document.getElementById('rf_np'+num)?.value||''; const tipo=document.getElementById('rf_ts'+num)?.value||''; if(idx===1){ data['Nombre Persona']=nom; data['Tipo de Seguimiento']=tipo; } else { data['Nombre Persona '+idx]=nom; data['Tipo de Seguimiento '+idx]=tipo; } idx++; }); // Limpiar seguimientos no usados for(let i=idx;i<=10;i++){ if(i===1){if(!data['Nombre Persona']&&!data['Tipo de Seguimiento'])continue;} else {data['Nombre Persona '+i]='';data['Tipo de Seguimiento '+i]='';} } if(!data['Codigo']){toast('Selecciona un código de líder','wa');return;} if(!data['Fecha']){toast('La fecha es requerida','wa');return;} data.token=getToken(); loading(true);call('saveReporte',data,(_,res)=>{loading(false);if(res?.ok){toast('Reporte guardado','ok');closeModal();loadRep();}else toast('Error: '+(res?.msg||''),'er');}); } function delRep(id){customConfirm('¿Estás seguro de eliminar este reporte? Esta acción no se puede deshacer.',function(ok){if(ok){loading(true);call('deleteReporte',{id},(_,res)=>{loading(false);if(res?.ok){toast('Eliminado','ok');loadRep();}else toast('Error al eliminar','er');});}},{icon:'fa-trash-alt',color:'#e74c3c',danger:true});} function printRep(id){ const r=_rep.find(x=>x['ID']===id);if(!r)return; const isPend=r['Ofrenda Recibida']==='Pendiente'||r['Ofrenda Recibida']===''; var reunionTipo=r['Tipo de Reporte']||''; if(reunionTipo==='Reporte Digital'||reunionTipo==='Reporte Físico')reunionTipo='Mixta (Reunión Regular)'; const h=`
⛪ ${esc(APP.config.nombre||'Iglesia Restauración')}  |  ${fmtD(r['Fecha'])} · Código: ${r['Codigo']}
${[['Líder',r['Lider'],''],['Sup. Sector',r['Sup Sector'],''],['Anfitrión',r['Anfitrion'],''],['Fecha',fmtD(r['Fecha']),''],['Hora',r['Hora Inicio']+' – '+r['Hora Final'],''],['AGF',r['Asistencia Grupo Familiar'],''],['H/Am/N',r['Hnos']+'/'+r['Amigos']+'/'+r['Niños'],''],['Total Cultos',r['Total'],''],['M/J/D/Otros',r['Martes']+'/'+r['Jueves']+'/'+r['Domingo']+'/'+r['Otros'],''],['Ofr.Iglesia','Q '+r['Ofrenda Iglesia'],''],['Ofr.Bus','Q '+r['Ofrenda Bus'],''],['Ofr.Total','Q '+r['Ofrenda Total'],''],['Seg.1',(r['Nombre Persona']||''),''],['Seg.2',(r['Nombre Persona 2']||''),''],['Seg.3',(r['Nombre Persona 3']||''),''],['Seg.4',(r['Nombre Persona 4']||''),''],['Seg.5',(r['Nombre Persona 5']||''),''],['Seg.6',(r['Nombre Persona 6']||''),''],['Seg.7',(r['Nombre Persona 7']||''),''],['Seg.8',(r['Nombre Persona 8']||''),''],['Seg.9',(r['Nombre Persona 9']||''),''],['Seg.10',(r['Nombre Persona 10']||''),''],['Reporte',r['Reporte']||(r['Tipo de Reporte']==='Reporte Digital'?'Digital':r['Tipo de Reporte']==='Reporte Físico'?'Físico':''),''],['Tipo Reunión',reunionTipo,''],['Ofrenda Recibida',isPend?'⚠ PENDIENTE':r['Ofrenda Recibida'],isPend?'pend':'']].map(([k,v,cl])=>`
${k}
${esc(v)||'—'}
`).join('')}
`; const w=abrirVentanaImpresion(h); } function imprimirReportes(){loading(true);call('generarReporte',{desde:g('rD'),hasta:g('rH'),lider:g('rL')},(_,res)=>{loading(false);if(!res?.ok){toast('Error','er');return;}abrirVentanaImpresion(res.html);if(res.pdfUrl)toast('PDF guardado en Drive','ok',4000);});} // Función central de impresión — compatible web y ejecutable function abrirVentanaImpresion(html){ // Inyectar script de impresión robusto que no cierra la ventana prematuramente const printScript='window.addEventListener(\'load\',function(){setTimeout(function(){try{window.print();}catch(e){}},600);});<'+'/scr'+'ipt>'; const htmlConPrint=html.replace('',printScript+''); const w=window.open('','_blank','width=1200,height=800,scrollbars=yes,resizable=yes'); if(!w){ // Fallback: blob URL para ejecutable/entornos sin popups try{ const blob=new Blob([htmlConPrint],{type:'text/html;charset=utf-8'}); const url=URL.createObjectURL(blob); window.open(url,'_blank'); }catch(e){toast('Habilita ventanas emergentes para imprimir','wa',6000);} return; } w.document.open();w.document.write(htmlConPrint);w.document.close(); } // ══════════════════════════════════════════════════════════ // REPORTE DIGITAL // ══════════════════════════════════════════════════════════ function renderReporteDigital(el){ el.innerHTML=`

Reporte Digital

📱

Link del Formulario Digital

Comparte este enlace con tus líderes para que llenen el reporte desde su celular. Los datos llegan directamente al sistema marcados como ⚠ Pendiente hasta que confirmes la ofrenda.

${APP.formUrlPublic||'Cargando...'}
Abrir
Reportes Digitales (últimos 20)
`; call('getFormUrl',{},(_, res)=>{if(res?.ok){APP.formUrlPublic=res.url;const d=document.getElementById('rdUrlDisp');if(d)d.textContent=res.url;const a=document.getElementById('rdAbrirBtn');if(a)a.href=res.url;}}); call('getReportes',{},(_, d)=>{ const el2=document.getElementById('rdList');if(!el2)return; const data=(Array.isArray(d)?d:[]).filter(function(r){return (r['Reporte']||(r['Tipo de Reporte']==='Reporte Digital'?'Digital':''))==='Digital';}).slice(-20).reverse(); if(!data.length){el2.innerHTML=`

Sin reportes digitales aún

`;return;} el2.innerHTML=`
${data.map(r=>{const isPend=r['Ofrenda Recibida']==='Pendiente'||r['Ofrenda Recibida']==='';return ``;}).join('')}
FechaCódigoLíderAGFOfr.TotalOfrenda RecibidaAcción
${fmtD(r['Fecha'])}${esc(r['Codigo'])}${esc(r['Lider'])}${r['Asistencia Grupo Familiar']||0}${r['Ofrenda Total']||''}${isPend?'⚠ Pendiente':`✓ Recibida`}${isPend?``:'—'}
`; }); } // ══════════════════════════════════════════════════════════ // FORMULARIO EXTERNO // ══════════════════════════════════════════════════════════ function renderFormulario(el){ el.innerHTML=`

Formulario Externo

Nueva pestaña

Cargando formulario...

`; call('getFormHtml',{},function(_,res){ var cont=document.getElementById('formEmbed');if(!cont)return; if(!res||!res.ok){cont.innerHTML='

Error al cargar: '+(res&&res.msg||'')+'

';return;} var ifr=document.createElement('iframe');ifr.style='width:100%;height:calc(100vh - 200px);min-height:500px;border:none;border-radius:8px'; cont.innerHTML='';cont.appendChild(ifr); ifr.contentDocument.open();ifr.contentDocument.write(res.html);ifr.contentDocument.close(); }); } // ══════════════════════════════════════════════════════════ // GENERADOR DE REPORTES // ══════════════════════════════════════════════════════════ let _genData=null; function renderGenerador(el){ const supSects=[...new Set(APP.hermanos.map(h=>h['Sup SectorL']).filter(Boolean))].sort(); const supAreas=[...new Set(APP.hermanos.map(h=>h['Sup AreaL']).filter(Boolean))].sort(); var dists=[...new Set(APP.hermanos.map(function(h){return h['Distrito']}).filter(Boolean))].sort(); var zonas=[...new Set(APP.hermanos.map(function(h){return h['Zona']}).filter(Boolean))].sort(); el.innerHTML=`

Generador de Reportes e Informe Exprés

Configuración del Reporte
Repositorio de Reportes
Panel de Previsualización y Control Contable

Configure los filtros o presione "Ver Reporte Exprés de Cuadre"

`; loadRepo(); } // ── CARGA EL REPOSITORIO DE REPORTES GENERADOS ──────────────── function toggleRepoSort(){ window._repoAsc=!window._repoAsc; var btn=document.getElementById('repoSortBtn'); if(btn)btn.innerHTML=(window._repoAsc?' Asc':' Desc'); loadRepo(); } function loadRepo(){ var el2=document.getElementById('genRepo'); if(!el2) return; call('getGeneradores',{},function(_,d){ var data=Array.isArray(d)?d:[]; if(!data.length){el2.innerHTML='

Sin reportes generados

';return;} // Ordenar por No Serie descendente (ultimo creado primero) window._repoAsc=window._repoAsc||false; data.sort(function(a,b){var va=a['No Serie']||'',vb=b['No Serie']||'';return window._repoAsc?va.localeCompare(vb):vb.localeCompare(va);}); // Aplicar filtro por fecha exacta var ff=document.getElementById('repoFiltroFecha'); if(ff&&ff.value) data=data.filter(function(r){return r['Fecha Inicio']===ff.value||r['Fecha Fin']===ff.value;}); if(!data.length){el2.innerHTML='

Sin reportes para los filtros seleccionados

';return;} el2.style.maxHeight='calc(100vh - 420px)';el2.style.overflowY='auto'; el2.innerHTML=data.map(function(r){ var hasPdf=r['Archivo Generado']&&(r['Archivo Generado'].startsWith('http')||r['Archivo Generado'].includes('drive')); var id='ri_'+String(r['No Serie']||'').replace(/[^A-Z0-9]/gi,''); var sid=esc(r['No Serie']);var sdesde=esc(r['Fecha Inicio']);var shasta=esc(r['Fecha Fin']); var slid=esc(r['Filtro Lider']||'');var ssec=esc(r['Filtro Sup Sector']||'');var sarea=esc(r['Filtro Sup Area']||''); var stitu=esc(r['Titulo de Reporte']);var spdf=hasPdf?esc(r['Archivo Generado']):''; return '
' +'
' +'
' +'
'+esc(r['No Serie'])+''+(hasPdf?' \u2713 PDF':'')+'
' +'
'+esc(r['Titulo de Reporte'])+' \u00b7 '+fmtD(r['Fecha Inicio'])+' \u2192 '+fmtD(r['Fecha Fin'])+'
' +'
' +'
' +(hasPdf?'':'') +'' +'
' +'
'; }).join(''); }); } function clickRepo(el){ previewRepoItem(el.dataset.serie,el.dataset.desde,el.dataset.hasta,el.dataset.lider,el.dataset.supsec,el.dataset.suparea,el.dataset.titulo,el.dataset.pdf); } function clickRepoPrint(el){ var p=el.parentElement.parentElement; regenImprimir(p.dataset.desde,p.dataset.hasta,p.dataset.lider,p.dataset.titulo); } function previewRepoItem(serie,desde,hasta,lider,supSector,supArea,titulo,pdfUrl){ var prev=document.getElementById('genPreview');if(!prev)return; var acts=document.getElementById('genPrevActions'); // Si ya existe PDF guardado: mostrarlo directamente sin generar nada nuevo if(pdfUrl&&pdfUrl.length>10){ if(acts)acts.innerHTML= ' Ver PDF en Drive'; var viewUrl=pdfUrl; if(pdfUrl.includes('drive.google.com/file/d/')){ var fileId=pdfUrl.match(/\/d\/([a-zA-Z0-9_-]+)/); if(fileId)viewUrl='https://drive.google.com/file/d/'+fileId[1]+'/preview'; } else if(pdfUrl.includes('id=')){ var idMatch=pdfUrl.match(/id=([a-zA-Z0-9_-]+)/); if(idMatch)viewUrl='https://drive.google.com/file/d/'+idMatch[1]+'/preview'; } prev.innerHTML=''; toast('PDF de '+serie,'in'); return; } // Sin PDF guardado: generar preview HTML (sin crear archivo ni registro) prev.innerHTML='

Generando vista previa...

'; if(acts)acts.innerHTML=''; call('generarReporte',{desde:desde,hasta:hasta,lider:lider,supSector:supSector,supArea:supArea,pastorZona:g('gPZ')||'',tipo:titulo,_noGuardar:true},function(_,res){ if(!res||!res.ok){prev.innerHTML='

Error cargando vista previa

';return;} _lastGenHtml=res.html;_lastGenSerie=serie; var ifr=document.createElement('iframe');ifr.style='width:100%;height:560px;border:none;border-radius:8px'; prev.innerHTML='';prev.appendChild(ifr); ifr.contentDocument.open();ifr.contentDocument.write(res.html);ifr.contentDocument.close(); toast('Vista previa de '+serie,'in'); }); } // ── VISTA EXPRÉS DE CUADRE (Modal con checkboxes multi-select) ─────────── function cargarCuadreExpresDistrital(){ var distritos = [...new Set(APP.hermanos.map(function(h){return h["Distrito"]}).filter(Boolean))].sort(); var zonas = [...new Set(APP.hermanos.map(function(h){return h["Zona"]}).filter(Boolean))].sort(); if(!distritos.length){toast('No hay hermanos cargados','wa');return;} var hoy = new Date(); var diaSem = hoy.getDay(); var diffLun = hoy.getDate() - diaSem + (diaSem === 0 ? -6 : 1); var lun = new Date(hoy); lun.setDate(diffLun); var dom = new Date(lun); dom.setDate(lun.getDate() + 6); var fDesde = lun.toISOString().split('T')[0]; var fHasta = dom.toISOString().split('T')[0]; var checkDist = distritos.map(function(d){ return ''; }).join(''); var checkZonas = zonas.map(function(z){ return ''; }).join(''); openModal('📊 Reporte Exprés de Cuadre Dominical', '
' +'
' +'
Ciclo Semanal Actual
' +'
'+fmtD(fDesde)+' → '+fmtD(fHasta)+' (Lun-Dom)
' +'
⏰ Cierre semanal: Domingo 11:59 PM · Esta vista es informativa para cuadre de ofrendas, no genera PDF.
' +'
' +'
' +'
'+checkDist+'
' +'
' +'
'+checkZonas+'
' +'
' +'

Selecciona distrito(s) y zona(s), luego presiona Calcular

' +'
', '' +'' ,'xl'); } function ejecutarCuadreExpres(){ var distritos = []; var zonas = []; document.querySelectorAll('.cdDist:checked').forEach(function(el){distritos.push(el.value);}); document.querySelectorAll('.cdZona:checked').forEach(function(el){zonas.push(el.value);}); if(!distritos.length && !zonas.length){toast('Selecciona al menos un distrito o zona','wa');return;} var res = document.getElementById('cuadreExpresResult'); if(!res) return; res.innerHTML = '

Calculando...

'; var hoy = new Date(); var diaSem = hoy.getDay(); var diffLun = hoy.getDate() - diaSem + (diaSem === 0 ? -6 : 1); var lun = new Date(hoy); lun.setDate(diffLun); var dom = new Date(lun); dom.setDate(lun.getDate() + 6); var fDesde = lun.toISOString().split('T')[0]; var fHasta = dom.toISOString().split('T')[0]; var payload = {desde:fDesde, hasta:fHasta}; if(distritos.length === 1) payload.distrito = distritos[0]; else if(distritos.length > 1) payload.distritos = distritos; if(zonas.length === 1) payload.zona = zonas[0]; else if(zonas.length > 1) payload.zonas = zonas; call('obtenerVistazoDistritalDominical', payload, function(err, resData){ if(err || !resData || !resData.ok){ res.innerHTML = '
No se pudo procesar. Error: '+(err?.message||resData?.msg||'desconocido')+'
'; return; } var rows = resData.data || []; if(!rows.length){ res.innerHTML = '

Sin datos para el período seleccionado

'; return; } var tRptes=0, tDig=0, tDigOk=0, tDigPend=0, tSobres=0, tMDig=0, tMOfrenda=0, tMPend=0, tMFis=0; rows.forEach(function(r){ tRptes+=Number(r.reportes||0); tDig+=Number(r.digitales||0); tDigOk+=Number(r.digitalConOfrenda||0); tDigPend+=Number(r.digitalSinOfrenda||0); tSobres+=Number(r.sobres||0); tMDig+=Number(r.montoDigital||0); tMOfrenda+=Number(r.montoConOfrenda||0); tMPend+=Number(r.montoSinOfrenda||0); tMFis+=Number(r.montoFisico||0); }); var totalGen = tMOfrenda + tMFis; var tableRows = rows.map(function(r){ var pend = Number(r.pendientes||0); var estilo = pend > 0 ? 'background:#fff5f5;' : ''; var totalRow = Number(r.montoConOfrenda||0) + Number(r.montoFisico||0); return '' +''+esc(r.distrito||'—')+'' +''+esc(r.zona||'—')+'' +''+r.reportes+'' +''+r.digitales+'' +''+r.digitalConOfrenda+'' +'Q '+Number(r.montoConOfrenda||0).toFixed(2)+'' +''+(r.digitalSinOfrenda||'0')+'' +'Q '+Number(r.montoSinOfrenda||0).toFixed(2)+'' +''+r.sobres+'' +'Q '+Number(r.montoFisico||0).toFixed(2)+'' +'Q '+totalRow.toFixed(2)+'' +''+(pend>0?'⚠ '+pend+'':'')+'' +''; }).join(''); res.innerHTML = '
' +'
' +'
Cuadre de Ofrendas vs Reportes
' +'
'+fmtD(fDesde)+' → '+fmtD(fHasta)+'
' +'
' +[['Total Rptes',tRptes,'#1a3a5c'],['Digitales',tDig,'#2563a8'],['Con Ofrenda ✓',tDigOk,'var(--ok)'],['Sin Ofrenda ⚠',tDigPend,'var(--err)'],['Recibido Q','Q '+totalGen.toFixed(2),'#c87f00']] .map(function(k){return '
'+k[1]+'
'+k[0]+'
';}).join('') +'
' +'
' +'' +'' +'' +tableRows +'' +'' +'' +'' +'' +'' +'' +'' +'' +'' +'
DistZonaRptesDigitalesFísicoTotal QPend
EnviadosCon OfrendaMontoSin OfrendaSobresMonto
TOTALES'+tRptes+''+tDig+''+tDigOk+'Q '+tMOfrenda.toFixed(2)+''+tDigPend+'Q '+tMPend.toFixed(2)+''+tSobres+'Q '+tMFis.toFixed(2)+'Q '+totalGen.toFixed(2)+''+(tDigPend>0?'⚠ '+tDigPend:'✓')+'
' +'
' +'✔ Cuadrado: Digitales con Ofrenda + Sobres Físicos = Q '+totalGen.toFixed(2)+' · ' +'✘ Pendiente: Q '+tMPend.toFixed(2)+' en ofrendas digitales no recibidas físicamente aún' +'
'; }); } function guardarRepoPdf(noSerie,desde,hasta,lider,titulo,btn){ if(btn){btn.disabled=true;btn.innerHTML='';} loading(true); call('generarReporte',{desde:desde,hasta:hasta,lider:lider,supSector:supSector||'',supArea:supArea||'',pastorZona:g('gPZ')||'',tipo:titulo},function(_,res){ loading(false); if(!res||!res.ok){ if(btn){btn.disabled=false;btn.innerHTML='';} toast('Error generando: '+(res&&res.msg||''),'er');return; } if(res.pdfUrl){ toast('PDF guardado: '+noSerie,'ok',5000);loadRepo(); if(btn)btn.outerHTML=''; } else { call('guardarPdfAction',{html:res.html,noSerie:res.noSerie},function(_,r2){ if(r2&&r2.ok&&r2.pdfUrl){ toast('PDF guardado: '+noSerie,'ok',5000);loadRepo(); if(btn)btn.outerHTML=''; } else { if(btn){btn.disabled=false;btn.innerHTML='';} toast('No se pudo guardar PDF','wa',5000); } }); } }); } function regenImprimir(desde,hasta,lider,titulo){ loading(true);call('generarReporte',{desde,hasta,lider,supSector:g('gSS')||'',supArea:g('gSA')||'',pastorZona:g('gPZ')||'',tipo:titulo,_noGuardar:true},(_,res)=>{loading(false);if(!res?.ok){toast('Error','er');return;}abrirVentanaImpresion(res.html);}); } let _lastGenHtml='';let _lastGenSerie=''; function genReporte(){ loading(true); call('generarReporte',{desde:g('gD'),hasta:g('gH'),lider:g('gL'),supSector:g('gSS'),supArea:g('gSA'),pastorZona:g('gPZ')||'',distrito:g('gDist')||'',zona:g('gZona')||'',tipo:g('gTit')},(_,res)=>{ loading(false); if(!res?.ok){toast('Error: '+(res?.msg||''),'er');return;} _lastGenHtml=res.html;_lastGenSerie=res.noSerie; const prev=document.getElementById('genPreview');if(!prev)return; const ifr=document.createElement('iframe'); ifr.style='width:100%;height:450px;border:none;border-radius:8px'; prev.innerHTML='';prev.appendChild(ifr); ifr.contentDocument.open();ifr.contentDocument.write(res.html);ifr.contentDocument.close(); // Barra de acciones const bar=document.createElement('div'); bar.style='display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center;padding:10px;background:var(--bg3);border-radius:8px'; if(res.pdfUrl){ bar.innerHTML=' PDF guardado: '+esc(res.noSerie)+'.pdf' +' Ver en Drive' +''; } else { bar.innerHTML='' +''; } prev.appendChild(bar); loadRepo(); toast('Reporte generado — '+res.noSerie,'ok'); }); } function guardarEnDrive(btn){ if(!_lastGenHtml||!_lastGenSerie){toast('Genera un reporte primero','wa');return;} if(btn){btn.disabled=true;btn.innerHTML=' Guardando...';} loading(true); call('guardarPdfAction',{html:_lastGenHtml,noSerie:_lastGenSerie},(_,res)=>{ loading(false); if(btn)btn.disabled=false; if(res&&res.ok&&res.pdfUrl){ if(btn)btn.outerHTML=' PDF en Drive'; toast('PDF guardado correctamente','ok',5000); loadRepo(); } else { if(btn)btn.innerHTML=' Reintentar'; toast('Error: '+(res&&res.msg?res.msg:'Drive no configurado'),'er',7000); } }); } function dlExcelGen(){loading(true);call('getReportes',{desde:g('gD'),hasta:g('gH'),lider:g('gL'),supSector:g('gSS'),supArea:g('gSA')},(_,d)=>{loading(false);exportXLS(Array.isArray(d)?d:[],'Reportes','reporte_'+today());});} function imprimirGen(){ if(_lastGenHtml){abrirVentanaImpresion(_lastGenHtml);return;} loading(true); call('generarReporte',{desde:g('gD'),hasta:g('gH'),lider:g('gL'),supSector:g('gSS'),supArea:g('gSA'),pastorZona:g('gPZ')||'',tipo:g('gTit'),_noGuardar:true},(_,res)=>{ loading(false);if(!res?.ok){toast('Error','er');return;} _lastGenHtml=res.html;_lastGenSerie=res.noSerie; abrirVentanaImpresion(res.html); }); } // === HERMANOS LIDERES v6 === var _herm=[]; function renderHermanos(el){ el.innerHTML = `

Hermanos Lideres

Codigo: DnZnALSnGn Ej:11A11
CodigoNombreDZASGSup SectorPastor Zona
`; loadHerm(); } var _hermQ=''; function loadHerm(){call('getHermanos',{},function(_,d){_herm=Array.isArray(d)?d:[];APP.hermanos=_herm;var ds=[...new Set(_herm.map(function(h){return h['Distrito'];}).filter(Boolean))].sort();var zs=[...new Set(_herm.map(function(h){return h['Zona'];}).filter(Boolean))].sort();var dsel=document.getElementById('hFD'),zsel=document.getElementById('hFZ');if(dsel)dsel.innerHTML=''+ds.map(function(d){return '';}).join('');if(zsel)zsel.innerHTML=''+zs.map(function(z){return '';}).join('');renderHermTb(_herm);});} function filterHerm(q){_hermQ=q||'';filterHermTable();} function filterHermTable(){var q=_hermQ.toLowerCase(),dist=(document.getElementById('hFD')||{}).value||'',zona=(document.getElementById('hFZ')||{}).value||'';renderHermTb(_herm.filter(function(h){if(dist&&h['Distrito']!==dist)return false;if(zona&&h['Zona']!==zona)return false;if(q&&!JSON.stringify(h).toLowerCase().includes(q))return false;return true;}));} function renderHermTb(d){var tb=document.getElementById('hermTb');if(!tb)return;if(!d.length){tb.innerHTML='

Sin lideres

';return;}tb.innerHTML=d.map(function(h){return ''+esc(h['CodigoL'])+''+esc(h['NombreL'])+''+esc(h['Distrito']||'')+''+esc(h['Zona']||'')+''+esc(h['Area']||'')+''+esc(h['Sector']||'')+''+esc(h['Grupo']||'')+''+esc(h['Sup SectorL']||'-')+''+esc(h['Pastor Zona']||'-')+'
';}).join('');} function hermForm(h){h=h||{};var svs=[];var pas=[];var aas=[];try{svs=JSON.parse(localStorage.getItem('_svs_c')||'[]');}catch(ex){}try{pas=JSON.parse(localStorage.getItem('_pas_c')||'[]');}catch(ex){}try{aas=JSON.parse(localStorage.getItem('_aas_c')||'[]');}catch(ex){}return '
Jerarquia D(n)Z(n)A(L)S(n)G(n) Ej:11A11
Codigo:'+esc(h['CodigoL']||'??')+'
Datos del Lider
Supervision
'+svs.map(function(s){return '';}).join('')+'
'+pas.map(function(p){return '';}).join('')+'
'+aas.map(function(a){return '';}).join('')+'
';} function actualizarDatalistsForm() { try { var svs = JSON.parse(localStorage.getItem('_svs_c') || '[]'); var pas = JSON.parse(localStorage.getItem('_pas_c') || '[]'); var aas = JSON.parse(localStorage.getItem('_aas_c') || '[]'); var ssl = document.getElementById('hf_ssl'); var pzl = document.getElementById('hf_pzl'); var sal = document.getElementById('hf_sal'); if (ssl && svs.length) { ssl.innerHTML = svs.map(function(s){ return ''; }).join(''); } if (pzl && pas.length) { pzl.innerHTML = pas.map(function(p){ return ''; }).join(''); } if (sal && aas.length) { sal.innerHTML = aas.map(function(a){ return ''; }).join(''); } } catch(e) { console.error("Error al actualizar listas", e); } } function newHerm(){ openModal('Nuevo Lider', hermForm(), '','xl'); actualizarDatalistsForm(); call('getSupervisores',{},function(_,r){ if(r&&r.ok&&r.data){ try{localStorage.setItem('_svs_c',JSON.stringify(r.data.map(function(s){return{c:s['CodigoSup'],n:s['NombreSup']};})));}catch(ex){} actualizarDatalistsForm(); } }); call('getPastores',{},function(_,rp){ if(rp&&rp.ok&&rp.data){ try{localStorage.setItem('_pas_c',JSON.stringify(rp.data.map(function(p){return{c:p['CodigoPastor'],n:p['NombrePastor']};})));}catch(ex){} actualizarDatalistsForm(); } }); call('getAreaSupervisores',{},function(_,ra){ if(ra&&ra.ok&&ra.data){ try{localStorage.setItem('_aas_c',JSON.stringify(ra.data));}catch(ex){} actualizarDatalistsForm(); } }); } function editHerm(id){ var h=_herm.find(function(x){return x['ID']===id;}); if(!h)return; openModal('Editar Lider', hermForm(h), ``,'xl'); actualizarDatalistsForm(); call('getSupervisores',{},function(_,r){ if(r&&r.ok&&r.data) { try{localStorage.setItem('_svs_c',JSON.stringify(r.data.map(function(s){return{c:s['CodigoSup'],n:s['NombreSup']};})));}catch(ex){} actualizarDatalistsForm(); } call('getPastores',{},function(_,rp){ if(rp&&rp.ok&&rp.data) { try{localStorage.setItem('_pas_c',JSON.stringify(rp.data.map(function(p){return{c:p['CodigoPastor'],n:p['NombrePastor']};})));}catch(ex){} actualizarDatalistsForm(); } call('getAreaSupervisores',{},function(_,ra){ if(ra&&ra.ok&&ra.data){ try{localStorage.setItem('_aas_c',JSON.stringify(ra.data));}catch(ex){} actualizarDatalistsForm(); } }); }); }); } function saveHerm(id){var cod=(document.getElementById('hf_codigo')||{}).value||'',nom=(document.getElementById('hf_nombre')||{}).value||'',pastor=(document.getElementById('hf_pastor')||{}).value||'',supSec=(document.getElementById('hf_supSec')||{}).value||'';if(!nom){toast('Nombre requerido','wa');return;}if(!cod||cod==='??'){previewCodigo();cod=(document.getElementById('hf_codigo')||{}).value||'';}if(!cod){toast('Completa 5 campos','wa');return;}if(!pastor){toast('Pastor Zona requerido','wa');return;}var cp='';var cs='';try{var pz=JSON.parse(localStorage.getItem('_pas_c')||'[]');var pz2=pz.find(function(p){return p.n===pastor;});if(pz2)cp=pz2.c;}catch(ex){}try{var ss=JSON.parse(localStorage.getItem('_svs_c')||'[]');var ss2=ss.find(function(s){return s.n===supSec;});if(ss2)cs=ss2.c;}catch(ex){}loading(true);call('saveHermano',{ID:id||'',CodigoL:cod,NombreL:nom,Distrito:(document.getElementById('hf_dist')||{}).value||'',Zona:(document.getElementById('hf_zona')||{}).value||'',Area:((document.getElementById('hf_area')||{}).value||'').toUpperCase(),Sector:(document.getElementById('hf_sector')||{}).value||'',Grupo:(document.getElementById('hf_grupo')||{}).value||'',Anfitrion:(document.getElementById('hf_anf')||{}).value||'',Direccion:(document.getElementById('hf_dir')||{}).value||'','Sup SectorL':supSec,'Sup AreaL':(document.getElementById('hf_supArea')||{}).value||'','Pastor Zona':pastor,CodigoPastor:cp,CodigoSup:cs},function(_,r){loading(false);if(r&&r.ok){toast('Lider: '+cod,'ok',4000);closeModal();loadHerm();}else toast('Error: '+(r&&r.msg||''),'er');});} function delHerm(id){customConfirm('Eliminar lider?',function(ok){if(!ok)return;loading(true);call('deleteHermano',{id:id},function(_,r){loading(false);if(r&&r.ok){toast('Eliminado','ok');loadHerm();}});},{icon:'fa-trash',danger:true});} // ══════════════════════════════════════════════════════════ // PRIVILEGIOS — Lista por área + Cronograma // ══════════════════════════════════════════════════════════ var PRV_TIPOS_DEFAULT=['Predicador','Maestro de Escuela Dominical','Coordinador de Área','Diácono','Ujier','Ministracion','Protocolo','Líder de Adoración','Tesorero de Grupo','Secretario','Coordinador de Jóvenes','Coordinador de Niños','Evangelista','Consejero']; var PRV_TIPOS=PRV_TIPOS_DEFAULT; // alias function getPrvTipos(){ try{ var cfg=APP.config&&APP.config.prvTipos; if(cfg&&cfg.length)return cfg; }catch(e){} return PRV_TIPOS_DEFAULT; } function getPrvAreas(){ var areas=[]; if(APP.hermanos&&APP.hermanos.length){ var fromH=[...new Set(APP.hermanos.map(function(h){return h['Area']||'';}).filter(Boolean))].sort(); areas=areas.concat(fromH); } try{ var cfgA=APP.config&&APP.config.prvAreas; if(cfgA&&cfgA.length)cfgA.forEach(function(a){if(!areas.includes(a))areas.push(a);}); }catch(e){} if(!areas.length)areas=['A','B','C','D','E','General']; return areas; } const PRV_AREAS=['Área A','Área B','Área C','Área D','Área E','General','Sin Área']; let _prv=[];let _prvTab='lista'; function renderPrivilegios(el){ el.innerHTML=`

Privilegios

`; loadPrv(); } function switchPrvTab(tab){ _prvTab=tab; ['lista','area','cron'].forEach(t=>{ const b=document.getElementById('ptab_'+t);if(!b)return; const active=t===tab; b.style.background=active?'#fff':'var(--bg3)'; b.style.color=active?'var(--pr)':'var(--tx2)'; b.style.borderBottom=active?'2px solid var(--pr)':'2px solid transparent'; }); renderPrvContent(); } function loadPrv(){call('getPrivilegios',{},(_, d)=>{_prv=Array.isArray(d)?d:[];renderPrvContent();});} function renderPrvContent(){ const c=document.getElementById('prvContent');if(!c)return; if(_prvTab==='lista') renderPrvLista(c); else if(_prvTab==='area') renderPrvPorArea(c); else renderPrvCronograma(c); } function renderPrvLista(c){ c.innerHTML=`
HermanoÁreaPrivilegioInicioFinEstadoObservacionesAcciones
`; renderPrvTb(_prv); } function renderPrvTb(data){ const tb=document.getElementById('prvTb');if(!tb)return; const tipo=g('prvFiltTipo');const est=g('prvFiltEstado'); let d=data.filter(p=>{ if(tipo&&p['Privilegio']!==tipo)return false; if(est&&p['Activo']!==est)return false; return true; }); if(!d.length){tb.innerHTML=`

Sin privilegios

`;return;} tb.innerHTML=d.map(p=>` ${esc(p['Hermano'])} ${esc(p['Area']||p['CodigoL']||'—')} ${esc(p['Privilegio'])} ${fmtD(p['FechaInicio'])} ${fmtD(p['FechaFin'])||'—'} ${p['Activo']==='SI'?'Activo':'Inactivo'} ${esc(p['Observaciones']||'—')}
`).join(''); } function filterPrvLista(q){renderPrvTb(_prv.filter(p=>JSON.stringify(p).toLowerCase().includes(q.toLowerCase())));} function renderPrvPorArea(c){ const activos=_prv.filter(p=>p['Activo']==='SI'); const byArea={}; activos.forEach(p=>{ const a=p['Area']||'General'; if(!byArea[a])byArea[a]=[]; byArea[a].push(p); }); // Agrupar también por tipo de privilegio para cada área const areas=Object.keys(byArea).sort(); c.innerHTML=`
${areas.length?areas.map(area=>{ const miembros=byArea[area]; const byTipo={}; miembros.forEach(m=>{if(!byTipo[m['Privilegio']])byTipo[m['Privilegio']]=[];byTipo[m['Privilegio']].push(m);}); return `
${esc(area)} (${miembros.length} hermanos)
${Object.entries(byTipo).map(([tipo,lista])=>`
${esc(tipo)}
${lista.map(m=>`
${esc(m['Hermano'])}
`).join('')}
`).join('')}
`; }).join(''):`

Sin privilegios activos

`}
`; } let _cronData=[]; function renderPrvCronograma(c){ c.innerHTML=`

Horarios de Servicio
${(function(){var svcs=getServiciosDinamicos();return svcs.map(function(d){return '
'+esc(d.label)+'
';}).join('');})()}
Resumen
Cronograma de Servicios
`; loadCronograma(); } function loadCronograma(){ call('getCronograma',{},(_, res)=>{ if(!res?.ok){toast('Error cargando cronograma','er');return;} _cronData=res.data||[]; renderCronTable(_cronData); renderCronResumen(_cronData); // Poblar filtros const areas=[...new Set(_cronData.map(d=>d['Area']).filter(Boolean))].sort(); const servs=[...new Set(_cronData.map(d=>d['Servicio']||d['Privilegio']).filter(Boolean))].sort(); const fa=document.getElementById('cronFiltArea');const fs=document.getElementById('cronFiltServ'); if(fa)areas.forEach(a=>{const o=document.createElement('option');o.textContent=a;fa.appendChild(o);}); if(fs)servs.forEach(s=>{const o=document.createElement('option');o.textContent=s;fs.appendChild(o);}); }); } function filtrarCron(){ const area=g('cronFiltArea');const serv=g('cronFiltServ'); let d=_cronData; if(area)d=d.filter(x=>x['Area']===area); if(serv)d=d.filter(x=>(x['Servicio']||x['Privilegio'])===serv); renderCronTable(d); } function renderCronResumen(data){ const el=document.getElementById('cronResumen');if(!el)return; const activos=data.filter(d=>d['Activo']!=='NO'); const byServ={};activos.forEach(d=>{const s=d['Servicio']||d['Privilegio']||'Otro';if(!byServ[s])byServ[s]=0;byServ[s]++;}); el.innerHTML=Object.entries(byServ).slice(0,4).map(([s,n])=>`
${s}${n}
`).join('')||'

Sin datos

'; } function renderCronTable(data){ const tb=document.getElementById('cronTable');if(!tb)return; if(!data.length){tb.innerHTML=`

Sin asignaciones. Agrega con "+ Asignar Servicio"

`;return;} const svcs=getServiciosDinamicos(); const thCols=svcs.map(s=>`${esc(s.label)}`).join(''); tb.innerHTML=`${thCols}${data.map(d=>{ const activo=d['Activo']!=='NO'; return ` ${svcs.map(s=>``).join('')} `; }).join('')}
HermanoÁreaServicioObs.EstadoAcciones
${esc(d['Hermano'])} ${esc(d['Area']||'—')} ${esc(d['Servicio']||d['Privilegio']||'—')}${d[s.key]==='SI'?'':''}${esc(d['Observaciones']||'')} ${activo?'Activo':'Inactivo'}
`; } function autoFillCronArea(nombre){ if(!nombre)return; // Buscar en privilegios primero var prv=_prv.find(function(p){return p['Hermano']===nombre&&p['Activo']==='SI';}); if(prv&&prv['Area']){ var aEl=document.getElementById('cr_area');if(aEl)aEl.value=prv['Area']; return; } // Luego en hermanos var h=APP.hermanos.find(function(x){return x['NombreL']===nombre;}); if(h&&h['Area']){var aEl2=document.getElementById('cr_area');if(aEl2)aEl2.value=h['Area'];} } function cronForm(d){ d=d||{}; var svcs=getServiciosDinamicos(); // Fuente de hermanos: primero los que tienen privilegios registrados, luego los de APP.hermanos var prvNombres=[]; if(_prv && _prv.length){ prvNombres=[...new Set(_prv.filter(function(p){return p['Activo']==='SI';}).map(function(p){return p['Hermano']||'';}).filter(Boolean))].sort(); } // También incluir líderes de APP.hermanos que no estén en privilegios var hermNombres=[...new Set((APP.hermanos||[]).map(function(h){return h['NombreL']||'';}).filter(Boolean))].sort(); // Opciones: primero los de privilegios (marcados), luego líderes no repetidos var allNombres=prvNombres.slice(); hermNombres.forEach(function(n){if(!allNombres.includes(n))allNombres.push(n);}); var hO=allNombres.map(function(nombre){ var enPriv=prvNombres.includes(nombre); var sel=d['Hermano']===nombre?' selected':''; var prefix=enPriv?'[★ P] ':''; return ''; }).join(''); var SERVS=getPrvTipos().concat(['Predicador','Maestro E.D.','Ujier','Líder Adoración','Servidor A/V','Coordinador','Diácono','Caporal','Secretario','Tesorero','Otro'].filter(function(s){return !getPrvTipos().includes(s);})); var diasHTML=svcs.map(function(x){ return '
'; }).join(''); return '
' +'
' +'' +'' +(prvNombres.length?prvNombres.map(function(n){return '' +(prvNombres.length?' Los nombres sugeridos pertenecen a la lista de privilegios y hermanos registrados.':'') +'
' +'
' +'' +'' +[...new Set((APP.hermanos||[]).map(function(h){return h['Area']||'';}).filter(Boolean))].sort().map(function(a){return '';}).join('') +'
' +'
' +'
' +'
Días de Servicio Asignados
' +'

Marca los servicios en que participará este hermano.

' +diasHTML +'
' +'
' +'
'; } function newCronItem(){openModal('Asignar Servicio',cronForm(),mFoot('saveCronItem'));} function editCronItem(id){ const d=_cronData.find(x=>x['ID']===id); openModal( 'Editar Asignación', cronForm(d), `` ); } function viewCronItem(id){ const d=_cronData.find(x=>x['ID']===id);if(!d)return; const svcs=getServiciosDinamicos(); const horBadges=svcs.map(s=>{ const v=d[s.key]==='SI'; return ` ${esc(s.label)}`; }).join(''); openModal('Detalle de Servicio — '+esc(d['Hermano']),`
${[['Hermano',d['Hermano']],['Área',d['Area']],['Servicio',d['Servicio']||d['Privilegio']],['Estado',d['Activo']==='NO'?'Inactivo':'Activo'],['Asignado',fmtD(d['FechaAsignacion'])],['Observaciones',d['Observaciones']||'—']].map(([k,v])=>`
${k}
${esc(v)||'—'}
`).join('')}
Horarios asignados
${horBadges}
`,``); } function saveCronItem(id){ var svcs=getServiciosDinamicos(); var data={ID:id||'',Hermano:g('cr_h'),Area:g('cr_area'),Servicio:g('cr_serv'),Privilegio:g('cr_serv'), FechaAsignacion:g('cr_fecha'),Observaciones:g('cr_obs'),Activo:g('cr_act')}; // Marcar cada servicio dinámico svcs.forEach(function(s){ var el=document.getElementById('cr_'+s.key); data[s.key]=el&&el.checked?'SI':'NO'; }); // Asegurar compatibilidad legacy con campos fijos ['Lunes','Jueves','Domingo_Mañana','Domingo_Tarde'].forEach(function(k){ if(data[k]===undefined)data[k]='NO'; }); if(!data.Hermano||!data.Area||!data.Servicio){toast('Hermano, área y servicio son requeridos','wa');return;} loading(true);call('saveCronograma',data,(_,res)=>{loading(false);if(res?.ok){toast('Guardado','ok');closeModal();loadCronograma();}else toast('Error: '+(res?.msg||''),'er');}); } function delCronItem(id){ customConfirm('¿Eliminar esta asignación?',function(ok){ if(!ok)return; loading(true); call('deleteCronograma',{id:id},function(_,res){ loading(false); if(res&&res.ok){toast('Eliminado','ok');loadCronograma();} }); },{icon:'fa-trash',danger:true}); } function abrirAICronograma(){ var cfg=APP.config||{}; var noKey=(!(cfg.gemini_api_key&&cfg.gemini_api_key.length>10))&&(!(cfg.openrouter_api_key&&cfg.openrouter_api_key.length>10))&&(!(cfg.deepseek_api_key&&cfg.deepseek_api_key.length>10)); var hoy=new Date(); var mes=['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'][hoy.getMonth()]; var body=''; if(noKey)body='
Configura tu API Key en Configuracion > Asistente IA.
'; body+='' +'' +'
Sugerencias:
' +'
' +'' +'' +'' +'
' +''; openModal('IA - Cronograma',body, '' +'','xl'); } function setCronQ(btn){var el=document.getElementById('aiCronQ');if(el)el.value=btn.textContent||btn.innerText||'';} function enviarAICronograma(){ var q=(document.getElementById('aiCronQ')||{}).value||''; if(!q.trim()){toast('Escribe tu consulta','wa');return;} var hoy=new Date(); var mes=['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'][hoy.getMonth()]; var respEl=document.getElementById('aiCronResp'); if(respEl){respEl.style.display='block';respEl.innerHTML=' Analizando...';} call('preguntarAICronograma',{pregunta:q,mes:mes,anio:hoy.getFullYear()},function(err,res){ if(!respEl)return; if(res&&res.ok){ respEl.innerHTML='
REDIL IA
'+esc(res.respuesta)+'
'; }else{ respEl.innerHTML=''+esc((res&&res.msg)||'Error conectando con IA')+''; } }); } function genCronPDF(){ const hoy=new Date(); const mes=['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'][hoy.getMonth()]; loading(true);call('generarCronogramaPDF',{mes,anio:hoy.getFullYear()},(_,res)=>{ loading(false); if(res && !res.ok){toast('Error generando','er');return;} abrirVentanaImpresion(res.html); if(res.pdfUrl)toast('PDF guardado en Drive','ok',4000); }); } function imprimirCronograma(){ const tb=document.getElementById('cronTable');if(!tb)return; const hoy=new Date(); const svcs=getServiciosDinamicos(); const horLabel=svcs.map(s=>s.label).join(' · '); const mes=['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'][hoy.getMonth()]; const h=`

⛪ Cronograma de Servicios — ${mes} ${hoy.getFullYear()}

${tb.innerHTML}

Horarios: ${horLabel} · Generado: ${new Date().toLocaleDateString('es-GT')}

setTimeout(()=>{window.print();setTimeout(()=>window.close(),400);},350);`; abrirVentanaImpresion(h); } function imprimirPrivilegios(){ const activos=_prv.filter(p=>p['Activo']==='SI'); const byArea={};activos.forEach(p=>{const a=p['Area']||'General';if(!byArea[a])byArea[a]=[];byArea[a].push(p);}); const h=`

📋 Listado de Privilegios Activos

${Object.entries(byArea).sort().map(([area,lista])=>`
${area} (${lista.length})
${lista.map(p=>``).join('')}
HermanoPrivilegioDesdeObservaciones
${esc(p['Hermano'])}${esc(p['Privilegio'])}${fmtD(p['FechaInicio'])}${esc(p['Observaciones']||'')}
`).join('')}

Generado: ${new Date().toLocaleDateString('es-GT')}

setTimeout(()=>{window.print();setTimeout(()=>window.close(),400);},350);`; abrirVentanaImpresion(h); } function prvForm(p){ p=p||{}; const hO=(APP.hermanos||[]).map(h=>`'; }).join(''); var body='
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
'; // Store ID for guardarGasto to use - avoids quote issues in onclick window._editGastoId=isEdit?gasto['ID']:''; var foot='' +''; openModal((isEdit?'Editar':'Nuevo')+' Gasto',body,foot,'m'); } function guardarGasto(id){ var fecha=g('gf_fecha'),evento=g('gf_evento'),desc=g('gf_desc'),monto=g('gf_monto'); if(!fecha||!evento||!desc||!monto){toast('Completa los campos requeridos (*)','wa');return;} loading(true); var catVal=g('gf_cat'); if(catVal==='Otro'){var customCat=(g('gf_cat_custom')||'').trim();if(customCat)catVal=customCat;} var payload={fecha:fecha,evento:evento,categoria:catVal,descripcion:desc,monto:parseFloat(monto)||0,responsable:g('gf_resp'),metodo:g('gf_metodo'),observaciones:g('gf_obs')}; if(id)payload.id=id; call('saveGasto',payload,function(_,res){ loading(false); if(res&&res.ok){toast(res.msg||'Gasto guardado','ok');closeModal();loadGastos();} else{toast('Error: '+(res&&res.msg||'Error'),'er');} }); } function eliminarGasto(id){ customConfirm('Eliminar este gasto?',function(ok){ if(!ok)return; loading(true); call('deleteGasto',{id:id},function(_,res){ loading(false); if(res&&res.ok){toast('Gasto eliminado','ok');loadGastos();} else toast('Error eliminando','er'); }); },{icon:'fa-trash',danger:true}); } function renderConfiguracion(el){ const cfg=APP.config||{}; if(cfg.menuConfig)initMenuState(cfg.menuConfig); const inactMin=cfg.inactividadMinutos||60; // Cargar servicios dinámicos actuales var svcs=getServiciosDinamicos(); el.innerHTML=`

Configuración del Sistema

Identidad de la Iglesia

El nombre y logo que configures aquí se verán reflejados en toda la aplicación: login, menú, reportes, PDFs y correos.

${localStorage.getItem(LOGO_KEY)?'':''}

PNG/JPG recomendado cuadrado

Se mostrará en el login, menú lateral, reportes PDF y correos enviados.
Vista previa del nombre: ${esc(cfg.nombre||'Iglesia Restauración')}
Estado del Sistema

Activa o desactiva el acceso al sistema completo.

Versión: v5.1
Timezone: America/Guatemala
Moneda: Quetzal (Q)
Spreadsheet ID: ${esc(cfg.ssId||'')}
Tiempo de Inactividad

Tiempo en minutos sin actividad antes de cerrar sesión automáticamente.

Grupos esperados por mes (gráfica dashboard)
Días y Horarios de Servicios

Define los servicios semanales de la iglesia. Estos nombres se usarán en el Cronograma y Privilegios.

Agrega todos los servicios que necesites. Ej: "Martes 7:00 PM", "Viernes Jóvenes 6:00 PM", "Domingo 9:00 AM".

${svcs.map((s,i)=>`
`).join('')}
Vista previa — Horarios en Cronograma:
${svcs.map(s=>` ${esc(s.label)}`).join('')}
Link del Formulario Digital

Comparte este enlace con tus líderes. Los reportes llegan marcados como ⚠ Pendiente.

${APP.formUrlPublic||'Disponible después de implementar'}
Abrir
Menú Lateral — Activar/Desactivar Módulos

Activa o desactiva módulos para todos los usuarios (el propietario siempre los ve todos).

Bitácora de Accesos

Historial de todos los inicios de sesión del sistema. Solo el propietario puede ver todo y limpiarla. También puedes otorgar acceso a admins desde Usuarios.

Cargando...
Asistente IA ${((cfg.gemini_api_key&&cfg.gemini_api_key.length>10)||(cfg.openrouter_api_key&&cfg.openrouter_api_key.length>10)||(cfg.deepseek_api_key&&cfg.deepseek_api_key.length>10))? 'Activo': 'Sin configurar'}

Soporta Google Gemini, OpenRouter y DeepSeek. Modo Auto: usa Gemini primero, fallback a OpenRouter o DeepSeek.

Bot de Pantalla
Activa/desactiva el asistente flotante para todos
Proveedor:
Google Gemini - Gratis, max 15 req/min
Obtener Key
OpenRouter RECOMENDADO
Mistral, Qwen, Llama - gratis. Sin rate limit. Registrate en openrouter.ai
Obtener Key
DeepSeek BARATO
Modelo deepseek-chat. Muy economico (~$0.28/M tokens). platform.deepseek.com
Obtener Key
Soporte Técnico

Datos de contacto del proveedor del sistema. El asistente IA usará estos datos cuando no pueda resolver un problema.

Número al que se dirigirán los usuarios para soporte
${esc(cfg.nombre_soporte||'Total App GT - Daniel Martínez')}
${esc(cfg.whatsapp_soporte||'+502 5830-3182')}
Spreadsheet e Instalación
El ID está en la URL: /spreadsheets/d/ID/edit
Carpeta principal. Los reportes, cronograma y Excel se guardan aquí.
Opcional. Si se deja vacío, usa la carpeta principal. Los PDF del bot se guardan aquí.
Texto que aparece en el encabezado de los PDFs generados por el bot.
Correo Electrónico (SMTP)

Configuración para enviar correos desde el sistema. Usa Gmail con una Contraseña de Aplicación.

Tema del Sistema

Elige un esquema de colores predefinido. El cambio se ve al instante con "Vista Previa".

Elige un esquema predeterminado o "Personalizado" para usar tus propios colores.
${function(){ var tc=APP.config&&APP.config.theme_colors||''; var cur=''; try{cur=JSON.parse(tc).preset||'';}catch(e){} return THEME_PRESETS.map(function(p){ var active=(cur===p.id||(!cur&&p.id==='iglesia')); return '
' +'
' +Object.keys(p.colors).slice(0,4).map(function(k){return '';}).join('') +'
' +'
'+p.nombre+'
' +'
'; }).join(''); }()}
`; setTimeout(initBotToggle,20); call('getFormUrl',{},(_, res)=>{if(res&&res.ok){APP.formUrlPublic=res.url;const d=document.getElementById('cfgFormUrlDisp');if(d)d.textContent=res.url;const a=document.getElementById('cfgAbrirForm');if(a)a.href=res.url;}}); // Cargar resumen rápido de bitácora call('getBitacora',{desde:today(),hasta:today()},function(_,d){ const el=document.getElementById('cfgBitResumen');if(!el)return; const arr=Array.isArray(d)?d:[]; el.innerHTML='Accesos hoy: '+arr.length+' · Ver todos'; }); // Actualizar logo preview si existe var lk=localStorage.getItem(LOGO_KEY); if(lk){var lp=document.getElementById('cfgLogoPreview');if(lp)lp.innerHTML='';} } // Toggles const _menuState={}; function initMenuState(menuConfig){ ALL_MENU.forEach(m=>{ if(m.id==='dashboard'||m.id==='configuracion')return; if(menuConfig&&menuConfig[m.id]===false)_menuState[m.id]=false; else _menuState[m.id]=menuConfig?menuConfig[m.id]!==false:true; }); } function applyGlobalMenuConfig(menuConfig, isPropietario){ if(isPropietario) return; // propietario ve todo siempre ALL_MENU.forEach(function(m){ var niEls = document.querySelectorAll('.ni[data-mod="'+m.id+'"]'); var enabled = !menuConfig || menuConfig[m.id]!==false; niEls.forEach(function(el){ el.style.opacity = enabled ? '' : '0.35'; el.style.pointerEvents = enabled ? '' : 'none'; }); }); } let _sysActive=true; function toggleSistema(){ _sysActive=!_sysActive; const tog=document.getElementById('sysToggle');const lbl=document.getElementById('sysLabel'); if(tog){tog.style.background=_sysActive?'var(--ok)':'var(--err)';const kn=tog.querySelector('.tog-knob');if(kn){kn.style.right=_sysActive?'3px':'auto';kn.style.left=_sysActive?'auto':'3px';}} if(lbl){lbl.textContent=_sysActive?'Sistema ACTIVO':'Sistema DESACTIVADO';lbl.style.color=_sysActive?'var(--ok)':'var(--err)';} } // ── SERVICIOS DINÁMICOS ─────────────────────────────────── function getServiciosDinamicos(){ var cfg=APP.config||{}; // Preferir servicios_dinamicos si existe if(cfg.servicios_dinamicos&&Array.isArray(cfg.servicios_dinamicos)&&cfg.servicios_dinamicos.length){ return cfg.servicios_dinamicos; } // Fallback a los 4 campos legacy return [ {key:'Lunes', label:cfg.cron_lunes ||'Lunes 6:30 PM'}, {key:'Jueves', label:cfg.cron_jueves ||'Jueves 6:30 PM'}, {key:'Domingo_Mañana',label:cfg['cron_domMañana'] ||'Domingo 7:00 AM'}, {key:'Domingo_Tarde', label:cfg.cron_domTarde ||'Domingo 10:30 AM'}, ]; } function addSvcRow(){ var container=document.getElementById('svcsContainer');if(!container)return; var idx=container.querySelectorAll('.svc-row').length; var div=document.createElement('div'); div.className='svc-row'; div.id='svcRow_'+idx; div.style.cssText='display:flex;align-items:center;gap:8px;background:var(--bg3);padding:8px 12px;border-radius:8px;border:1px solid var(--brd)'; div.innerHTML='
' +'' +'' +'
'; container.appendChild(div); div.querySelector('input').focus(); updateSvcPreview(); } function removeSvcRow(idx){ var rows=document.querySelectorAll('.svc-row'); if(rows.length<=1){toast('Debes tener al menos un servicio','wa');return;} var row=document.getElementById('svcRow_'+idx); if(row)row.remove(); updateSvcPreview(); } function readSvcRows(){ var inputs=document.querySelectorAll('.svc-label-input'); var result=[]; var keys=['Lunes','Jueves','Domingo_Mañana','Domingo_Tarde','Servicio5','Servicio6','Servicio7','Servicio8','Servicio9','Servicio10']; inputs.forEach(function(inp,i){ var label=inp.value.trim(); if(label){result.push({key:keys[i]||('Servicio'+(i+1)),label:label});} }); return result; } function updateSvcPreview(){ var svcs=readSvcRows(); var el=document.getElementById('svcPreviewList'); if(!el)return; el.innerHTML=svcs.map(function(s){ return ' '+esc(s.label)+''; }).join(''); } function previewNombreSistema(val){ var el=document.getElementById('cfgNombrePreview');if(el)el.textContent=val||'—'; } function clearLogoConfig(){ clearLogo(); var lp=document.getElementById('cfgLogoPreview'); if(lp)lp.innerHTML=''; } // Override saveLogo to also update config preview var _origSaveLogo=saveLogo; saveLogo=function(){ _origSaveLogo(); var lk=localStorage.getItem(LOGO_KEY); if(lk){var lp=document.getElementById('cfgLogoPreview');if(lp)lp.innerHTML='';} }; const THEME_PRESETS=[ {id:'iglesia',nombre:'⛪ Iglesia (Default)',colors:{'--pr':'#1a3a5c','--pr2':'#2563a8','--ac':'#e8a020','--ok':'#27ae60','--err':'#e74c3c','--pur':'#8e44ad','--inf':'#2980b9','--tl':'#0e6655'}}, {id:'noche',nombre:'🌙 Noche Azul',colors:{'--pr':'#0f172a','--pr2':'#1e3a5f','--ac':'#60a5fa','--ok':'#34d399','--err':'#f87171','--pur':'#a78bfa','--inf':'#38bdf8','--tl':'#2dd4bf'}}, {id:'bosque',nombre:'🌲 Bosque',colors:{'--pr':'#14532d','--pr2':'#166534','--ac':'#fbbf24','--ok':'#4ade80','--err':'#fca5a5','--pur':'#c084fc','--inf':'#22d3ee','--tl':'#2dd4bf'}}, {id:'vino',nombre:'🍷 Vino Tinto',colors:{'--pr':'#4a0e2e','--pr2':'#7c1d4b','--ac':'#f59e0b','--ok':'#10b981','--err':'#ef4444','--pur':'#8b5cf6','--inf':'#3b82f6','--tl':'#14b8a6'}}, {id:'atardecer',nombre:'🌅 Atardecer',colors:{'--pr':'#7c2d12','--pr2':'#9a3412','--ac':'#fbbf24','--ok':'#22c55e','--err':'#dc2626','--pur':'#a855f7','--inf':'#2563eb','--tl':'#0d9488'}}, {id:'oceano',nombre:'🌊 Océano',colors:{'--pr':'#0c4a6e','--pr2':'#0369a1','--ac':'#facc15','--ok':'#10b981','--err':'#e11d48','--pur':'#8b5cf6','--inf':'#38bdf8','--tl':'#14b8a6'}}, {id:'lavanda',nombre:'🪻 Lavanda',colors:{'--pr':'#3b1f54','--pr2':'#5b2d8e','--ac':'#f0abfc','--ok':'#86efac','--err':'#fda4af','--pur':'#d8b4fe','--inf':'#93c5fd','--tl':'#5eead4'}}, {id:'carbon',nombre:'⚫ Carbón',colors:{'--pr':'#1f2937','--pr2':'#374151','--ac':'#f59e0b','--ok':'#10b981','--err':'#ef4444','--pur':'#8b5cf6','--inf':'#3b82f6','--tl':'#14b8a6'}}, {id:'personalizado',nombre:'🎨 Personalizado',colors:{'--pr':'#1a3a5c','--pr2':'#2563a8','--ac':'#e8a020','--ok':'#27ae60','--err':'#e74c3c','--pur':'#8e44ad','--inf':'#2980b9','--tl':'#0e6655'}}, ]; function extraeColor(def,key){ if(!APP.config||!APP.config.theme_colors)return def; try{var tc=JSON.parse(APP.config.theme_colors);return tc.colors&&tc.colors['--'+key]?tc.colors['--'+key]:(tc['--'+key]||def);}catch(e){return def;} } function getThemePresetColors(){ var sel=document.getElementById('cfgThemePreset'); var presetId=sel?sel.value:'iglesia'; var preset=THEME_PRESETS.find(function(p){return p.id===presetId;}); if(!preset)preset=THEME_PRESETS[0]; var colors=Object.assign({},preset.colors); if(presetId!=='personalizado'){ var accentEl=document.getElementById('cfgThemeAccent'); if(accentEl&&accentEl.value&&accentEl.value!=='#e8a020')colors['--ac']=accentEl.value; } return colors; } function serializeTema(){ var sel=document.getElementById('cfgThemePreset'); var presetId=sel?sel.value:'iglesia'; var colors=getThemePresetColors(); var obj={preset:presetId,colors:colors}; return JSON.stringify(obj); } function vistaPreviaTema(){ var s=document.getElementById('themeOverride'); if(!s){s=document.createElement('style');s.id='themeOverride';document.head.appendChild(s);} var raw=serializeTema(); try{var pr=JSON.parse(raw);var c=pr.colors||{};s.textContent=':root{'+Object.keys(c).map(function(k){return k+':'+c[k]+';'}).join('')+'}';}catch(e){} } function restaurarTemaDefecto(){ var sel=document.getElementById('cfgThemePreset');if(sel)sel.value='iglesia'; var accent=document.getElementById('cfgThemeAccent');if(accent)accent.value='#e8a020'; document.getElementById('cfgThemeAccentWrap').style.display='none'; onThemePresetChange(); vistaPreviaTema(); } function onThemePresetChange(){ var sel=document.getElementById('cfgThemePreset'); var wrap=document.getElementById('cfgThemeAccentWrap'); var preview=document.getElementById('cfgThemePreview'); if(!sel||!wrap||!preview)return; var id=sel.value; wrap.style.display=(id!=='personalizado'&&id!=='iglesia')?'block':'none'; // Actualizar preview cards Array.from(preview.children).forEach(function(card){ card.classList.toggle('theme-card-active',card.getAttribute('data-preset')===id); }); if(id==='personalizado'){ // Para personalizado se puede añadir un editor inline } } function selectThemePreset(id){ var sel=document.getElementById('cfgThemePreset');if(sel)sel.value=id; onThemePresetChange(); vistaPreviaTema(); } function guardarConfig(){ var svcs=readSvcRows(); if(!svcs.length){toast('Debes tener al menos un servicio definido','wa');return;} var inactivos=ALL_MENU.filter(m=>m.id!=='dashboard'&&m.id!=='configuracion'&&_menuState[m.id]===false).map(m=>m.id); // Compatibilidad legacy: primeros 4 servicios como campos individuales var data={ nombre:g('cfgNombre'),form_url:g('cfgFormUrl'), spreadsheet_id:g('cfgSsId'), drive_folder_id:g('cfgDriveId')||'', sistema_activo:_sysActive?'SI':'NO', mensaje_mantenimiento:g('cfgMsgMant'), titulo_mantenimiento:g('cfgTitleMant')||'Sistema en Mantenimiento', menu_config:JSON.stringify(inactivos), inactividad_minutos:g('cfgInact'), meta_grupos:g('cfgMetaGrupos')||'407', // Legacy fields for backwards compat cron_lunes:svcs[0]?svcs[0].label:'Lunes 6:30 PM', cron_jueves:svcs[1]?svcs[1].label:'Jueves 6:30 PM', cron_domMañana:svcs[2]?svcs[2].label:'Domingo 7:00 AM', cron_domTarde:svcs[3]?svcs[3].label:'Domingo 10:30 AM', // Nuevo: lista dinámica completa servicios_dinamicos:JSON.stringify(svcs), logo_url:localStorage.getItem(LOGO_KEY)||'', gemini_api_key:g('cfgGeminiKey')||'', openrouter_api_key:g('cfgOpenrouterKey')||'', deepseek_api_key:g('cfgDeepSeekKey')||'', ai_provider:g('cfgAIProvider')||'auto', bot_habilitado:g('cfgBotHabilitadoVal')||'SI', whatsapp_soporte:g('cfgWhatsappSoporte')||'+502 5830-3182', nombre_soporte:g('cfgNombreSoporte')||'Total App GT - Daniel Martínez', pdf_id:g('cfgPdfId')||'', bot_pdf_folder_id:g('cfgBotPdfFolderId')||'', theme_colors:serializeTema(), smtp_user:g('cfgSmtpUser')||'totalappgt@gmail.com', smtp_password:g('cfgSmtpPassword')||'', }; // Claves individuales módulos ALL_MENU.forEach(m=>{if(m.id!=='dashboard'&&m.id!=='configuracion')data['menu_mod_'+m.id]=_menuState[m.id]===false?'NO':'SI';}); loading(true); call('saveConfig',data,(_,res)=>{ loading(false); if(res && res.ok){ toast('✓ Configuración guardada','ok'); APP.config=Object.assign(APP.config||{},data,{servicios_dinamicos:svcs}); var nombre=g('cfgNombre');if(nombre)aplicarNombreSistema(nombre); var lk=localStorage.getItem(LOGO_KEY);if(lk)applyLogoToUI(lk); buildSidebarMenu(APP.user.menu,APP.user.isPropietario); applyGlobalMenuConfig(getMenuConfigFromState(),APP.user.isPropietario); var _bEl=document.getElementById('aiBot'); if(data.bot_habilitado==='NO'){if(_bEl)_bEl.remove();_botInit=false;} else if(!_bEl){_botInit=false;setTimeout(initAIBot,300);} aplicarTema(); invalidateDashCache(); if(APP.mod!=='configuracion')nav(APP.mod,true); } else toast('Error: '+(res && res.msg ? res.msg : ''),'er'); }); } function getMenuConfigFromState(){const mc={};ALL_MENU.forEach(m=>{if(m.id!=='dashboard')mc[m.id]=_menuState[m.id]!==false;});return mc;} function probarConexion(){const el=document.getElementById('conexStatus');if(!el)return;el.innerHTML='

Probando...

';call('inicializarSistema',{},(_,res)=>{if(res && res.ok)el.innerHTML=`
Conexión exitosa. ${esc(res.msg)}
`;else el.innerHTML=`
Error: ${esc(res && res.msg ? res.msg : '')}
`;});} function probarCorreo(){call('enviarReporte',{destinatarios:g('cfgSmtpUser')||'totalappgt@gmail.com',asunto:'Prueba - REDIL',cuerpo:'

Este es un correo de prueba desde el Sistema REDIL.

'},function(_,res){if(res&&res.ok)toast('Correo de prueba enviado correctamente','ok',5000);else toast('Error: '+(res&&res.msg||'Error al enviar'),'er',6000);});} function inicializar(){customConfirm('¿Está seguro de inicializar las hojas base? Los datos existentes NO se borrarán.',function(ok){if(!ok)return;loading(true);call('inicializarSistema',{},(_,res)=>{loading(false);if(res && res.ok)toast(res.msg,'ok',5000);else toast('Error: '+(res && res.msg ? res.msg : ''),'er');});},{icon:'fa-database'});} // ══════════════════════════════════════════════════════════ // 🤖 ASISTENTE IA — Gemini Bot Flotante // ══════════════════════════════════════════════════════════ var _botOpen=false; var _botInit=false; function selectProvider(p){ var inp=document.getElementById('cfgAIProvider'); if(inp)inp.value=p; } function toggleBotHabilitado(){ var btn=document.getElementById('cfgBotToggleBtn'); var hid=document.getElementById('cfgBotHabilitadoVal'); if(!btn||!hid)return; var newOn=(hid.value!=='SI'); hid.value=newOn?'SI':'NO'; btn.textContent=newOn?'ON':'OFF'; btn.style.background=newOn?'#27ae60':'#9ca3af'; var botEl=document.getElementById('aiBot'); if(newOn){_botInit=false;if(APP.config)APP.config.bot_habilitado=true;setTimeout(initAIBot,300);} else{if(botEl)botEl.remove();_botInit=false;if(APP.config)APP.config.bot_habilitado=false;} } function initBotToggle(){ var btn=document.getElementById('cfgBotToggleBtn'); var hid=document.getElementById('cfgBotHabilitadoVal'); if(!btn||!hid)return; var isOn=(hid.value==='SI'); btn.textContent=isOn?'ON':'OFF'; btn.style.background=isOn?'#27ae60':'#9ca3af'; } function probarBot(provider){ provider=provider||'auto'; var gemKey=(document.getElementById('cfgGeminiKey')||{}).value||''; var orKey=(document.getElementById('cfgOpenrouterKey')||{}).value||''; var dsKey=(document.getElementById('cfgDeepSeekKey')||{}).value||''; var resEl=document.getElementById('botTestResult'); if(provider==='gemini'&&gemKey.length<20){toast('Ingresa tu Gemini API Key','wa');return;} if(provider==='openrouter'&&orKey.length<20){toast('Ingresa tu OpenRouter API Key','wa');return;} if(provider==='deepseek'&&dsKey.length<20){toast('Ingresa tu DeepSeek API Key','wa');return;} if(provider==='auto'&&gemKey.length<20&&orKey.length<20&&dsKey.length<20){toast('Ingresa al menos una API Key','wa');return;} if(resEl)resEl.innerHTML='

Probando...

'; call('preguntarAI',{pregunta:'Responde solo: ACTIVO',gemini_key:gemKey,openrouter_key:orKey,deepseek_key:dsKey,provider:provider},function(_,res){ if(res&&res.ok){ var mod=res.modelo?(' - '+esc(res.modelo)):''; if(resEl)resEl.innerHTML='

Conexion exitosa'+mod+'

'; toast('Asistente IA funcionando','ok',4000); }else{ var msg=(res&&res.msg)||'Error'; if(resEl)resEl.innerHTML='

'+esc(msg)+'

'; toast('Error: '+msg,'er',6000); } }); } function initAIBot(){ if(!APP.user)return; var _bcfg=APP.config||{}; // Regla estricta: Si no está configurado explícitamente como 'SI', se mantiene oculto por defecto if(_bcfg.bot_habilitado !== 'SI'){ var _bex=document.getElementById('aiBot'); if(_bex)_bex.remove(); _botInit=false; return; } if(_botInit)return; _botInit=true; if(document.getElementById('aiBot'))return; // Continuar con la creación del bot flotante (Código original)... var cfg=APP.config||{}; var waNum=(cfg.whatsapp_soporte||'+502 5830-3182').replace(/[^0-9]/g,''); var waName=cfg.nombre_soporte||'Total App GT - Daniel Martínez'; var sysNom=cfg.nombre||'REDIL'; var bot=document.createElement('div'); bot.id='aiBot'; bot.style.cssText='position:fixed;bottom:24px;right:24px;z-index:8999;font-family:var(--fn)'; bot.innerHTML= // Botón flotante '' // Panel de chat +''; document.body.appendChild(bot); } function toggleAIBot(){ _botOpen=!_botOpen; var panel=document.getElementById('aiBotPanel'); var ico=document.getElementById('aiBotIco'); var dot=document.getElementById('aiBotDot'); if(panel)panel.style.display=_botOpen?'block':'none'; if(ico)ico.className=_botOpen?'fas fa-times':'fas fa-robot'; if(_botOpen){ var inp=document.getElementById('aiBotInp'); if(inp)setTimeout(function(){inp.focus();},100); if(dot)dot.style.display='none'; } } function botAddMsg(text,isUser){ var msgs=document.getElementById('aiBotMsgs'); if(!msgs)return; var div=document.createElement('div'); var cfg=APP.config||{}; var waNum=(cfg.whatsapp_soporte||'+502 5830-3182').replace(/[^0-9]/g,''); var waName=cfg.nombre_soporte||'Total App GT'; if(isUser){ div.style.cssText='background:var(--pr);color:#fff;border-radius:14px 14px 4px 14px;padding:10px 13px;font-size:13px;line-height:1.5;max-width:88%;align-self:flex-end'; div.textContent=text; }else{ div.style.cssText='background:#e8f0fe;color:#222;border-radius:14px 14px 14px 4px;padding:10px 13px;font-size:13px;line-height:1.5;max-width:92%'; // Convert **bold** and line breaks var html=esc(text) .replace(/\*\*(.*?)\*\*/g,'$1'); html=html.replace(/\n/g,'
'); // Add WhatsApp link if suggests contacting admin if(text.toLowerCase().includes('administrador')||text.toLowerCase().includes('soporte')||text.toLowerCase().includes('contactar')){ html+='

' +' Contactar: '+esc(waName)+''; } div.innerHTML=html; } msgs.appendChild(div); msgs.scrollTop=msgs.scrollHeight; } function enviarBot(){ var inp=document.getElementById('aiBotInp'); if(!inp)return; var q=inp.value.trim(); if(!q)return; inp.value=''; botAddMsg(q,true); var cfg=APP.config||{}; var apiKey=cfg.gemini_api_key||''; var waNum=(cfg.whatsapp_soporte||'+502 5830-3182').replace(/[^0-9]/g,''); var waName=cfg.nombre_soporte||'Total App GT - Daniel Martínez'; var orKey=cfg.openrouter_api_key||''; var dsKey=cfg.deepseek_api_key||''; if((!apiKey||apiKey.length<20)&&(!orKey||orKey.length<20)&&(!dsKey||dsKey.length<20)){ botAddMsg('⚠ El Asistente IA no está configurado. Ve a Configuración > Asistente IA y agrega una API Key.',false); return; } // Indicador de escritura var msgs=document.getElementById('aiBotMsgs'); var typing=document.createElement('div'); typing.id='botTyping'; typing.style.cssText='background:#e8f0fe;border-radius:14px;padding:10px 13px;font-size:13px;color:#888;max-width:88%'; typing.innerHTML=' Escribiendo...'; if(msgs){msgs.appendChild(typing);msgs.scrollTop=msgs.scrollHeight;} call('preguntarAI',{pregunta:q},function(err,res){ var t=document.getElementById('botTyping'); if(t)t.remove(); if(res&&res.ok){ botAddMsg(res.respuesta,false); }else{ var errMsg=(res&&res.msg)||'Error de conexión'; botAddMsg('⚠ '+errMsg+'\n\nPuedes contactar al soporte técnico directamente.',false); } }); } // ══════════════════════════════════════════════════════════ // RENDER MAP // ══════════════════════════════════════════════════════════ // === MÓDULOS v6 (sin backticks) === var _svs=[],_pas=[],_ayu=[]; function renderSupervisores(el){el.innerHTML='

Supervisores de Sector

CodigoNombreDZASTelEstado
';loadSvs();} function loadSvs(){call('getSupervisores',{},function(_,r){_svs=(r&&r.ok&&r.data)?r.data:[];try{localStorage.setItem('_svs_c',JSON.stringify(_svs.map(function(s){return{c:s['CodigoSup'],n:s['NombreSup']};})));}catch(ex){}renderSvsTb(_svs);});} function filterSvs(q){renderSvsTb(q?_svs.filter(function(s){return JSON.stringify(s).toLowerCase().includes(q.toLowerCase());}):_svs);} function renderSvsTb(d){var tb=document.getElementById('svsTb');if(!tb)return;if(!d.length){tb.innerHTML='

Sin supervisores

';return;}tb.innerHTML=d.map(function(s){return ''+esc(s['CodigoSup'])+''+esc(s['NombreSup'])+''+esc(s['Distrito']||'')+''+esc(s['Zona']||'')+''+esc(s['Area']||'')+''+esc(s['Sector']||'')+''+esc(s['Telefono']||'')+''+(s['Activo']==='SI'?'Activo':'Inactivo')+'
';}).join('');} function previewCodigoSvs(){var d=(document.getElementById('sf_dist')||{}).value||'',z=(document.getElementById('sf_zona')||{}).value||'',a=((document.getElementById('sf_area')||{}).value||'').toUpperCase().replace(/[^A-Z]/g,'').substring(0,1)||'',s=(document.getElementById('sf_sector')||{}).value||'';var cod=(d&&z&&a&&s)?(d+z+a+s):'??';var pv=document.getElementById('svsCP');if(pv)pv.textContent=cod;var hd=document.getElementById('sf_codigo');if(hd)hd.value=(cod!=='??'?cod:'');} function svsForm(s){s=s||{};return '
Jerarquia D(n)Z(n)A(L)S(n) Ej:11A1
Codigo:'+esc(s['CodigoSup']||'??')+'
';} function newSvs(){openModal('Nuevo Supervisor',svsForm(),'','xl');} function editSvs(id){ var s=_svs.find(function(x){return x['ID']===id;}); if(!s)return; openModal( 'Editar Supervisor', svsForm(s), ``, 'xl' ); } function saveSvs(id){var cod=(document.getElementById('sf_codigo')||{}).value||'',nom=(document.getElementById('sf_nombre')||{}).value||'';if(!nom){toast('Nombre requerido','wa');return;}if(!cod||cod==='??'){toast('Completa 4 campos','wa');return;}loading(true);call('saveSupervisor',{ID:id||'',CodigoSup:cod,NombreSup:nom,Distrito:(document.getElementById('sf_dist')||{}).value||'',Zona:(document.getElementById('sf_zona')||{}).value||'',Area:((document.getElementById('sf_area')||{}).value||'').toUpperCase(),Sector:(document.getElementById('sf_sector')||{}).value||'',Telefono:(document.getElementById('sf_tel')||{}).value||'',Email:(document.getElementById('sf_email')||{}).value||'',Activo:(document.getElementById('sf_act')||{}).value||'SI'},function(_,r){loading(false);if(r&&r.ok){toast('Supervisor: '+cod,'ok',4000);closeModal();loadSvs();}else toast('Error: '+(r&&r.msg||''),'er');});} function delSvs(id){customConfirm('Eliminar supervisor?',function(ok){if(!ok)return;call('deleteSupervisor',{id:id},function(_,r){if(r&&r.ok){toast('Eliminado','ok');loadSvs();}});},{icon:'fa-trash',danger:true});} function renderPastores(el){el.innerHTML='

Pastores de Zona

CodigoNombreDZTelEstado
';loadPas();} function loadPas(){call('getPastores',{},function(_,r){_pas=(r&&r.ok&&r.data)?r.data:[];try{localStorage.setItem('_pas_c',JSON.stringify(_pas.map(function(p){return{c:p['CodigoPastor'],n:p['NombrePastor']};})));}catch(ex){}renderPasTb(_pas);});} function filterPas(q){renderPasTb(q?_pas.filter(function(p){return JSON.stringify(p).toLowerCase().includes(q.toLowerCase());}):_pas);} function renderPasTb(d){var tb=document.getElementById('pasTb');if(!tb)return;if(!d.length){tb.innerHTML='

Sin pastores

';return;}tb.innerHTML=d.map(function(p){return ''+esc(p['CodigoPastor'])+''+esc(p['NombrePastor'])+''+esc(p['Distrito']||'')+''+esc(p['Zona']||'')+''+esc(p['Telefono']||'')+''+(p['Activo']==='SI'?'Activo':'Inactivo')+'
';}).join('');} function previewCodigoPas(){var d=(document.getElementById('pf_dist')||{}).value||'',z=(document.getElementById('pf_zona')||{}).value||'';var cod=(d&&z)?(d+z):'??';var pv=document.getElementById('pasCP');if(pv)pv.textContent=cod;var hd=document.getElementById('pf_codigo');if(hd)hd.value=(cod!=='??'?cod:'');} function pasForm(p){p=p||{};return '
Jerarquia D(n)Z(n) Ej:11
Codigo:'+esc(p['CodigoPastor']||'??')+'
';} function newPas(){ openModal( 'Nuevo Pastor', pasForm(), ``, 'xl' ); } function editPas(id){ var p=_pas.find(function(x){return x['ID']===id;}); if(!p)return; openModal( 'Editar Pastor', pasForm(p), ``, 'xl' ); } function savePas(id){var cod=(document.getElementById('pf_codigo')||{}).value||'',nom=(document.getElementById('pf_nombre')||{}).value||'';if(!nom){toast('Nombre requerido','wa');return;}if(!cod||cod==='??'){toast('Completa Distrito y Zona','wa');return;}loading(true);call('savePastor',{ID:id||'',CodigoPastor:cod,NombrePastor:nom,Distrito:(document.getElementById('pf_dist')||{}).value||'',Zona:(document.getElementById('pf_zona')||{}).value||'',Telefono:(document.getElementById('pf_tel')||{}).value||'',Email:(document.getElementById('pf_email')||{}).value||'',Activo:(document.getElementById('pf_act')||{}).value||'SI'},function(_,r){loading(false);if(r&&r.ok){toast('Pastor: '+cod,'ok',4000);closeModal();loadPas();}else toast('Error: '+(r&&r.msg||''),'er');});} function delPas(id){customConfirm('Eliminar pastor?',function(ok){if(!ok)return;call('deletePastor',{id:id},function(_,r){if(r&&r.ok){toast('Eliminado','ok');loadPas();}});},{icon:'fa-trash',danger:true});} // ── AYUDA DE PASTOR ─────────────────────────────────────────── function renderAyudaPastor(el){el.innerHTML='

Ayuda de Pastor

CodigoNombreDZATelEstado
';loadAyu();} function loadAyu(){call('getAyudaPastor',{},function(_,r){_ayu=(r&&r.ok&&r.data)?r.data:[];renderAyuTb(_ayu);});} function filterAyu(q){renderAyuTb(q?_ayu.filter(function(a){return JSON.stringify(a).toLowerCase().includes(q.toLowerCase());}):_ayu);} function renderAyuTb(d){var tb=document.getElementById('ayuTb');if(!tb)return;if(!d.length){tb.innerHTML='

Sin registros

';return;}tb.innerHTML=d.map(function(a){return ''+esc(a['CodigoAyuda'])+''+esc(a['NombreAyuda'])+''+esc(a['Distrito']||'')+''+esc(a['Zona']||'')+''+esc(a['Area']||'')+''+esc(a['Telefono']||'')+''+(a['Activo']==='SI'?'Activo':'Inactivo')+'
';}).join('');} function previewCodigoAyu(){var d=(document.getElementById('af_dist')||{}).value||'',z=(document.getElementById('af_zona')||{}).value||'',a=((document.getElementById('af_area')||{}).value||'').toUpperCase().replace(/[^A-Z]/g,'').substring(0,1)||'';var cod=(d&&z&&a)?(d+z+a):'??';var pv=document.getElementById('ayuCP');if(pv)pv.textContent=cod;var hd=document.getElementById('af_codigo');if(hd)hd.value=(cod!=='??'?cod:'');} function ayuForm(a){a=a||{};return '
Jerarquia D(n)Z(n)A(L) Ej:11A
Codigo:'+esc(a['CodigoAyuda']||'??')+'
';} function newAyu(){openModal('Nuevo Ayuda de Pastor',ayuForm(),'','xl');} function editAyu(id){var a=_ayu.find(function(x){return x['ID']===id;});if(!a)return;openModal('Editar Ayuda de Pastor',ayuForm(a),'','xl');} function saveAyu(id){var cod=(document.getElementById('af_codigo')||{}).value||'',nom=(document.getElementById('af_nombre')||{}).value||'';if(!nom){toast('Nombre requerido','wa');return;}if(!cod||cod==='??'){toast('Completa Distrito, Zona y Area','wa');return;}loading(true);call('saveAyudaPastor',{ID:id||'',CodigoAyuda:cod,NombreAyuda:nom,Distrito:(document.getElementById('af_dist')||{}).value||'',Zona:(document.getElementById('af_zona')||{}).value||'',Area:((document.getElementById('af_area')||{}).value||'').toUpperCase(),Telefono:(document.getElementById('af_tel')||{}).value||'',Email:(document.getElementById('af_email')||{}).value||'',Activo:(document.getElementById('af_act')||{}).value||'SI'},function(_,r){loading(false);if(r&&r.ok){toast('Ayuda de Pastor: '+cod,'ok',4000);closeModal();loadAyu();}else toast('Error: '+(r&&r.msg||''),'er');});} function delAyu(id){customConfirm('Eliminar ayuda de pastor?',function(ok){if(!ok)return;call('deleteAyudaPastor',{id:id},function(_,r){if(r&&r.ok){toast('Eliminado','ok');loadAyu();}});},{icon:'fa-trash',danger:true});} function previewCodigo(){var d=(document.getElementById('hf_dist')||{}).value||'',z=(document.getElementById('hf_zona')||{}).value||'',a=((document.getElementById('hf_area')||{}).value||'').toUpperCase().replace(/[^A-Z]/g,'').substring(0,1)||'',s=(document.getElementById('hf_sector')||{}).value||'',g=(document.getElementById('hf_grupo')||{}).value||'';var cod=(d&&z&&a&&s&&g)?(d+z+a+s+g):'??';var pv=document.getElementById('hermCP');if(pv)pv.textContent=cod;var hd=document.getElementById('hf_codigo');if(hd)hd.value=(cod!=='??'?cod:'');} function abrirReporteFinanciero(){var dists=[...new Set(APP.hermanos.map(function(h){return h['Distrito'];}).filter(Boolean))].sort();var zonas=[...new Set(APP.hermanos.map(function(h){return h['Zona'];}).filter(Boolean))].sort();openModal('Reporte Financiero','
','','xl');} function generarRF(){var el=document.getElementById('rfResult');if(el)el.innerHTML='
';call('getReporteFinancieroDistrito',{desde:(document.getElementById('rfD')||{}).value||'',hasta:(document.getElementById('rfH')||{}).value||'',distrito:(document.getElementById('rfDist')||{}).value||'',zona:(document.getElementById('rfZona')||{}).value||''},function(_,res){if(!el)return;if(!res||!res.ok){el.innerHTML='

Error

';return;}var data=res.byZona||[];if(!data.length){el.innerHTML='

Sin datos

';return;}el.innerHTML='
'+(res.totalReportes||0)+'
Reportes
'+(res.totalAsistencia||0)+'
Asistencia
Q '+Number(res.totalOfrenda||0).toFixed(2)+'
Ofrenda
'+data.map(function(z){return '';}).join('')+'
DZRptsAsistOfrendaRecibidaPendiente
D'+esc(z.distrito)+'Z'+esc(z.zona)+''+z.reportes+''+z.asistencia+'Q '+Number(z.ofrendaTotal||0).toFixed(2)+'Q '+Number(z.ofrendaRecibida||0).toFixed(2)+'Q '+Number(z.pendientes||0).toFixed(2)+'
';});} // ─── MARCADORES DE POSICIÓN PARA MÓDULOS FALTANTES ─── // 1. Dibuja la pantalla de Carga Masiva // 1. Interfaz de la Carga Masiva function renderCargaMasiva(el) { el.innerHTML = `

Carga Masiva de Datos

Descarga la plantilla deseada, llénala con tus datos y luego súbela aquí para procesarla.

`; } // ── DESCARGAR PLANTILLA PARA CARGA MASIVA ─────────── function descargarPlantilla(tipo) { loading(true); call('getEncabezadosCargaMasiva', {tipo: tipo}, function(err, res) { loading(false); if (err || !res || !res.ok) { toast("Error obteniendo encabezados", "er"); return; } var hdrs = res.data || res; if (!hdrs || !hdrs.length) { toast("Error obteniendo encabezados", "er"); return; } var ejemplo = hdrs.map(function(h) { if (typeof h !== 'string') return ""; if (h.indexOf("Nombre") >= 0) return "Ejemplo"; if (h === "Distrito" || h === "Sector" || h === "Grupo") return "1"; if (h === "Zona") return "1"; if (h === "Area") return "A"; if (h === "Activo") return "SI"; return ""; }); var nombre = "Plantilla_" + tipo.charAt(0).toUpperCase() + tipo.slice(1) + ".xlsx"; var ws = XLSX.utils.aoa_to_sheet([hdrs, ejemplo]); var wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Plantilla"); var out = XLSX.write(wb, { bookType: "xlsx", type: "array" }); var blob = new Blob([out], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); var url = URL.createObjectURL(blob); var a = document.createElement("a"); a.href = url; a.download = nombre; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast(nombre + " descargada", "ok", 3000); }); } function procesarCargaMasiva() { const resEl = document.getElementById('cmResult'); const fileInput = document.getElementById("cmFile"); const tipo = document.getElementById("cmTipo") ? document.getElementById("cmTipo").value : "hermanos"; if(!fileInput || !fileInput.files || fileInput.files.length === 0) { toast('Selecciona un archivo Excel', 'wa'); return; } const reader = new FileReader(); resEl.innerHTML = ' Leyendo archivo...'; reader.onload = function(e) { try { const data = new Uint8Array(e.target.result); const workbook = XLSX.read(data, {type: 'array'}); const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; const rows = XLSX.utils.sheet_to_json(firstSheet, {header: 1, defval: ""}); if(rows.length <= 1) { resEl.innerHTML = 'El archivo está vacío o no tiene el formato correcto.'; return; } resEl.innerHTML = ' Procesando '+(rows.length-1)+' registros...'; // Enviamos la matriz limpia al servidor call('importarDatosMasivos', {tipo: tipo, rows: rows}, function(err, res) { if(err) { resEl.innerHTML = ' Error de servidor.'; return; } if(res && res.ok) { resEl.innerHTML = ' ' + res.msg + ''; toast('Carga exitosa', 'ok'); } else { resEl.innerHTML = ' ' + (res.msg || 'Error') + ''; } }); } catch(ex) { resEl.innerHTML = 'Error procesando Excel. Asegúrate de que no esté corrupto.'; } }; reader.readAsArrayBuffer(fileInput.files[0]); } function renderSeguimientos(el) { el.innerHTML = `

Control de Seguimientos

FechaPersonaTipoResponsableEstadoObservacionesAcciones
`; loadSegs(); } var _segs=[]; var _qSeg=''; function loadSegs(){ loading(true); call('getSeguimientos',{},function(_,d){loading(false);_segs=Array.isArray(d)?d:[];renderSegRes();renderSegTable();}); } function renderSegRes(){ var el=document.getElementById('segRes');if(!el)return; var total=_segs.length; var pend=_segs.filter(function(s){return s['Estado']==='Pendiente';}).length; var proc=_segs.filter(function(s){return s['Estado']==='En Proceso';}).length; var done=_segs.filter(function(s){return s['Estado']==='Completado';}).length; el.innerHTML=[ {l:'Total',v:total,c:''},{l:'⚠ Pendientes',v:pend,c:'r'}, {l:'🔄 En Proceso',v:proc,c:'o'},{l:'✓ Completados',v:done,c:'g'} ].map(function(s){return '
'+s.v+'
'+s.l+'
';}).join(''); } function getVisibleSegs(){ var d=_segs.slice(); var est=document.getElementById('segFiltroEstado'); if(est&&est.value)d=d.filter(function(s){return s['Estado']===est.value;}); if(_qSeg)d=d.filter(function(s){return JSON.stringify(s).toLowerCase().includes(_qSeg.toLowerCase());}); return d; } function renderSegTable(){ var data=getVisibleSegs(); var tb=document.getElementById('segTb');if(!tb)return; if(!data.length){tb.innerHTML='

Sin seguimientos registrados

';return;} var cls={'Completado':'bg','Pendiente':'bo','En Proceso':'bi','Cancelado':'bgr'}; tb.innerHTML=data.map(function(s){return '' +''+fmtD(s['Fecha'])+'' +''+esc(s['Persona'])+'' +''+esc(s['Tipo'])+'' +''+esc(s['Responsable'])+'' +''+esc(s['Estado'])+'' +''+esc(s['Observaciones'])+'' +'
' +'' +'' +'
';}).join(''); } function newSeg(){openModal('Nuevo Seguimiento',segForm(),mFoot('saveSegForm'));} function editSeg(id){ var s=_segs.find(function(x){return x['ID']===id;}); if(!s)return; openModal('Editar Seguimiento',segForm(s),mFoot("saveSegForm('"+esc(id)+"')",'Actualizar')); } function segForm(s){ s=s||{}; var tipos=['Convertido','Reconciliación','Visita Programada','Bautizo en Agua','Sanidad','Oración','Discipulado','Llamada','Cita','Otro']; var estados=['Pendiente','En Proceso','Completado','Cancelado']; return '
' +'
' +'
' +'
' +'
' +'
' +'
'; } function saveSegForm(id){ var data={'ID':id||'','Persona':g('segPersona'),'Tipo':g('segTipo'),'Responsable':g('segResp'),'Estado':g('segEstado'),'Observaciones':g('segObs')}; if(!data['Persona']){toast('El nombre de la persona es requerido','wa');return;} loading(true); call('saveSeguimiento',data,function(_,res){loading(false);if(res&&res.ok){toast('Seguimiento guardado','ok');closeModal();loadSegs();}else toast('Error: '+((res&&res.msg)||''),'er');}); } function delSeg(id){ customConfirm('¿Eliminar este seguimiento?',function(ok){if(!ok)return;loading(true);call('deleteSeguimiento',{id: id},function(_,res){loading(false);if(res&&res.ok){toast('Eliminado','ok');loadSegs();}else toast('Error','er');});},{icon:'fa-trash-alt',danger:true}); } function renderCronograma(el){renderPrivilegios(el);setTimeout(function(){switchPrvTab('cron');},400);} // ══════════════════════════════════════════════════════════ // BAUTIZOS EN AGUA // ══════════════════════════════════════════════════════════ var _bautizosData=[]; function renderBautizos(el){ el.innerHTML=`

Bautizos en Agua

IDFechaNombreEdadTeléfonoPastorLugarActivoAcción
`; loadBautizos(); } function loadBautizos(){ loading(true); call('getBautizos',{},function(_,res){ loading(false); if(!res){toast('Error cargando bautizos','er');return;} _bautizosData=Array.isArray(res)?res:(res&&res.data?res.data:(res&&res.bautizos?res.bautizos:[])); var tb=document.getElementById('bautTb');if(!tb)return; var tbody=tb.querySelector('tbody');if(!tbody)return; if(!_bautizosData.length){tbody.innerHTML='

Sin bautizos registrados

';return;} tbody.innerHTML=_bautizosData.map(function(b){return ''+ ''+esc(b.ID||b.id||'')+''+ ''+esc(b.Fecha||b.fecha||'')+''+ ''+esc(b.Nombre||b.nombre||'')+''+ ''+esc(b.Edad||b.edad||'')+''+ ''+esc(b.Telefono||b.telefono||'')+''+ ''+esc(b.PastorOficiante||b.pastor_oficiante||'')+''+ ''+esc(b.Lugar||b.lugar||'')+''+ ''+(b.Activo===false||b.activo===false?'No':'')+''+ '
';}).join(''); }); } function bautForm(d){ d=d||{}; return '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'; } function newBautizo(){openModal('Nuevo Bautizo',bautForm(),modalBtns('guardarBautizo()','Guardar'),'lg');} function editBautizo(id){ var d=_bautizosData.find(function(b){return (b.ID||b.id)==id;}); if(!d){toast('No encontrado','er');return;} openModal('Editar Bautizo',bautForm(d),modalBtns("guardarBautizo("+id+")",'Actualizar'),'lg'); } function guardarBautizo(id){ var data={ID:id||'',Fecha:g('b_fecha'),Nombre:g('b_nombre'),Edad:parseInt(g('b_edad')||'0'),Telefono:g('b_telefono'),Direccion:g('b_direccion'),PastorOficiante:g('b_pastor'),Lugar:g('b_lugar'),Observaciones:g('b_obs'),Activo:document.getElementById('b_activo')?.checked||false}; if(!data.Nombre){toast('El nombre es requerido','wa');return;} loading(true); call('saveBautizo',data,function(_,res){loading(false);if(res&&res.ok){toast('Guardado','ok');closeModal();loadBautizos();}else toast('Error: '+(res&&res.msg||'Error'),'er');}); } function delBautizo(id){ customConfirm('¿Eliminar este bautizo?',function(ok){if(!ok)return;loading(true);call('deleteBautizo',{id:id},function(_,res){loading(false);if(res&&res.ok){toast('Eliminado','ok');loadBautizos();}else toast('Error','er');});},{icon:'fa-trash-alt'}); } var RENDERS={dashboard:renderDashboard,reportes:renderReportes,reporteDigital:renderReporteDigital,formulario:renderFormulario,generador:renderGenerador,hermanos:renderHermanos,cargaMasiva:renderCargaMasiva,supervisores:renderSupervisores,pastores:renderPastores,ayudapastor:renderAyudaPastor,seguimientos:renderSeguimientos,privilegios:renderPrivilegios,cronograma:renderCronograma,bautizos:renderBautizos,diezmos:renderDiezmos,gastos:renderGastos,inventario:renderInventario,insumos:renderInsumos,envio:renderEnvio,contactos:renderContactos,cuadre:renderCuadre,usuarios:renderUsuarios,bitacora:renderBitacora,configuracion:renderConfiguracion}; // ══════════════════════════════════════════════════════════ // CUADRE DOMINICAL — Cierre semanal (domingo 12am) // ══════════════════════════════════════════════════════════ function renderCuadre(el){ el.innerHTML=`

Cuadre Dominical — Cierre Semanal

📊

Cierre Semanal de Reportes

Cada domingo a las 12:00 AM se cierra la semana. Aquí ves qué líderes entregaron su reporte y quiénes están pendientes.

0
Total Líderes
0
✓ Entregaron
0
⏳ Pendientes
Q 0.00
Ofrenda Total
Semana: (Selecciona cualquier día de la semana)
#CódigoLíderPastor ZonaSup. SectorReporteOfrendaEstado
`; var todayStr=today(); var fe=document.getElementById('cuaFecha'); if(fe)fe.value=todayStr; cargarCuadreDominical(); } var _cuadreData=null; function cargarCuadreDominical(){ var fechaEl=document.getElementById('cuaFecha'); var fecha=fechaEl?fechaEl.value:today(); if(!fecha){toast('Selecciona una fecha','wa');return;} loading(true); call('getCuadreDominical',{fecha:fecha},function(_,res){ loading(false); if(!res||!res.ok){toast('Error al cargar cuadre','er');return;} _cuadreData=res.data||[]; document.getElementById('cuaTotal').textContent=res.totalLideres||0; document.getElementById('cuaEntregaron').textContent=res.entregaron||0; document.getElementById('cuaPendientes').textContent=res.pendientes||0; document.getElementById('cuaOfrenda').textContent='Q '+(res.ofrendaTotal||0).toFixed(2); renderCuadreTable(_cuadreData); }); } function renderCuadreTable(data){ var tb=document.getElementById('cuaTb');if(!tb)return; data=data||_cuadreData||[]; if(!data.length){ tb.innerHTML='

¡Todos los líderes entregaron su reporte!

'; return; } tb.innerHTML=data.map(function(r,i){ var tieneReporte=r.tieneReporte; var bg=tieneReporte?'':'style="background:#fff5f5"'; var estado=tieneReporte ?(r.ofrendaRecibida?'✓ Completado':'⚠ Pendiente Ofrenda') :'✕ Sin Reporte'; return ''+(i+1)+''+esc(r.codigo||'')+'' +''+esc(r.nombre||'')+'' +''+esc(r.pastorZona||'')+'' +''+esc(r.supSector||'')+'' +''+(tieneReporte?' Sí':' No')+'' +''+fmtQ(r.ofrendaTotal||0)+'' +''+estado+''; }).join(''); } function filtrarCuadre(q){ q=q||document.getElementById('cuaBuscar')?.value||''; var filtro=document.getElementById('cuaFiltro')?.value||''; var data=_cuadreData||[]; if(q)data=data.filter(function(r){return (r.nombre||'').toLowerCase().includes(q.toLowerCase())||(r.codigo||'').includes(q);}); if(filtro==='entregaron')data=data.filter(function(r){return r.tieneReporte;}); else if(filtro==='pendientes')data=data.filter(function(r){return !r.tieneReporte;}); renderCuadreTable(data); } // ── AUTO-REFRESH ────────────────────────────────────────── function aplicarTema(cfg){ var tc=(cfg&&cfg.theme_colors)||(APP.config&&APP.config.theme_colors)||''; if(!tc){var s=document.getElementById('themeOverride');if(s)s.remove();return;} var colors={}; try{colors=JSON.parse(tc);}catch(e){} var css=':root{'+Object.keys(colors).map(function(k){return k+':'+colors[k]+';'}).join('')+'}'; var el=document.getElementById('themeOverride'); if(!el){el=document.createElement('style');el.id='themeOverride';document.head.appendChild(el);} el.textContent=css; } function invalidateDashCache(){ call('invalidateDashCache',{},function(){}); } function startAutoRefresh(){ setInterval(function(){ if(!APP||!APP.user)return; call('getContactos',{},function(_,c){if(Array.isArray(c))APP.contactos=c;}); call('getHermanos',{},function(_,h){if(Array.isArray(h))APP.hermanos=h;}); try{var d=document.getElementById('mod_dashboard');if(d&&d.classList.contains('active'))recargarDash();}catch(e){} },120000); setInterval(function(){ if(!APP||!APP.user)return; call('getConfig',{},function(_,cfg){ if(cfg&&cfg.ok){cfg._loaded=true;APP.config=Object.assign(APP.config||{},cfg);aplicarTema(cfg);} }); },180000); } // ── CARGAR CONFIG EN PANTALLA DE LOGIN ANTES DE INGRESAR ── function cargarConfigLogin(){ call('getConfig', {}, function(err, cfg) { if(err || !cfg || !cfg.ok) return; APP.config=cfg; var lu=cfg.logo_url||cfg.logoUrl||''; if(lu){ var _t=''; var lw=document.getElementById('loginLogoWrap');if(lw)lw.innerHTML=_t; var sw=document.getElementById('sidebarLogoWrap');if(sw)sw.innerHTML=_t; } if(cfg.nombre){ var lt=document.getElementById('loginTitle');if(lt)lt.textContent=cfg.nombre; var st=document.getElementById('sbAppTitle');if(st)st.innerHTML=''+cfg.nombre+''; } }); } // ── INIT ────────────────────────────────────────────────── document.addEventListener('keydown',e=>{if(e.key==='Escape')closeModal();}); initLogo(); cargarConfigLogin(); startAutoRefresh(); // Esperar a que google.script.run esté disponible tryRestoreSession();